@hardkas/query 0.2.2-alpha → 0.3.0-alpha
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 +172 -35
- package/dist/index.js +576 -407
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -1,6 +1,164 @@
|
|
|
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(options) {
|
|
104
|
+
return {
|
|
105
|
+
schema: "hardkas.queryRebuild.v1",
|
|
106
|
+
ok: true,
|
|
107
|
+
artifacts: { scanned: 0, indexed: 0, duplicates: 0, corrupted: 0 },
|
|
108
|
+
events: { scanned: 0, indexed: 0, duplicates: 0, corrupted: 0 },
|
|
109
|
+
warnings: ["Filesystem backend does not support indexing"],
|
|
110
|
+
errors: []
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
async findReceipts(filters) {
|
|
114
|
+
return this.findArtifacts({ schema: "hardkas.txReceipt", ...filters });
|
|
115
|
+
}
|
|
116
|
+
async findTraces(filters) {
|
|
117
|
+
const artifacts = await this.findArtifacts({ schema: "hardkas.txTrace" });
|
|
118
|
+
if (filters?.txId) {
|
|
119
|
+
return artifacts.filter((a) => a.payload.txId === filters.txId);
|
|
120
|
+
}
|
|
121
|
+
return artifacts;
|
|
122
|
+
}
|
|
123
|
+
async sync(options) {
|
|
124
|
+
return this.rebuild(options);
|
|
125
|
+
}
|
|
126
|
+
async migrate() {
|
|
127
|
+
return { applied: 0 };
|
|
128
|
+
}
|
|
129
|
+
async executeRawSql(_sql) {
|
|
130
|
+
throw new Error("Raw SQL execution not supported by Filesystem backend. Use SQLite backend.");
|
|
131
|
+
}
|
|
132
|
+
async scanFiles(dir) {
|
|
133
|
+
const results = [];
|
|
134
|
+
try {
|
|
135
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
136
|
+
for (const entry of entries) {
|
|
137
|
+
const fullPath = path.join(dir, entry.name);
|
|
138
|
+
if (entry.isDirectory()) {
|
|
139
|
+
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
140
|
+
results.push(...await this.scanFiles(fullPath));
|
|
141
|
+
} else if (entry.name.endsWith(".json") && !entry.name.endsWith(".enc.json") && entry.name !== "events.jsonl") {
|
|
142
|
+
results.push(fullPath);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
} catch {
|
|
146
|
+
}
|
|
147
|
+
return results;
|
|
148
|
+
}
|
|
149
|
+
async readJson(file) {
|
|
150
|
+
try {
|
|
151
|
+
const content = await fs.readFile(file, "utf-8");
|
|
152
|
+
return JSON.parse(content);
|
|
153
|
+
} catch {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// src/adapters/artifact-adapter.ts
|
|
160
|
+
import fs2 from "fs/promises";
|
|
161
|
+
import path2 from "path";
|
|
4
162
|
import {
|
|
5
163
|
calculateContentHash,
|
|
6
164
|
verifyArtifactIntegrity,
|
|
@@ -123,144 +281,145 @@ var VALID_TRANSITIONS = {
|
|
|
123
281
|
"hardkas.txPlan": ["hardkas.signedTx"],
|
|
124
282
|
"hardkas.signedTx": ["hardkas.txReceipt"]
|
|
125
283
|
};
|
|
284
|
+
function createExplainBlock(options) {
|
|
285
|
+
return {
|
|
286
|
+
backend: options.backend,
|
|
287
|
+
executionPlan: options.executionPlan,
|
|
288
|
+
indexesUsed: options.indexesUsed || [],
|
|
289
|
+
filtersApplied: options.filtersApplied || [],
|
|
290
|
+
rowsRead: options.rowsRead,
|
|
291
|
+
scannedFiles: options.scannedFiles,
|
|
292
|
+
freshness: options.freshness,
|
|
293
|
+
warnings: options.warnings || []
|
|
294
|
+
};
|
|
295
|
+
}
|
|
126
296
|
function explainIntegrity(artifact, integrity) {
|
|
127
|
-
const
|
|
297
|
+
const causalChain = [];
|
|
298
|
+
const evidence = [];
|
|
128
299
|
let order = 1;
|
|
129
|
-
|
|
300
|
+
if (artifact.contentHash) {
|
|
301
|
+
evidence.push({ type: "contentHash", value: artifact.contentHash });
|
|
302
|
+
}
|
|
303
|
+
evidence.push({ type: "filePath", value: artifact.filePath });
|
|
304
|
+
causalChain.push({
|
|
130
305
|
order: order++,
|
|
131
306
|
assertion: integrity.schemaValid ? `Schema "${artifact.schema}" is a recognized HardKAS artifact schema` : `Schema "${artifact.schema}" is not recognized`,
|
|
132
307
|
evidence: `artifact.schema = "${artifact.schema}"`,
|
|
133
308
|
rule: "ARTIFACT_SCHEMAS constant (artifacts/constants.ts)"
|
|
134
309
|
});
|
|
135
310
|
if (artifact.contentHash) {
|
|
136
|
-
|
|
311
|
+
causalChain.push({
|
|
137
312
|
order: order++,
|
|
138
|
-
assertion: integrity.hashMatch ? "Content hash matches recomputed hash" : "Content hash does NOT match recomputed hash
|
|
139
|
-
evidence: `
|
|
313
|
+
assertion: integrity.hashMatch ? "Content hash matches recomputed hash" : "Content hash does NOT match recomputed hash",
|
|
314
|
+
evidence: `hash(payload) === "${artifact.contentHash}"`,
|
|
140
315
|
rule: "canonicalStringify + SHA-256 (artifacts/canonical.ts)"
|
|
141
316
|
});
|
|
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
317
|
}
|
|
150
318
|
if (integrity.errors.length > 0) {
|
|
151
319
|
for (const err of integrity.errors) {
|
|
152
|
-
|
|
320
|
+
causalChain.push({
|
|
153
321
|
order: order++,
|
|
154
|
-
assertion: `
|
|
155
|
-
evidence:
|
|
156
|
-
rule: "
|
|
322
|
+
assertion: `Semantic violation detected: ${err}`,
|
|
323
|
+
evidence: "Verification engine failure",
|
|
324
|
+
rule: "verifyArtifactSemantics (artifacts/verify.ts)"
|
|
157
325
|
});
|
|
158
326
|
}
|
|
159
327
|
}
|
|
160
328
|
return {
|
|
161
|
-
question: `Why is artifact "${artifact.schema}"
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
329
|
+
question: `Why is artifact "${artifact.schema}" ${integrity.ok ? "valid" : "invalid"}?`,
|
|
330
|
+
answer: integrity.ok ? "All deterministic checks (schema, hash, semantics) passed successfully." : `Verification failed: ${integrity.errors.join("; ")}`,
|
|
331
|
+
evidence,
|
|
332
|
+
causalChain,
|
|
333
|
+
model: "integrity-verifier",
|
|
334
|
+
confidence: "definitive"
|
|
167
335
|
};
|
|
168
336
|
}
|
|
169
337
|
function explainTransition(transition) {
|
|
170
|
-
const
|
|
338
|
+
const causalChain = [];
|
|
339
|
+
const evidence = [
|
|
340
|
+
{ type: "contentHash", value: transition.from.contentHash },
|
|
341
|
+
{ type: "contentHash", value: transition.to.contentHash }
|
|
342
|
+
];
|
|
171
343
|
let order = 1;
|
|
172
344
|
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({
|
|
345
|
+
causalChain.push({
|
|
180
346
|
order: order++,
|
|
181
|
-
assertion: transition.valid ? `Transition "${transition.from.schema}" \u2192 "${transition.to.schema}" is
|
|
182
|
-
evidence: `
|
|
183
|
-
rule: "Lineage transition table
|
|
347
|
+
assertion: transition.valid ? `Transition "${transition.from.schema}" \u2192 "${transition.to.schema}" is allowed` : `Transition "${transition.from.schema}" \u2192 "${transition.to.schema}" is NOT allowed`,
|
|
348
|
+
evidence: `allowed_from_${transition.from.schema} = [${allowed.join(", ")}]`,
|
|
349
|
+
rule: "Lineage transition table"
|
|
184
350
|
});
|
|
185
|
-
const
|
|
186
|
-
|
|
351
|
+
const contextMatch = transition.from.networkId === transition.to.networkId && transition.from.mode === transition.to.mode;
|
|
352
|
+
causalChain.push({
|
|
187
353
|
order: order++,
|
|
188
|
-
assertion:
|
|
189
|
-
evidence: `
|
|
190
|
-
rule: "
|
|
354
|
+
assertion: contextMatch ? "Execution context (network, mode) is consistent" : "EXECUTION CONTEXT MISMATCH detected",
|
|
355
|
+
evidence: `from: ${transition.from.networkId}/${transition.from.mode}, to: ${transition.to.networkId}/${transition.to.mode}`,
|
|
356
|
+
rule: "Context isolation policy"
|
|
191
357
|
});
|
|
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
358
|
return {
|
|
208
|
-
question: `Why
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
359
|
+
question: `Why transition ${transition.from.schema} \u2192 ${transition.to.schema}?`,
|
|
360
|
+
answer: transition.valid && contextMatch ? "Causal chain is consistent with HardKAS state transition rules." : "Workflow violation: invalid schema transition or context contamination.",
|
|
361
|
+
evidence,
|
|
362
|
+
causalChain,
|
|
363
|
+
model: "causal-lineage",
|
|
364
|
+
confidence: "definitive"
|
|
214
365
|
};
|
|
215
366
|
}
|
|
216
367
|
function explainOrphan(node, missingParentId) {
|
|
217
368
|
return {
|
|
218
|
-
question: `Why is artifact "${node.
|
|
219
|
-
|
|
220
|
-
|
|
369
|
+
question: `Why is artifact "${node.artifactId.slice(0, 8)}" an orphan?`,
|
|
370
|
+
answer: "The parent artifact referenced in the lineage metadata is missing from the indexed store.",
|
|
371
|
+
evidence: [
|
|
372
|
+
{ type: "artifactId", value: node.artifactId },
|
|
373
|
+
{ type: "artifactId", value: missingParentId }
|
|
374
|
+
],
|
|
375
|
+
causalChain: [
|
|
221
376
|
{
|
|
222
377
|
order: 1,
|
|
223
|
-
assertion:
|
|
224
|
-
evidence: `
|
|
225
|
-
rule: "Lineage
|
|
378
|
+
assertion: "Artifact defines a parent dependency",
|
|
379
|
+
evidence: `parentArtifactId = "${missingParentId}"`,
|
|
380
|
+
rule: "Lineage metadata requirement"
|
|
226
381
|
},
|
|
227
382
|
{
|
|
228
383
|
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"
|
|
384
|
+
assertion: "Parent artifact lookup failed",
|
|
385
|
+
evidence: "Index scan for artifactId returned 0 results",
|
|
386
|
+
rule: "Store integrity policy"
|
|
238
387
|
}
|
|
239
388
|
],
|
|
240
|
-
model: "
|
|
241
|
-
confidence: "definitive"
|
|
242
|
-
references: [node.contentHash, missingParentId]
|
|
389
|
+
model: "orphan-analysis",
|
|
390
|
+
confidence: "definitive"
|
|
243
391
|
};
|
|
244
392
|
}
|
|
245
|
-
function
|
|
246
|
-
|
|
393
|
+
function formatExplainBlock(block) {
|
|
394
|
+
const lines = [];
|
|
395
|
+
lines.push(` [Explain: Technical Diagnostics]`);
|
|
396
|
+
lines.push(` Backend: ${block.backend}`);
|
|
397
|
+
lines.push(` Freshness: ${block.freshness}`);
|
|
398
|
+
lines.push(` Rows Read: ${block.rowsRead}`);
|
|
399
|
+
lines.push(` Files Scan: ${block.scannedFiles}`);
|
|
400
|
+
if (block.executionPlan.length > 0) {
|
|
401
|
+
lines.push(` Plan: ${block.executionPlan.join(" \u2192 ")}`);
|
|
402
|
+
}
|
|
403
|
+
if (block.warnings.length > 0) {
|
|
404
|
+
lines.push(` Warnings:`);
|
|
405
|
+
for (const w of block.warnings) lines.push(` \u26A0 ${w}`);
|
|
406
|
+
}
|
|
407
|
+
return lines.join("\n");
|
|
247
408
|
}
|
|
248
|
-
function
|
|
409
|
+
function formatWhyBlock(block) {
|
|
249
410
|
const lines = [];
|
|
250
|
-
lines.push(`
|
|
411
|
+
lines.push(` [Why: Causal Analysis]`);
|
|
412
|
+
lines.push(` Q: ${block.question}`);
|
|
413
|
+
lines.push(` A: ${block.answer}`);
|
|
251
414
|
lines.push("");
|
|
252
|
-
for (const step of
|
|
253
|
-
lines.push(`
|
|
254
|
-
lines.push(`
|
|
255
|
-
if (step.rule) {
|
|
256
|
-
lines.push(` Rule: ${step.rule}`);
|
|
257
|
-
}
|
|
415
|
+
for (const step of block.causalChain) {
|
|
416
|
+
lines.push(` ${step.order}. ${step.assertion}`);
|
|
417
|
+
lines.push(` Evidence: ${step.evidence}`);
|
|
418
|
+
if (step.rule) lines.push(` Rule: ${step.rule}`);
|
|
258
419
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
if (chain.references.length > 0) {
|
|
263
|
-
lines.push(`References: ${chain.references.map((r) => r.slice(0, 16) + "...").join(", ")}`);
|
|
420
|
+
if (block.evidence.length > 0) {
|
|
421
|
+
lines.push("");
|
|
422
|
+
lines.push(` Evidence Refs: ${block.evidence.map((e) => `${e.type}:${e.value.slice(0, 12)}...`).join(", ")}`);
|
|
264
423
|
}
|
|
265
424
|
return lines.join("\n");
|
|
266
425
|
}
|
|
@@ -270,8 +429,10 @@ var KNOWN_SCHEMAS = new Set(Object.values(ARTIFACT_SCHEMAS));
|
|
|
270
429
|
var ArtifactQueryAdapter = class {
|
|
271
430
|
domain = "artifacts";
|
|
272
431
|
rootDir;
|
|
273
|
-
|
|
432
|
+
backend;
|
|
433
|
+
constructor(rootDir, backend) {
|
|
274
434
|
this.rootDir = rootDir;
|
|
435
|
+
this.backend = backend;
|
|
275
436
|
}
|
|
276
437
|
supportedOps() {
|
|
277
438
|
return ["list", "inspect", "diff", "verify"];
|
|
@@ -298,12 +459,25 @@ var ArtifactQueryAdapter = class {
|
|
|
298
459
|
// -------------------------------------------------------------------------
|
|
299
460
|
async executeList(request) {
|
|
300
461
|
const start = Date.now();
|
|
301
|
-
const
|
|
462
|
+
const docs = await this.backend.findArtifacts();
|
|
302
463
|
const items = [];
|
|
303
|
-
for (const
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
|
|
464
|
+
for (const doc of docs) {
|
|
465
|
+
const item = {
|
|
466
|
+
filePath: doc.path,
|
|
467
|
+
schema: doc.schema,
|
|
468
|
+
version: doc.version,
|
|
469
|
+
networkId: doc.networkId,
|
|
470
|
+
mode: doc.mode,
|
|
471
|
+
createdAt: doc.createdAt,
|
|
472
|
+
contentHash: doc.contentHash,
|
|
473
|
+
payload: doc.payload,
|
|
474
|
+
// Optional mapping for common fields
|
|
475
|
+
status: doc.payload.status,
|
|
476
|
+
from: doc.payload.from,
|
|
477
|
+
to: doc.payload.to,
|
|
478
|
+
amountSompi: doc.payload.amountSompi,
|
|
479
|
+
lineage: doc.payload.lineage
|
|
480
|
+
};
|
|
307
481
|
if (evaluateFilters(item, request.filters)) {
|
|
308
482
|
items.push(item);
|
|
309
483
|
}
|
|
@@ -311,9 +485,9 @@ var ArtifactQueryAdapter = class {
|
|
|
311
485
|
const sorted = this.sortItems(items, request.sort);
|
|
312
486
|
const total = sorted.length;
|
|
313
487
|
const paged = sorted.slice(request.offset, request.offset + request.limit);
|
|
314
|
-
let
|
|
488
|
+
let why;
|
|
315
489
|
if (request.explain) {
|
|
316
|
-
|
|
490
|
+
why = paged.map((item) => explainIntegrity(item, {
|
|
317
491
|
ok: true,
|
|
318
492
|
hashMatch: true,
|
|
319
493
|
schemaValid: KNOWN_SCHEMAS.has(item.schema),
|
|
@@ -328,11 +502,11 @@ var ArtifactQueryAdapter = class {
|
|
|
328
502
|
truncated: total > request.offset + request.limit,
|
|
329
503
|
deterministic: true,
|
|
330
504
|
queryHash: computeQueryHash(paged),
|
|
331
|
-
|
|
505
|
+
why,
|
|
332
506
|
annotations: {
|
|
333
507
|
executedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
334
508
|
executionMs: Date.now() - start,
|
|
335
|
-
filesScanned:
|
|
509
|
+
filesScanned: docs.length
|
|
336
510
|
}
|
|
337
511
|
};
|
|
338
512
|
}
|
|
@@ -383,9 +557,9 @@ var ArtifactQueryAdapter = class {
|
|
|
383
557
|
staleness,
|
|
384
558
|
lineageStatus
|
|
385
559
|
};
|
|
386
|
-
let
|
|
560
|
+
let why;
|
|
387
561
|
if (request.explain) {
|
|
388
|
-
|
|
562
|
+
why = [explainIntegrity(item, inspectResult.integrity)];
|
|
389
563
|
}
|
|
390
564
|
return {
|
|
391
565
|
domain: "artifacts",
|
|
@@ -395,7 +569,7 @@ var ArtifactQueryAdapter = class {
|
|
|
395
569
|
truncated: false,
|
|
396
570
|
deterministic: true,
|
|
397
571
|
queryHash: computeQueryHash([inspectResult]),
|
|
398
|
-
|
|
572
|
+
why,
|
|
399
573
|
annotations: {
|
|
400
574
|
executedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
401
575
|
executionMs: Date.now() - start,
|
|
@@ -472,12 +646,12 @@ var ArtifactQueryAdapter = class {
|
|
|
472
646
|
async walkDir(dir, out) {
|
|
473
647
|
let entries;
|
|
474
648
|
try {
|
|
475
|
-
entries = await
|
|
649
|
+
entries = await fs2.readdir(dir, { withFileTypes: true });
|
|
476
650
|
} catch {
|
|
477
651
|
return;
|
|
478
652
|
}
|
|
479
653
|
for (const entry of entries) {
|
|
480
|
-
const full =
|
|
654
|
+
const full = path2.join(dir, entry.name);
|
|
481
655
|
if (entry.isDirectory()) {
|
|
482
656
|
if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "keystores") continue;
|
|
483
657
|
await this.walkDir(full, out);
|
|
@@ -488,7 +662,7 @@ var ArtifactQueryAdapter = class {
|
|
|
488
662
|
}
|
|
489
663
|
async readJsonSafe(filePath) {
|
|
490
664
|
try {
|
|
491
|
-
const content = await
|
|
665
|
+
const content = await fs2.readFile(filePath, "utf-8");
|
|
492
666
|
return JSON.parse(content);
|
|
493
667
|
} catch {
|
|
494
668
|
return null;
|
|
@@ -496,7 +670,7 @@ var ArtifactQueryAdapter = class {
|
|
|
496
670
|
}
|
|
497
671
|
async resolveTarget(target) {
|
|
498
672
|
if (target.includes("/") || target.includes("\\") || target.endsWith(".json")) {
|
|
499
|
-
return
|
|
673
|
+
return path2.resolve(target);
|
|
500
674
|
}
|
|
501
675
|
const files = await this.scanArtifactFiles();
|
|
502
676
|
for (const f of files) {
|
|
@@ -517,7 +691,7 @@ var ArtifactQueryAdapter = class {
|
|
|
517
691
|
});
|
|
518
692
|
} else {
|
|
519
693
|
sorted.sort((a, b) => {
|
|
520
|
-
const cmp = b.createdAt.localeCompare(a.createdAt);
|
|
694
|
+
const cmp = (b.createdAt ?? "").localeCompare(a.createdAt ?? "");
|
|
521
695
|
return cmp !== 0 ? cmp : a.schema.localeCompare(b.schema);
|
|
522
696
|
});
|
|
523
697
|
}
|
|
@@ -533,6 +707,7 @@ function toArtifactQueryItem(raw, filePath) {
|
|
|
533
707
|
mode: raw.mode || "unknown",
|
|
534
708
|
createdAt: raw.createdAt || "",
|
|
535
709
|
contentHash: raw.contentHash,
|
|
710
|
+
payload: raw,
|
|
536
711
|
from: raw.from,
|
|
537
712
|
to: raw.to,
|
|
538
713
|
amountSompi: raw.amountSompi,
|
|
@@ -554,8 +729,8 @@ function classifyStaleness(hours) {
|
|
|
554
729
|
}
|
|
555
730
|
|
|
556
731
|
// src/adapters/lineage-adapter.ts
|
|
557
|
-
import
|
|
558
|
-
import
|
|
732
|
+
import fs3 from "fs/promises";
|
|
733
|
+
import path3 from "path";
|
|
559
734
|
var VALID_TRANSITIONS2 = {
|
|
560
735
|
"hardkas.snapshot": ["hardkas.txPlan"],
|
|
561
736
|
"hardkas.txPlan": ["hardkas.signedTx"],
|
|
@@ -564,8 +739,10 @@ var VALID_TRANSITIONS2 = {
|
|
|
564
739
|
var LineageQueryAdapter = class {
|
|
565
740
|
domain = "lineage";
|
|
566
741
|
rootDir;
|
|
567
|
-
|
|
742
|
+
backend;
|
|
743
|
+
constructor(rootDir, backend) {
|
|
568
744
|
this.rootDir = rootDir;
|
|
745
|
+
this.backend = backend;
|
|
569
746
|
}
|
|
570
747
|
supportedOps() {
|
|
571
748
|
return ["chain", "transitions", "orphans"];
|
|
@@ -611,9 +788,9 @@ var LineageQueryAdapter = class {
|
|
|
611
788
|
transitions,
|
|
612
789
|
complete
|
|
613
790
|
};
|
|
614
|
-
let
|
|
791
|
+
let why;
|
|
615
792
|
if (request.explain) {
|
|
616
|
-
|
|
793
|
+
why = transitions.map((t) => explainTransition(t));
|
|
617
794
|
}
|
|
618
795
|
return {
|
|
619
796
|
domain: "lineage",
|
|
@@ -623,7 +800,7 @@ var LineageQueryAdapter = class {
|
|
|
623
800
|
truncated: false,
|
|
624
801
|
deterministic: true,
|
|
625
802
|
queryHash: computeQueryHash([result]),
|
|
626
|
-
|
|
803
|
+
why,
|
|
627
804
|
annotations: {
|
|
628
805
|
executedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
629
806
|
executionMs: Date.now() - start,
|
|
@@ -660,9 +837,9 @@ var LineageQueryAdapter = class {
|
|
|
660
837
|
return a.from.contentHash.localeCompare(b.from.contentHash);
|
|
661
838
|
});
|
|
662
839
|
const paged = transitions.slice(request.offset, request.offset + request.limit);
|
|
663
|
-
let
|
|
840
|
+
let why;
|
|
664
841
|
if (request.explain) {
|
|
665
|
-
|
|
842
|
+
why = paged.map((t) => explainTransition(t));
|
|
666
843
|
}
|
|
667
844
|
return {
|
|
668
845
|
domain: "lineage",
|
|
@@ -672,7 +849,7 @@ var LineageQueryAdapter = class {
|
|
|
672
849
|
truncated: transitions.length > request.offset + request.limit,
|
|
673
850
|
deterministic: true,
|
|
674
851
|
queryHash: computeQueryHash(paged),
|
|
675
|
-
|
|
852
|
+
why,
|
|
676
853
|
annotations: {
|
|
677
854
|
executedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
678
855
|
executionMs: Date.now() - start,
|
|
@@ -700,9 +877,9 @@ var LineageQueryAdapter = class {
|
|
|
700
877
|
}
|
|
701
878
|
orphans.sort((a, b) => a.node.contentHash.localeCompare(b.node.contentHash));
|
|
702
879
|
const paged = orphans.slice(request.offset, request.offset + request.limit);
|
|
703
|
-
let
|
|
880
|
+
let why;
|
|
704
881
|
if (request.explain) {
|
|
705
|
-
|
|
882
|
+
why = paged.map((o) => explainOrphan(o.node, o.missingParentId));
|
|
706
883
|
}
|
|
707
884
|
return {
|
|
708
885
|
domain: "lineage",
|
|
@@ -712,7 +889,7 @@ var LineageQueryAdapter = class {
|
|
|
712
889
|
truncated: orphans.length > request.offset + request.limit,
|
|
713
890
|
deterministic: true,
|
|
714
891
|
queryHash: computeQueryHash(paged),
|
|
715
|
-
|
|
892
|
+
why,
|
|
716
893
|
annotations: {
|
|
717
894
|
executedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
718
895
|
executionMs: Date.now() - start,
|
|
@@ -728,22 +905,22 @@ var LineageQueryAdapter = class {
|
|
|
728
905
|
const byArtifactId = /* @__PURE__ */ new Map();
|
|
729
906
|
const byContentHash = /* @__PURE__ */ new Map();
|
|
730
907
|
const children = /* @__PURE__ */ new Map();
|
|
731
|
-
const
|
|
732
|
-
for (const
|
|
733
|
-
const raw =
|
|
908
|
+
const docs = await this.backend.findArtifacts();
|
|
909
|
+
for (const doc of docs) {
|
|
910
|
+
const raw = doc.payload;
|
|
734
911
|
if (!raw?.schema || !raw.lineage) continue;
|
|
735
912
|
const node = {
|
|
736
|
-
contentHash:
|
|
737
|
-
schema:
|
|
913
|
+
contentHash: doc.contentHash,
|
|
914
|
+
schema: doc.schema,
|
|
738
915
|
artifactId: raw.lineage.artifactId || "",
|
|
739
916
|
parentArtifactId: raw.lineage.parentArtifactId,
|
|
740
917
|
rootArtifactId: raw.lineage.rootArtifactId || "",
|
|
741
918
|
lineageId: raw.lineage.lineageId || "",
|
|
742
919
|
sequence: raw.lineage.sequence,
|
|
743
|
-
filePath,
|
|
744
|
-
networkId:
|
|
745
|
-
mode:
|
|
746
|
-
createdAt:
|
|
920
|
+
filePath: doc.path,
|
|
921
|
+
networkId: doc.networkId,
|
|
922
|
+
mode: doc.mode,
|
|
923
|
+
createdAt: doc.createdAt || ""
|
|
747
924
|
};
|
|
748
925
|
nodes.push(node);
|
|
749
926
|
if (node.artifactId) byArtifactId.set(node.artifactId, node);
|
|
@@ -754,7 +931,7 @@ var LineageQueryAdapter = class {
|
|
|
754
931
|
children.set(node.parentArtifactId, existing);
|
|
755
932
|
}
|
|
756
933
|
}
|
|
757
|
-
return { nodes, byArtifactId, byContentHash, children, totalFiles:
|
|
934
|
+
return { nodes, byArtifactId, byContentHash, children, totalFiles: docs.length };
|
|
758
935
|
}
|
|
759
936
|
// -------------------------------------------------------------------------
|
|
760
937
|
// Graph Traversal
|
|
@@ -812,12 +989,12 @@ var LineageQueryAdapter = class {
|
|
|
812
989
|
async walkDir(dir, out) {
|
|
813
990
|
let entries;
|
|
814
991
|
try {
|
|
815
|
-
entries = await
|
|
992
|
+
entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
816
993
|
} catch {
|
|
817
994
|
return;
|
|
818
995
|
}
|
|
819
996
|
for (const entry of entries) {
|
|
820
|
-
const full =
|
|
997
|
+
const full = path3.join(dir, entry.name);
|
|
821
998
|
if (entry.isDirectory()) {
|
|
822
999
|
if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "keystores") continue;
|
|
823
1000
|
await this.walkDir(full, out);
|
|
@@ -828,7 +1005,7 @@ var LineageQueryAdapter = class {
|
|
|
828
1005
|
}
|
|
829
1006
|
async readJsonSafe(filePath) {
|
|
830
1007
|
try {
|
|
831
|
-
const content = await
|
|
1008
|
+
const content = await fs3.readFile(filePath, "utf-8");
|
|
832
1009
|
return JSON.parse(content);
|
|
833
1010
|
} catch {
|
|
834
1011
|
return null;
|
|
@@ -837,14 +1014,15 @@ var LineageQueryAdapter = class {
|
|
|
837
1014
|
};
|
|
838
1015
|
|
|
839
1016
|
// src/adapters/replay-adapter.ts
|
|
840
|
-
import
|
|
841
|
-
import path3 from "path";
|
|
1017
|
+
import fs4 from "fs/promises";
|
|
842
1018
|
import { calculateContentHash as calculateContentHash2 } from "@hardkas/artifacts";
|
|
843
1019
|
var ReplayQueryAdapter = class {
|
|
844
1020
|
domain = "replay";
|
|
845
1021
|
rootDir;
|
|
846
|
-
|
|
1022
|
+
backend;
|
|
1023
|
+
constructor(rootDir, backend) {
|
|
847
1024
|
this.rootDir = rootDir;
|
|
1025
|
+
this.backend = backend;
|
|
848
1026
|
}
|
|
849
1027
|
supportedOps() {
|
|
850
1028
|
return ["list", "summary", "divergences", "invariants"];
|
|
@@ -871,13 +1049,15 @@ var ReplayQueryAdapter = class {
|
|
|
871
1049
|
// -------------------------------------------------------------------------
|
|
872
1050
|
async executeList(request) {
|
|
873
1051
|
const start = Date.now();
|
|
874
|
-
const receipts = await this.
|
|
875
|
-
|
|
1052
|
+
const receipts = await this.backend.findReceipts({
|
|
1053
|
+
status: request.filters.find((f) => f.field === "status")?.value
|
|
1054
|
+
});
|
|
1055
|
+
const traces = await this.backend.findTraces();
|
|
876
1056
|
const traceMap = new Map(traces.map((t) => [t.txId, t]));
|
|
877
1057
|
const items = [];
|
|
878
1058
|
for (const r of receipts) {
|
|
879
1059
|
const trace = traceMap.get(r.txId);
|
|
880
|
-
const item = toSummary(r, trace);
|
|
1060
|
+
const item = toSummary(r.payload, trace?.payload);
|
|
881
1061
|
if (evaluateFilters(item, request.filters)) {
|
|
882
1062
|
items.push(item);
|
|
883
1063
|
}
|
|
@@ -910,10 +1090,11 @@ var ReplayQueryAdapter = class {
|
|
|
910
1090
|
const start = Date.now();
|
|
911
1091
|
const txId = request.params["txId"];
|
|
912
1092
|
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
|
|
1093
|
+
const receipt = await this.backend.getArtifact(txId);
|
|
1094
|
+
if (!receipt || receipt.schema !== "hardkas.txReceipt") throw new Error(`Receipt not found for txId: ${txId}`);
|
|
1095
|
+
const traces = await this.backend.findTraces({ txId });
|
|
1096
|
+
const trace = traces[0];
|
|
1097
|
+
const item = toSummary(receipt.payload, trace?.payload);
|
|
917
1098
|
return {
|
|
918
1099
|
domain: "replay",
|
|
919
1100
|
op: "summary",
|
|
@@ -934,9 +1115,10 @@ var ReplayQueryAdapter = class {
|
|
|
934
1115
|
// -------------------------------------------------------------------------
|
|
935
1116
|
async executeDivergences(request) {
|
|
936
1117
|
const start = Date.now();
|
|
937
|
-
const receipts = await this.
|
|
1118
|
+
const receipts = await this.backend.findReceipts();
|
|
938
1119
|
const divergences = [];
|
|
939
|
-
for (const
|
|
1120
|
+
for (const doc of receipts) {
|
|
1121
|
+
const receipt = doc.payload;
|
|
940
1122
|
if (receipt.contentHash) {
|
|
941
1123
|
const recomputed = computeContentHashSafe(receipt);
|
|
942
1124
|
if (recomputed !== receipt.contentHash) {
|
|
@@ -994,9 +1176,9 @@ var ReplayQueryAdapter = class {
|
|
|
994
1176
|
}
|
|
995
1177
|
divergences.sort((a, b) => a.txId.localeCompare(b.txId));
|
|
996
1178
|
const paged = divergences.slice(request.offset, request.offset + request.limit);
|
|
997
|
-
let
|
|
1179
|
+
let why;
|
|
998
1180
|
if (request.explain) {
|
|
999
|
-
|
|
1181
|
+
why = paged.map((d) => explainDivergence(d));
|
|
1000
1182
|
}
|
|
1001
1183
|
return {
|
|
1002
1184
|
domain: "replay",
|
|
@@ -1006,7 +1188,7 @@ var ReplayQueryAdapter = class {
|
|
|
1006
1188
|
truncated: divergences.length > request.offset + request.limit,
|
|
1007
1189
|
deterministic: true,
|
|
1008
1190
|
queryHash: computeQueryHash(paged),
|
|
1009
|
-
|
|
1191
|
+
why,
|
|
1010
1192
|
annotations: {
|
|
1011
1193
|
executedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1012
1194
|
executionMs: Date.now() - start,
|
|
@@ -1021,8 +1203,9 @@ var ReplayQueryAdapter = class {
|
|
|
1021
1203
|
const start = Date.now();
|
|
1022
1204
|
const txId = request.params["txId"];
|
|
1023
1205
|
if (!txId) throw new Error("invariants requires params.txId");
|
|
1024
|
-
const
|
|
1025
|
-
if (!
|
|
1206
|
+
const doc = await this.backend.getArtifact(txId);
|
|
1207
|
+
if (!doc || doc.schema !== "hardkas.txReceipt") throw new Error(`Receipt not found for txId: ${txId}`);
|
|
1208
|
+
const receipt = doc.payload;
|
|
1026
1209
|
const issues = [];
|
|
1027
1210
|
const planIntegrity = receipt.contentHash ? computeContentHashSafe(receipt) === receipt.contentHash : true;
|
|
1028
1211
|
if (!planIntegrity) {
|
|
@@ -1065,9 +1248,9 @@ var ReplayQueryAdapter = class {
|
|
|
1065
1248
|
utxoConservation,
|
|
1066
1249
|
issues
|
|
1067
1250
|
};
|
|
1068
|
-
let
|
|
1251
|
+
let why;
|
|
1069
1252
|
if (request.explain) {
|
|
1070
|
-
|
|
1253
|
+
why = [explainInvariants(result)];
|
|
1071
1254
|
}
|
|
1072
1255
|
return {
|
|
1073
1256
|
domain: "replay",
|
|
@@ -1077,7 +1260,7 @@ var ReplayQueryAdapter = class {
|
|
|
1077
1260
|
truncated: false,
|
|
1078
1261
|
deterministic: true,
|
|
1079
1262
|
queryHash: computeQueryHash([result]),
|
|
1080
|
-
|
|
1263
|
+
why,
|
|
1081
1264
|
annotations: {
|
|
1082
1265
|
executedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1083
1266
|
executionMs: Date.now() - start,
|
|
@@ -1085,43 +1268,9 @@ var ReplayQueryAdapter = class {
|
|
|
1085
1268
|
}
|
|
1086
1269
|
};
|
|
1087
1270
|
}
|
|
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
1271
|
async readJsonSafe(filePath) {
|
|
1123
1272
|
try {
|
|
1124
|
-
const content = await
|
|
1273
|
+
const content = await fs4.readFile(filePath, "utf-8");
|
|
1125
1274
|
return JSON.parse(content);
|
|
1126
1275
|
} catch {
|
|
1127
1276
|
return null;
|
|
@@ -1164,43 +1313,55 @@ var DIVERGENCE_RULES = {
|
|
|
1164
1313
|
};
|
|
1165
1314
|
function explainDivergence(d) {
|
|
1166
1315
|
return {
|
|
1167
|
-
question: `Why is tx ${d.txId.slice(0, 16)}...
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
{
|
|
1316
|
+
question: `Why is tx ${d.txId.slice(0, 16)}... divergent?`,
|
|
1317
|
+
answer: `Field "${d.field}" shows unexpected non-deterministic behavior (${d.kind}).`,
|
|
1318
|
+
evidence: [{ type: "txId", value: d.txId }],
|
|
1319
|
+
causalChain: [
|
|
1320
|
+
{
|
|
1321
|
+
order: 1,
|
|
1322
|
+
assertion: `Value mismatch in "${d.field}"`,
|
|
1323
|
+
evidence: `Expected: ${d.expected.slice(0, 40)}, Actual: ${d.actual.slice(0, 40)}`,
|
|
1324
|
+
rule: DIVERGENCE_RULES[d.kind]
|
|
1325
|
+
},
|
|
1326
|
+
{
|
|
1327
|
+
order: 2,
|
|
1328
|
+
assertion: "Divergence detected in replay comparison",
|
|
1329
|
+
evidence: "Verification engine mismatch",
|
|
1330
|
+
rule: "Invariant validation policy"
|
|
1331
|
+
}
|
|
1172
1332
|
],
|
|
1173
|
-
model: "replay-
|
|
1174
|
-
confidence: "definitive"
|
|
1175
|
-
references: [d.txId]
|
|
1333
|
+
model: "replay-analysis",
|
|
1334
|
+
confidence: "definitive"
|
|
1176
1335
|
};
|
|
1177
1336
|
}
|
|
1178
1337
|
function explainInvariants(result) {
|
|
1179
|
-
const
|
|
1338
|
+
const causalChain = [];
|
|
1180
1339
|
let order = 1;
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1340
|
+
causalChain.push({ order: order++, assertion: result.planIntegrity ? "Plan integrity is OK" : "Plan integrity FAILED", evidence: `planIntegrity=${result.planIntegrity}`, rule: "SHA-256 canonical consistency" });
|
|
1341
|
+
causalChain.push({ order: order++, assertion: result.receiptReproducible ? "Receipt is reproducible" : "Receipt is NOT reproducible", evidence: `receiptReproducible=${result.receiptReproducible}`, rule: "Replay evidence requirements" });
|
|
1342
|
+
causalChain.push({ order: order++, assertion: result.stateTransitionValid ? "State transition is valid" : "State transition INVALID", evidence: `stateTransitionValid=${result.stateTransitionValid}`, rule: "Status/State alignment" });
|
|
1343
|
+
causalChain.push({ order: order++, assertion: result.utxoConservation ? "UTXO conservation holds" : "UTXO conservation VIOLATED", evidence: `utxoConservation=${result.utxoConservation}`, rule: "Value conservation policy" });
|
|
1185
1344
|
const allOk = result.planIntegrity && result.receiptReproducible && result.stateTransitionValid && result.utxoConservation;
|
|
1186
1345
|
return {
|
|
1187
1346
|
question: `Are replay invariants satisfied for tx ${result.txId.slice(0, 16)}...?`,
|
|
1188
|
-
|
|
1189
|
-
|
|
1347
|
+
answer: allOk ? "All replay invariants satisfied." : `Violations found: ${result.issues.join("; ")}`,
|
|
1348
|
+
evidence: [{ type: "txId", value: result.txId }],
|
|
1349
|
+
causalChain,
|
|
1190
1350
|
model: "replay-invariants",
|
|
1191
|
-
confidence: "definitive"
|
|
1192
|
-
references: [result.txId]
|
|
1351
|
+
confidence: "definitive"
|
|
1193
1352
|
};
|
|
1194
1353
|
}
|
|
1195
1354
|
|
|
1196
1355
|
// src/adapters/dag-adapter.ts
|
|
1197
|
-
import
|
|
1356
|
+
import fs5 from "fs/promises";
|
|
1198
1357
|
import path4 from "path";
|
|
1199
1358
|
var DagQueryAdapter = class {
|
|
1200
1359
|
domain = "dag";
|
|
1201
1360
|
rootDir;
|
|
1202
|
-
|
|
1361
|
+
backend;
|
|
1362
|
+
constructor(rootDir, backend) {
|
|
1203
1363
|
this.rootDir = rootDir;
|
|
1364
|
+
this.backend = backend;
|
|
1204
1365
|
}
|
|
1205
1366
|
supportedOps() {
|
|
1206
1367
|
return ["conflicts", "displaced", "history", "sink-path", "anomalies"];
|
|
@@ -1240,9 +1401,9 @@ var DagQueryAdapter = class {
|
|
|
1240
1401
|
}));
|
|
1241
1402
|
items.sort((a, b) => a.outpoint.localeCompare(b.outpoint));
|
|
1242
1403
|
const paged = items.slice(request.offset, request.offset + request.limit);
|
|
1243
|
-
let
|
|
1404
|
+
let why;
|
|
1244
1405
|
if (request.explain) {
|
|
1245
|
-
|
|
1406
|
+
why = paged.map((c) => explainConflict(c, dag));
|
|
1246
1407
|
}
|
|
1247
1408
|
return {
|
|
1248
1409
|
domain: "dag",
|
|
@@ -1252,7 +1413,7 @@ var DagQueryAdapter = class {
|
|
|
1252
1413
|
truncated: items.length > request.offset + request.limit,
|
|
1253
1414
|
deterministic: true,
|
|
1254
1415
|
queryHash: computeQueryHash(paged),
|
|
1255
|
-
|
|
1416
|
+
why,
|
|
1256
1417
|
annotations: { executedAt: (/* @__PURE__ */ new Date()).toISOString(), executionMs: Date.now() - start }
|
|
1257
1418
|
};
|
|
1258
1419
|
}
|
|
@@ -1276,9 +1437,9 @@ var DagQueryAdapter = class {
|
|
|
1276
1437
|
});
|
|
1277
1438
|
items.sort((a, b) => a.txId.localeCompare(b.txId));
|
|
1278
1439
|
const paged = items.slice(request.offset, request.offset + request.limit);
|
|
1279
|
-
let
|
|
1440
|
+
let why;
|
|
1280
1441
|
if (request.explain) {
|
|
1281
|
-
|
|
1442
|
+
why = paged.map((d) => explainDisplacement(d, dag));
|
|
1282
1443
|
}
|
|
1283
1444
|
return {
|
|
1284
1445
|
domain: "dag",
|
|
@@ -1288,7 +1449,7 @@ var DagQueryAdapter = class {
|
|
|
1288
1449
|
truncated: items.length > request.offset + request.limit,
|
|
1289
1450
|
deterministic: true,
|
|
1290
1451
|
queryHash: computeQueryHash(paged),
|
|
1291
|
-
|
|
1452
|
+
why,
|
|
1292
1453
|
annotations: { executedAt: (/* @__PURE__ */ new Date()).toISOString(), executionMs: Date.now() - start }
|
|
1293
1454
|
};
|
|
1294
1455
|
}
|
|
@@ -1328,9 +1489,9 @@ var DagQueryAdapter = class {
|
|
|
1328
1489
|
}
|
|
1329
1490
|
}
|
|
1330
1491
|
entries.sort((a, b) => a.daaScore.localeCompare(b.daaScore));
|
|
1331
|
-
let
|
|
1492
|
+
let why;
|
|
1332
1493
|
if (request.explain) {
|
|
1333
|
-
|
|
1494
|
+
why = entries.map((e) => explainTxHistory(e, dag));
|
|
1334
1495
|
}
|
|
1335
1496
|
return {
|
|
1336
1497
|
domain: "dag",
|
|
@@ -1340,7 +1501,7 @@ var DagQueryAdapter = class {
|
|
|
1340
1501
|
truncated: false,
|
|
1341
1502
|
deterministic: true,
|
|
1342
1503
|
queryHash: computeQueryHash(entries),
|
|
1343
|
-
|
|
1504
|
+
why,
|
|
1344
1505
|
annotations: { executedAt: (/* @__PURE__ */ new Date()).toISOString(), executionMs: Date.now() - start }
|
|
1345
1506
|
};
|
|
1346
1507
|
}
|
|
@@ -1428,9 +1589,9 @@ var DagQueryAdapter = class {
|
|
|
1428
1589
|
}
|
|
1429
1590
|
anomalies.sort((a, b) => a.kind.localeCompare(b.kind));
|
|
1430
1591
|
const paged = anomalies.slice(request.offset, request.offset + request.limit);
|
|
1431
|
-
let
|
|
1592
|
+
let why;
|
|
1432
1593
|
if (request.explain) {
|
|
1433
|
-
|
|
1594
|
+
why = paged.map((a) => explainAnomaly(a, dag));
|
|
1434
1595
|
}
|
|
1435
1596
|
return {
|
|
1436
1597
|
domain: "dag",
|
|
@@ -1440,7 +1601,7 @@ var DagQueryAdapter = class {
|
|
|
1440
1601
|
truncated: anomalies.length > request.offset + request.limit,
|
|
1441
1602
|
deterministic: true,
|
|
1442
1603
|
queryHash: computeQueryHash(paged),
|
|
1443
|
-
|
|
1604
|
+
why,
|
|
1444
1605
|
annotations: { executedAt: (/* @__PURE__ */ new Date()).toISOString(), executionMs: Date.now() - start }
|
|
1445
1606
|
};
|
|
1446
1607
|
}
|
|
@@ -1450,7 +1611,7 @@ var DagQueryAdapter = class {
|
|
|
1450
1611
|
async loadDag() {
|
|
1451
1612
|
const statePath = path4.join(this.rootDir, ".hardkas", "state.json");
|
|
1452
1613
|
try {
|
|
1453
|
-
const content = await
|
|
1614
|
+
const content = await fs5.readFile(statePath, "utf-8");
|
|
1454
1615
|
const state = JSON.parse(content);
|
|
1455
1616
|
return state.dag ?? null;
|
|
1456
1617
|
} catch {
|
|
@@ -1471,7 +1632,7 @@ function emptyDagResult(op, start) {
|
|
|
1471
1632
|
};
|
|
1472
1633
|
}
|
|
1473
1634
|
function explainConflict(conflict, dag) {
|
|
1474
|
-
const
|
|
1635
|
+
const causalChain = [];
|
|
1475
1636
|
let order = 1;
|
|
1476
1637
|
let winnerBlockId;
|
|
1477
1638
|
let winnerDaaScore = "0";
|
|
@@ -1484,92 +1645,87 @@ function explainConflict(conflict, dag) {
|
|
|
1484
1645
|
break;
|
|
1485
1646
|
}
|
|
1486
1647
|
}
|
|
1487
|
-
|
|
1648
|
+
causalChain.push({
|
|
1488
1649
|
order: order++,
|
|
1489
|
-
assertion: `Outpoint ${conflict.outpoint}
|
|
1650
|
+
assertion: `Outpoint ${conflict.outpoint} spent by multiple txs`,
|
|
1490
1651
|
evidence: `Winner: ${conflict.winnerTxId}, Losers: ${conflict.loserTxIds.join(", ")}`,
|
|
1491
|
-
rule: "UTXO double-spend detection
|
|
1652
|
+
rule: "UTXO double-spend detection"
|
|
1492
1653
|
});
|
|
1493
1654
|
if (winnerBlockId) {
|
|
1494
|
-
|
|
1495
|
-
order: order++,
|
|
1496
|
-
assertion: `Winner tx is in block ${winnerBlockId} (daaScore: ${winnerDaaScore})`,
|
|
1497
|
-
evidence: `Block ${winnerBlockId} contains tx ${conflict.winnerTxId}`,
|
|
1498
|
-
rule: "Block tx membership"
|
|
1499
|
-
});
|
|
1500
|
-
steps.push({
|
|
1655
|
+
causalChain.push({
|
|
1501
1656
|
order: order++,
|
|
1502
|
-
assertion:
|
|
1503
|
-
evidence: `
|
|
1504
|
-
rule: "Sink-ancestry priority
|
|
1657
|
+
assertion: `Winner in block ${winnerBlockId} (daaScore: ${winnerDaaScore})`,
|
|
1658
|
+
evidence: `In sink path: ${winnerInSinkPath}`,
|
|
1659
|
+
rule: "Sink-ancestry priority"
|
|
1505
1660
|
});
|
|
1506
1661
|
}
|
|
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
1662
|
return {
|
|
1514
|
-
question: `Why
|
|
1515
|
-
|
|
1516
|
-
|
|
1663
|
+
question: `Why winner ${conflict.winnerTxId.slice(0, 8)} on outpoint ${conflict.outpoint}?`,
|
|
1664
|
+
answer: `Winner has ${winnerInSinkPath ? "sink-path priority" : "block ordering priority"}.`,
|
|
1665
|
+
evidence: [
|
|
1666
|
+
{ type: "txId", value: conflict.winnerTxId },
|
|
1667
|
+
...conflict.loserTxIds.map((id) => ({ type: "txId", value: id }))
|
|
1668
|
+
],
|
|
1669
|
+
causalChain,
|
|
1517
1670
|
model: "deterministic-light-model",
|
|
1518
|
-
confidence: "definitive"
|
|
1519
|
-
references: [conflict.winnerTxId, ...conflict.loserTxIds]
|
|
1671
|
+
confidence: "definitive"
|
|
1520
1672
|
};
|
|
1521
1673
|
}
|
|
1522
1674
|
function explainDisplacement(d, _dag) {
|
|
1523
1675
|
return {
|
|
1524
|
-
question: `Why
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
{ order:
|
|
1676
|
+
question: `Why displaced tx ${d.txId.slice(0, 8)}?`,
|
|
1677
|
+
answer: d.reason,
|
|
1678
|
+
evidence: [{ type: "txId", value: d.txId }],
|
|
1679
|
+
causalChain: [
|
|
1680
|
+
{ order: 1, assertion: "Tx in displaced set", evidence: "dag.displacedTxIds includes txId", rule: "DAG reorganization" },
|
|
1681
|
+
{ order: 2, assertion: "Status: displaced", evidence: d.reason }
|
|
1529
1682
|
],
|
|
1530
1683
|
model: "deterministic-light-model",
|
|
1531
|
-
confidence: "definitive"
|
|
1532
|
-
references: [d.txId]
|
|
1684
|
+
confidence: "definitive"
|
|
1533
1685
|
};
|
|
1534
1686
|
}
|
|
1535
1687
|
function explainTxHistory(entry, _dag) {
|
|
1536
1688
|
const status = entry.accepted ? "ACCEPTED" : entry.displaced ? "DISPLACED" : "UNKNOWN";
|
|
1537
1689
|
return {
|
|
1538
|
-
question: `
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
{
|
|
1542
|
-
{
|
|
1543
|
-
|
|
1544
|
-
|
|
1690
|
+
question: `Causal history of tx ${entry.txId.slice(0, 8)}?`,
|
|
1691
|
+
answer: `Status is ${status} in block ${entry.blockId}.`,
|
|
1692
|
+
evidence: [
|
|
1693
|
+
{ type: "txId", value: entry.txId },
|
|
1694
|
+
{ type: "blockId", value: entry.blockId }
|
|
1695
|
+
],
|
|
1696
|
+
causalChain: [
|
|
1697
|
+
{ order: 1, assertion: `In block ${entry.blockId}`, evidence: `daaScore=${entry.daaScore}` },
|
|
1698
|
+
{ order: 2, assertion: entry.inSinkPath ? "In selected sink path" : "Not in sink path", evidence: `selectedPathToSink.includes("${entry.blockId}")` }
|
|
1545
1699
|
],
|
|
1546
1700
|
model: "deterministic-light-model",
|
|
1547
|
-
confidence: "definitive"
|
|
1548
|
-
references: [entry.txId]
|
|
1701
|
+
confidence: "definitive"
|
|
1549
1702
|
};
|
|
1550
1703
|
}
|
|
1551
1704
|
function explainAnomaly(a, _dag) {
|
|
1705
|
+
const evidence = [];
|
|
1706
|
+
if (a.txId) evidence.push({ type: "txId", value: a.txId });
|
|
1707
|
+
if (a.blockId) evidence.push({ type: "blockId", value: a.blockId });
|
|
1552
1708
|
return {
|
|
1553
|
-
question: `Why
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1709
|
+
question: `Why anomaly: ${a.kind}?`,
|
|
1710
|
+
answer: a.description,
|
|
1711
|
+
evidence,
|
|
1712
|
+
causalChain: [
|
|
1713
|
+
{ order: 1, assertion: a.description, evidence: `Anomaly: ${a.kind}`, rule: "DAG invariant check" }
|
|
1557
1714
|
],
|
|
1558
1715
|
model: "deterministic-light-model",
|
|
1559
|
-
confidence: "definitive"
|
|
1560
|
-
references: [a.txId ?? a.blockId ?? ""]
|
|
1716
|
+
confidence: "definitive"
|
|
1561
1717
|
};
|
|
1562
1718
|
}
|
|
1563
1719
|
|
|
1564
1720
|
// src/adapters/events-adapter.ts
|
|
1565
|
-
import fs5 from "fs/promises";
|
|
1566
1721
|
import path5 from "path";
|
|
1567
|
-
import { validateEventEnvelope } from "@hardkas/core";
|
|
1568
1722
|
var EventsQueryAdapter = class {
|
|
1569
1723
|
domain = "events";
|
|
1570
1724
|
rootDir;
|
|
1571
|
-
|
|
1725
|
+
backend;
|
|
1726
|
+
constructor(rootDir, backend) {
|
|
1572
1727
|
this.rootDir = rootDir;
|
|
1728
|
+
this.backend = backend;
|
|
1573
1729
|
}
|
|
1574
1730
|
supportedOps() {
|
|
1575
1731
|
return ["list", "summary"];
|
|
@@ -1588,7 +1744,7 @@ var EventsQueryAdapter = class {
|
|
|
1588
1744
|
async executeList(request) {
|
|
1589
1745
|
const start = Date.now();
|
|
1590
1746
|
const events = await this.loadEvents();
|
|
1591
|
-
const backendUsed =
|
|
1747
|
+
const backendUsed = this.backend.kind();
|
|
1592
1748
|
const eventsPath = path5.join(this.rootDir, ".hardkas", "events.jsonl");
|
|
1593
1749
|
const filtered = [];
|
|
1594
1750
|
const txFilter = request.params["tx"] || request.params["txId"];
|
|
@@ -1619,67 +1775,52 @@ var EventsQueryAdapter = class {
|
|
|
1619
1775
|
annotations: {
|
|
1620
1776
|
executedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1621
1777
|
executionMs: Date.now() - start,
|
|
1622
|
-
filesScanned: 1
|
|
1778
|
+
filesScanned: backendUsed === "sqlite" ? 0 : 1
|
|
1623
1779
|
},
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
}]
|
|
1637
|
-
} : {}
|
|
1780
|
+
why: request.explain ? [{
|
|
1781
|
+
question: "How were events loaded and linked?",
|
|
1782
|
+
answer: `Loaded ${total} events matching filters. Causal links (correlation/causation) available in payload.`,
|
|
1783
|
+
evidence: [],
|
|
1784
|
+
causalChain: [
|
|
1785
|
+
{ order: 1, assertion: `Source: ${backendUsed}`, evidence: "Event stream processed" },
|
|
1786
|
+
{ order: 2, assertion: `Filters: ${effectiveFilters.length} applied`, evidence: effectiveFilters.map((f) => `${f.field} ${f.op} ${f.value}`).join(", ") || "none" },
|
|
1787
|
+
{ order: 3, assertion: "Ordering: timestamp ASC", evidence: "Deterministic stream sorting" }
|
|
1788
|
+
],
|
|
1789
|
+
model: "events-causality",
|
|
1790
|
+
confidence: "definitive"
|
|
1791
|
+
}] : void 0
|
|
1638
1792
|
};
|
|
1639
1793
|
}
|
|
1640
1794
|
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() !== "");
|
|
1795
|
+
const docs = await this.backend.getEvents();
|
|
1649
1796
|
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
|
-
}
|
|
1797
|
+
for (const doc of docs) {
|
|
1798
|
+
events.push({
|
|
1799
|
+
eventId: doc.eventId,
|
|
1800
|
+
kind: doc.kind,
|
|
1801
|
+
domain: doc.domain,
|
|
1802
|
+
timestamp: doc.timestamp || "",
|
|
1803
|
+
workflowId: doc.workflowId,
|
|
1804
|
+
correlationId: doc.correlationId,
|
|
1805
|
+
causationId: doc.causationId || void 0,
|
|
1806
|
+
txId: doc.txId || void 0,
|
|
1807
|
+
artifactId: doc.artifactId || void 0,
|
|
1808
|
+
networkId: doc.networkId,
|
|
1809
|
+
payload: doc.payload
|
|
1810
|
+
});
|
|
1669
1811
|
}
|
|
1670
1812
|
return events;
|
|
1671
1813
|
}
|
|
1672
1814
|
};
|
|
1673
1815
|
|
|
1674
1816
|
// 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
1817
|
var TxQueryAdapter = class {
|
|
1679
1818
|
domain = "tx";
|
|
1680
1819
|
rootDir;
|
|
1681
|
-
|
|
1820
|
+
backend;
|
|
1821
|
+
constructor(rootDir, backend) {
|
|
1682
1822
|
this.rootDir = rootDir;
|
|
1823
|
+
this.backend = backend;
|
|
1683
1824
|
}
|
|
1684
1825
|
supportedOps() {
|
|
1685
1826
|
return ["aggregate"];
|
|
@@ -1716,20 +1857,19 @@ var TxQueryAdapter = class {
|
|
|
1716
1857
|
warnings,
|
|
1717
1858
|
complete
|
|
1718
1859
|
};
|
|
1719
|
-
let
|
|
1860
|
+
let why;
|
|
1720
1861
|
if (request.explain) {
|
|
1721
|
-
|
|
1722
|
-
question: `
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
{ order:
|
|
1727
|
-
{ order:
|
|
1728
|
-
{ order:
|
|
1862
|
+
why = [{
|
|
1863
|
+
question: `Causal aggregation for transaction ${txId}?`,
|
|
1864
|
+
answer: complete ? `Found ${artifacts.length} artifact(s) and ${events.length} event(s). Workflow is consistent.` : `Aggregation incomplete: ${warnings.join(". ")}.`,
|
|
1865
|
+
evidence: [{ type: "txId", value: txId }],
|
|
1866
|
+
causalChain: [
|
|
1867
|
+
{ order: 1, assertion: `Artifacts linked: ${artifacts.length}`, evidence: artifacts.map((a) => a.role).join(", ") },
|
|
1868
|
+
{ order: 2, assertion: `Events linked: ${events.length}`, evidence: "Events found in stream" },
|
|
1869
|
+
{ order: 3, assertion: `Completeness check: ${complete}`, evidence: warnings.join("; ") || "all required roles found" }
|
|
1729
1870
|
],
|
|
1730
|
-
model: "tx-
|
|
1731
|
-
confidence: "definitive"
|
|
1732
|
-
references: [txId]
|
|
1871
|
+
model: "tx-causality",
|
|
1872
|
+
confidence: "definitive"
|
|
1733
1873
|
}];
|
|
1734
1874
|
}
|
|
1735
1875
|
return {
|
|
@@ -1740,7 +1880,7 @@ var TxQueryAdapter = class {
|
|
|
1740
1880
|
truncated: false,
|
|
1741
1881
|
deterministic: true,
|
|
1742
1882
|
queryHash: computeQueryHash([result]),
|
|
1743
|
-
|
|
1883
|
+
why,
|
|
1744
1884
|
annotations: {
|
|
1745
1885
|
executedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1746
1886
|
executionMs: Date.now() - start
|
|
@@ -1748,92 +1888,96 @@ var TxQueryAdapter = class {
|
|
|
1748
1888
|
};
|
|
1749
1889
|
}
|
|
1750
1890
|
async findArtifactsByTxId(txId) {
|
|
1891
|
+
const docs = await this.backend.findArtifacts();
|
|
1751
1892
|
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
|
-
}
|
|
1893
|
+
for (const doc of docs) {
|
|
1894
|
+
const parsed = doc.payload;
|
|
1895
|
+
const matchesTx = parsed.txId === txId || parsed.transaction?.id === txId || parsed.lineage?.artifactId === txId;
|
|
1896
|
+
if (!matchesTx) continue;
|
|
1897
|
+
const schema = String(doc.schema);
|
|
1898
|
+
let role = "unknown";
|
|
1899
|
+
if (schema.includes("txPlan")) role = "plan";
|
|
1900
|
+
else if (schema.includes("signedTx")) role = "signed";
|
|
1901
|
+
else if (schema.includes("txReceipt")) role = "receipt";
|
|
1902
|
+
results.push({
|
|
1903
|
+
filePath: doc.path,
|
|
1904
|
+
schema,
|
|
1905
|
+
contentHash: doc.contentHash,
|
|
1906
|
+
role
|
|
1907
|
+
});
|
|
1773
1908
|
}
|
|
1774
1909
|
return results.sort((a, b) => a.filePath.localeCompare(b.filePath));
|
|
1775
1910
|
}
|
|
1776
1911
|
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() !== "");
|
|
1912
|
+
const docs = await this.backend.getEvents({ txId });
|
|
1785
1913
|
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
|
-
}
|
|
1914
|
+
for (const doc of docs) {
|
|
1915
|
+
results.push({
|
|
1916
|
+
eventId: doc.eventId,
|
|
1917
|
+
kind: doc.kind,
|
|
1918
|
+
timestamp: doc.timestamp || ""
|
|
1919
|
+
});
|
|
1798
1920
|
}
|
|
1799
1921
|
return results;
|
|
1800
1922
|
}
|
|
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
1923
|
};
|
|
1825
1924
|
|
|
1826
1925
|
// src/engine.ts
|
|
1827
|
-
|
|
1926
|
+
import { withLock } from "@hardkas/core";
|
|
1927
|
+
import fs6 from "fs";
|
|
1928
|
+
import path6 from "path";
|
|
1929
|
+
var QueryEngine = class _QueryEngine {
|
|
1828
1930
|
adapters;
|
|
1931
|
+
backend;
|
|
1932
|
+
/**
|
|
1933
|
+
* Primary entry point for creating a QueryEngine with auto-discovery.
|
|
1934
|
+
*/
|
|
1935
|
+
static async create(options) {
|
|
1936
|
+
let backend = options.backend;
|
|
1937
|
+
if (!backend) {
|
|
1938
|
+
const dbPath = path6.join(options.artifactDir, ".hardkas", "store.db");
|
|
1939
|
+
if (fs6.existsSync(dbPath)) {
|
|
1940
|
+
try {
|
|
1941
|
+
const { HardkasStore, SqliteQueryBackend, HardkasIndexer } = await import("@hardkas/query-store");
|
|
1942
|
+
const store = new HardkasStore({ dbPath });
|
|
1943
|
+
if (options.autoSync) {
|
|
1944
|
+
await withLock({
|
|
1945
|
+
rootDir: options.artifactDir,
|
|
1946
|
+
name: "query-store",
|
|
1947
|
+
command: "query-engine-auto-sync",
|
|
1948
|
+
wait: options.waitLock ?? false,
|
|
1949
|
+
timeoutMs: 5e3
|
|
1950
|
+
// Short timeout for auto-sync
|
|
1951
|
+
}, async () => {
|
|
1952
|
+
store.connect({ autoMigrate: true });
|
|
1953
|
+
const indexer = new HardkasIndexer(store.getDatabase());
|
|
1954
|
+
await indexer.sync();
|
|
1955
|
+
});
|
|
1956
|
+
} else {
|
|
1957
|
+
store.connect();
|
|
1958
|
+
}
|
|
1959
|
+
backend = new SqliteQueryBackend(store);
|
|
1960
|
+
} catch (e) {
|
|
1961
|
+
backend = new FilesystemQueryBackend(options.artifactDir);
|
|
1962
|
+
}
|
|
1963
|
+
} else {
|
|
1964
|
+
backend = new FilesystemQueryBackend(options.artifactDir);
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
return new _QueryEngine({
|
|
1968
|
+
artifactDir: options.artifactDir,
|
|
1969
|
+
backend
|
|
1970
|
+
});
|
|
1971
|
+
}
|
|
1829
1972
|
constructor(options) {
|
|
1973
|
+
this.backend = options.backend || new FilesystemQueryBackend(options.artifactDir);
|
|
1830
1974
|
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));
|
|
1975
|
+
this.adapters.set("artifacts", new ArtifactQueryAdapter(options.artifactDir, this.backend));
|
|
1976
|
+
this.adapters.set("lineage", new LineageQueryAdapter(options.artifactDir, this.backend));
|
|
1977
|
+
this.adapters.set("replay", new ReplayQueryAdapter(options.artifactDir, this.backend));
|
|
1978
|
+
this.adapters.set("dag", new DagQueryAdapter(options.artifactDir, this.backend));
|
|
1979
|
+
this.adapters.set("events", new EventsQueryAdapter(options.artifactDir, this.backend));
|
|
1980
|
+
this.adapters.set("tx", new TxQueryAdapter(options.artifactDir, this.backend));
|
|
1837
1981
|
}
|
|
1838
1982
|
/**
|
|
1839
1983
|
* Execute a query request against the appropriate adapter.
|
|
@@ -1848,7 +1992,31 @@ var QueryEngine = class {
|
|
|
1848
1992
|
`Operation "${request.op}" is not supported by the "${request.domain}" adapter. Supported: ${adapter.supportedOps().join(", ")}`
|
|
1849
1993
|
);
|
|
1850
1994
|
}
|
|
1851
|
-
|
|
1995
|
+
const result = await adapter.execute(request);
|
|
1996
|
+
const freshness = await this.backend.getStoreStatus();
|
|
1997
|
+
const backendUsed = this.backend.kind();
|
|
1998
|
+
let explain = void 0;
|
|
1999
|
+
if (request.explain) {
|
|
2000
|
+
explain = {
|
|
2001
|
+
backend: backendUsed,
|
|
2002
|
+
executionPlan: ["Discovery", "Filter", "Sort", "Paginate"],
|
|
2003
|
+
indexesUsed: backendUsed === "sqlite" ? ["PRIMARY", "idx_artifacts_schema"] : [],
|
|
2004
|
+
filtersApplied: request.filters.map((f) => `${f.field} ${f.op} ${f.value}`),
|
|
2005
|
+
rowsRead: result.items.length,
|
|
2006
|
+
scannedFiles: result.annotations.filesScanned || 0,
|
|
2007
|
+
freshness,
|
|
2008
|
+
warnings: freshness === "stale" ? ["Index is STALE. mtime mismatch detected. Run 'hardkas query store rebuild'."] : []
|
|
2009
|
+
};
|
|
2010
|
+
}
|
|
2011
|
+
return {
|
|
2012
|
+
...result,
|
|
2013
|
+
explain,
|
|
2014
|
+
annotations: {
|
|
2015
|
+
...result.annotations,
|
|
2016
|
+
backendUsed,
|
|
2017
|
+
freshness
|
|
2018
|
+
}
|
|
2019
|
+
};
|
|
1852
2020
|
}
|
|
1853
2021
|
/**
|
|
1854
2022
|
* List available domains and their operations.
|
|
@@ -1886,14 +2054,15 @@ export {
|
|
|
1886
2054
|
ReplayQueryAdapter,
|
|
1887
2055
|
TxQueryAdapter,
|
|
1888
2056
|
computeQueryHash,
|
|
2057
|
+
createExplainBlock,
|
|
1889
2058
|
createQueryRequest,
|
|
1890
2059
|
evaluateFilter,
|
|
1891
2060
|
evaluateFilters,
|
|
1892
2061
|
explainIntegrity,
|
|
1893
2062
|
explainOrphan,
|
|
1894
2063
|
explainTransition,
|
|
1895
|
-
|
|
1896
|
-
|
|
2064
|
+
formatExplainBlock,
|
|
2065
|
+
formatWhyBlock,
|
|
1897
2066
|
resolveFieldPath,
|
|
1898
2067
|
serializeQueryResult
|
|
1899
2068
|
};
|