@hardkas/query 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1899 @@
1
+ // src/adapters/artifact-adapter.ts
2
+ import fs from "fs/promises";
3
+ import path from "path";
4
+ import {
5
+ calculateContentHash,
6
+ verifyArtifactIntegrity,
7
+ verifyArtifactSemantics,
8
+ verifyFeeSemantics,
9
+ verifyLineage,
10
+ ARTIFACT_SCHEMAS
11
+ } from "@hardkas/artifacts";
12
+
13
+ // src/filter.ts
14
+ function resolveFieldPath(obj, path7) {
15
+ if (obj === null || obj === void 0) return void 0;
16
+ const segments = path7.split(".");
17
+ let current = obj;
18
+ for (const seg of segments) {
19
+ if (current === null || current === void 0 || typeof current !== "object") {
20
+ return void 0;
21
+ }
22
+ current = current[seg];
23
+ }
24
+ return current;
25
+ }
26
+ function evaluateFilter(item, filter) {
27
+ const value = resolveFieldPath(item, filter.field);
28
+ return applyOp(value, filter.op, filter.value);
29
+ }
30
+ function evaluateFilters(item, filters) {
31
+ for (const f of filters) {
32
+ if (!evaluateFilter(item, f)) return false;
33
+ }
34
+ return true;
35
+ }
36
+ function applyOp(resolved, op, target) {
37
+ if (op === "eq") {
38
+ if (resolved === target) return true;
39
+ if (typeof resolved === "bigint" && typeof target === "string") {
40
+ try {
41
+ return resolved === BigInt(target);
42
+ } catch {
43
+ return false;
44
+ }
45
+ }
46
+ return false;
47
+ }
48
+ if (op === "neq") {
49
+ return !applyOp(resolved, "eq", target);
50
+ }
51
+ if (op === "exists") {
52
+ return resolved !== void 0 && resolved !== null;
53
+ }
54
+ if (op === "gt" || op === "lt" || op === "gte" || op === "lte") {
55
+ const rNum = toNumber(resolved);
56
+ const tNum = toNumber(target);
57
+ if (rNum === null || tNum === null) return false;
58
+ switch (op) {
59
+ case "gt":
60
+ return rNum > tNum;
61
+ case "lt":
62
+ return rNum < tNum;
63
+ case "gte":
64
+ return rNum >= tNum;
65
+ case "lte":
66
+ return rNum <= tNum;
67
+ }
68
+ }
69
+ if (op === "in") {
70
+ if (!Array.isArray(target)) return false;
71
+ return target.some((t) => applyOp(resolved, "eq", t));
72
+ }
73
+ if (op === "contains") {
74
+ if (typeof resolved !== "string") return false;
75
+ return resolved.includes(String(target));
76
+ }
77
+ return false;
78
+ }
79
+ function toNumber(v) {
80
+ if (typeof v === "number" && !Number.isNaN(v)) return v;
81
+ if (typeof v === "bigint") return v;
82
+ if (typeof v === "string") {
83
+ try {
84
+ if (v.includes(".")) return parseFloat(v);
85
+ return BigInt(v);
86
+ } catch {
87
+ return null;
88
+ }
89
+ }
90
+ return null;
91
+ }
92
+
93
+ // src/serialize.ts
94
+ import { createHash } from "crypto";
95
+ import { canonicalStringify } from "@hardkas/artifacts";
96
+ function computeQueryHash(items) {
97
+ const canonical = canonicalStringify(items);
98
+ return createHash("sha256").update(canonical).digest("hex");
99
+ }
100
+ function serializeQueryResult(result) {
101
+ const output = {
102
+ annotations: result.annotations,
103
+ deterministic: result.deterministic,
104
+ domain: result.domain,
105
+ items: result.items,
106
+ op: result.op,
107
+ queryHash: result.queryHash,
108
+ total: result.total,
109
+ truncated: result.truncated
110
+ };
111
+ if (result.explain) {
112
+ output["explain"] = result.explain;
113
+ }
114
+ return JSON.stringify(output, bigIntReplacer, 2);
115
+ }
116
+ function bigIntReplacer(_key, value) {
117
+ return typeof value === "bigint" ? value.toString() : value;
118
+ }
119
+
120
+ // src/explain.ts
121
+ var VALID_TRANSITIONS = {
122
+ "hardkas.snapshot": ["hardkas.txPlan"],
123
+ "hardkas.txPlan": ["hardkas.signedTx"],
124
+ "hardkas.signedTx": ["hardkas.txReceipt"]
125
+ };
126
+ function explainIntegrity(artifact, integrity) {
127
+ const steps = [];
128
+ let order = 1;
129
+ steps.push({
130
+ order: order++,
131
+ assertion: integrity.schemaValid ? `Schema "${artifact.schema}" is a recognized HardKAS artifact schema` : `Schema "${artifact.schema}" is not recognized`,
132
+ evidence: `artifact.schema = "${artifact.schema}"`,
133
+ rule: "ARTIFACT_SCHEMAS constant (artifacts/constants.ts)"
134
+ });
135
+ if (artifact.contentHash) {
136
+ steps.push({
137
+ order: order++,
138
+ assertion: integrity.hashMatch ? "Content hash matches recomputed hash" : "Content hash does NOT match recomputed hash \u2014 possible corruption",
139
+ evidence: `artifact.contentHash = "${artifact.contentHash}"`,
140
+ rule: "canonicalStringify + SHA-256 (artifacts/canonical.ts)"
141
+ });
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
+ }
150
+ if (integrity.errors.length > 0) {
151
+ for (const err of integrity.errors) {
152
+ steps.push({
153
+ order: order++,
154
+ assertion: `Integrity issue: ${err}`,
155
+ evidence: err,
156
+ rule: "verifyArtifactIntegrity (artifacts/verify.ts)"
157
+ });
158
+ }
159
+ }
160
+ return {
161
+ question: `Why is artifact "${artifact.schema}" at ${artifact.filePath} ${integrity.ok ? "valid" : "invalid"}?`,
162
+ conclusion: integrity.ok ? "All integrity checks passed. Schema is valid and content hash matches." : `Integrity check failed: ${integrity.errors.join("; ")}`,
163
+ steps,
164
+ model: "artifact-verification",
165
+ confidence: "definitive",
166
+ references: artifact.contentHash ? [artifact.contentHash] : []
167
+ };
168
+ }
169
+ function explainTransition(transition) {
170
+ const steps = [];
171
+ let order = 1;
172
+ const allowed = VALID_TRANSITIONS[transition.from.schema] ?? [];
173
+ steps.push({
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({
180
+ order: order++,
181
+ assertion: transition.valid ? `Transition "${transition.from.schema}" \u2192 "${transition.to.schema}" is VALID` : `Transition "${transition.from.schema}" \u2192 "${transition.to.schema}" is NOT in the allowed set`,
182
+ evidence: `target schema = "${transition.to.schema}", allowed = [${allowed.map((s) => `"${s}"`).join(", ")}]`,
183
+ rule: "Lineage transition table (artifacts/lineage.ts)"
184
+ });
185
+ const networkMatch = transition.from.networkId === transition.to.networkId;
186
+ steps.push({
187
+ order: order++,
188
+ assertion: networkMatch ? `Network is consistent: both "${transition.from.networkId}"` : `NETWORK CONTAMINATION: "${transition.from.networkId}" \u2192 "${transition.to.networkId}"`,
189
+ evidence: `parent.networkId = "${transition.from.networkId}", child.networkId = "${transition.to.networkId}"`,
190
+ rule: "NETWORK_CONTAMINATION check (artifacts/lineage.ts)"
191
+ });
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
+ return {
208
+ question: `Why is the transition "${transition.from.schema}" \u2192 "${transition.to.schema}" ${allValid ? "valid" : "invalid"}?`,
209
+ conclusion: allValid ? `Valid transition. Rule: ${transition.from.schema} \u2192 ${transition.to.schema}. Network, mode, and lineage ID are all consistent.` : `Invalid transition. ${!transition.valid ? "Schema transition not allowed. " : ""}${!networkMatch ? "Network mismatch. " : ""}${!modeMatch ? "Mode mismatch. " : ""}${!lineageMatch ? "Lineage ID mismatch." : ""}`,
210
+ steps,
211
+ model: "lineage-rules",
212
+ confidence: "definitive",
213
+ references: [transition.from.contentHash, transition.to.contentHash]
214
+ };
215
+ }
216
+ function explainOrphan(node, missingParentId) {
217
+ return {
218
+ question: `Why is artifact "${node.schema}" (${node.contentHash.slice(0, 12)}...) an orphan?`,
219
+ conclusion: `Parent artifact with ID "${missingParentId.slice(0, 12)}..." is not found in the artifact store. This artifact is disconnected from its workflow context.`,
220
+ steps: [
221
+ {
222
+ order: 1,
223
+ assertion: `Artifact declares parentArtifactId = "${missingParentId}"`,
224
+ evidence: `artifact.lineage.parentArtifactId = "${missingParentId}"`,
225
+ rule: "Lineage parent resolution"
226
+ },
227
+ {
228
+ order: 2,
229
+ assertion: "No artifact in the store has a matching lineage.artifactId",
230
+ evidence: "Full store scan found 0 artifacts with this artifactId",
231
+ rule: "Lineage graph construction (query/lineage-adapter.ts)"
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"
238
+ }
239
+ ],
240
+ model: "lineage-rules",
241
+ confidence: "definitive",
242
+ references: [node.contentHash, missingParentId]
243
+ };
244
+ }
245
+ function formatExplainBrief(chain) {
246
+ return `${chain.conclusion} [model: ${chain.model}, confidence: ${chain.confidence}]`;
247
+ }
248
+ function formatExplainFull(chain) {
249
+ const lines = [];
250
+ lines.push(`Q: ${chain.question}`);
251
+ lines.push("");
252
+ for (const step of chain.steps) {
253
+ lines.push(` ${step.order}. ${step.assertion}`);
254
+ lines.push(` Evidence: ${step.evidence}`);
255
+ if (step.rule) {
256
+ lines.push(` Rule: ${step.rule}`);
257
+ }
258
+ }
259
+ lines.push("");
260
+ lines.push(`Conclusion: ${chain.conclusion}`);
261
+ lines.push(`Model: ${chain.model} | Confidence: ${chain.confidence}`);
262
+ if (chain.references.length > 0) {
263
+ lines.push(`References: ${chain.references.map((r) => r.slice(0, 16) + "...").join(", ")}`);
264
+ }
265
+ return lines.join("\n");
266
+ }
267
+
268
+ // src/adapters/artifact-adapter.ts
269
+ var KNOWN_SCHEMAS = new Set(Object.values(ARTIFACT_SCHEMAS));
270
+ var ArtifactQueryAdapter = class {
271
+ domain = "artifacts";
272
+ rootDir;
273
+ constructor(rootDir) {
274
+ this.rootDir = rootDir;
275
+ }
276
+ supportedOps() {
277
+ return ["list", "inspect", "diff", "verify"];
278
+ }
279
+ supportedFilters() {
280
+ return ["schema", "version", "networkId", "mode", "from.address", "to.address", "amountSompi", "status", "contentHash", "createdAt"];
281
+ }
282
+ async execute(request) {
283
+ switch (request.op) {
284
+ case "list":
285
+ return this.executeList(request);
286
+ case "inspect":
287
+ return this.executeInspect(request);
288
+ case "diff":
289
+ return this.executeDiff(request);
290
+ case "verify":
291
+ return this.executeVerify(request);
292
+ default:
293
+ throw new Error(`Unknown artifact op: ${request.op}`);
294
+ }
295
+ }
296
+ // -------------------------------------------------------------------------
297
+ // List — scan, filter, sort, paginate
298
+ // -------------------------------------------------------------------------
299
+ async executeList(request) {
300
+ const start = Date.now();
301
+ const files = await this.scanArtifactFiles();
302
+ const items = [];
303
+ for (const filePath of files) {
304
+ const raw = await this.readJsonSafe(filePath);
305
+ if (!raw || !raw.schema) continue;
306
+ const item = toArtifactQueryItem(raw, filePath);
307
+ if (evaluateFilters(item, request.filters)) {
308
+ items.push(item);
309
+ }
310
+ }
311
+ const sorted = this.sortItems(items, request.sort);
312
+ const total = sorted.length;
313
+ const paged = sorted.slice(request.offset, request.offset + request.limit);
314
+ let explain;
315
+ if (request.explain) {
316
+ explain = paged.map((item) => explainIntegrity(item, {
317
+ ok: true,
318
+ hashMatch: true,
319
+ schemaValid: KNOWN_SCHEMAS.has(item.schema),
320
+ errors: []
321
+ }));
322
+ }
323
+ return {
324
+ domain: "artifacts",
325
+ op: "list",
326
+ items: paged,
327
+ total,
328
+ truncated: total > request.offset + request.limit,
329
+ deterministic: true,
330
+ queryHash: computeQueryHash(paged),
331
+ explain,
332
+ annotations: {
333
+ executedAt: (/* @__PURE__ */ new Date()).toISOString(),
334
+ executionMs: Date.now() - start,
335
+ filesScanned: files.length
336
+ }
337
+ };
338
+ }
339
+ // -------------------------------------------------------------------------
340
+ // Inspect — deep structural analysis
341
+ // -------------------------------------------------------------------------
342
+ async executeInspect(request) {
343
+ const start = Date.now();
344
+ const target = request.params["target"];
345
+ if (!target) throw new Error("inspect requires params.target (content hash or file path)");
346
+ const filePath = await this.resolveTarget(target);
347
+ const raw = await this.readJsonSafe(filePath);
348
+ if (!raw) throw new Error(`Cannot read artifact at: ${filePath}`);
349
+ const item = toArtifactQueryItem(raw, filePath);
350
+ const integrityResult = await verifyArtifactIntegrity(raw);
351
+ const semanticResult = verifyArtifactSemantics(raw, { strict: true });
352
+ const hashMatch = raw.contentHash ? calculateContentHash(raw) === raw.contentHash : true;
353
+ let economics;
354
+ const artifactType = item.schema.split(".")[1];
355
+ if (artifactType === "txPlan" || artifactType === "signedTx" || artifactType === "txReceipt") {
356
+ const feeAudit = verifyFeeSemantics(raw);
357
+ economics = {
358
+ ok: feeAudit.ok,
359
+ massReported: feeAudit.actualMass.toString(),
360
+ massRecomputed: feeAudit.expectedMass.toString(),
361
+ feeReported: feeAudit.actualFeeSompi.toString(),
362
+ feeRecomputed: feeAudit.expectedFeeSompi.toString(),
363
+ feeRate: feeAudit.feeRateSompiPerMass.toString()
364
+ };
365
+ }
366
+ const ageHours = item.createdAt ? (Date.now() - new Date(item.createdAt).getTime()) / (1e3 * 60 * 60) : 0;
367
+ const staleness = {
368
+ ageHours: Math.round(ageHours * 100) / 100,
369
+ stale: ageHours > 24,
370
+ classification: classifyStaleness(ageHours)
371
+ };
372
+ const lineageResult = verifyLineage(raw);
373
+ const lineageStatus = !raw.lineage ? "missing" : lineageResult.ok ? "valid" : "orphan";
374
+ const inspectResult = {
375
+ item,
376
+ integrity: {
377
+ ok: integrityResult.ok && hashMatch,
378
+ hashMatch,
379
+ schemaValid: KNOWN_SCHEMAS.has(item.schema),
380
+ errors: [...integrityResult.issues.map((i) => i.message), ...semanticResult.issues.map((i) => i.message)]
381
+ },
382
+ economics,
383
+ staleness,
384
+ lineageStatus
385
+ };
386
+ let explain;
387
+ if (request.explain) {
388
+ explain = [explainIntegrity(item, inspectResult.integrity)];
389
+ }
390
+ return {
391
+ domain: "artifacts",
392
+ op: "inspect",
393
+ items: [inspectResult],
394
+ total: 1,
395
+ truncated: false,
396
+ deterministic: true,
397
+ queryHash: computeQueryHash([inspectResult]),
398
+ explain,
399
+ annotations: {
400
+ executedAt: (/* @__PURE__ */ new Date()).toISOString(),
401
+ executionMs: Date.now() - start,
402
+ filesScanned: 1
403
+ }
404
+ };
405
+ }
406
+ // -------------------------------------------------------------------------
407
+ // Diff — semantic field-by-field comparison
408
+ // -------------------------------------------------------------------------
409
+ async executeDiff(request) {
410
+ const start = Date.now();
411
+ const leftPath = request.params["left"];
412
+ const rightPath = request.params["right"];
413
+ if (!leftPath || !rightPath) throw new Error("diff requires params.left and params.right");
414
+ const leftRaw = await this.readJsonSafe(leftPath);
415
+ const rightRaw = await this.readJsonSafe(rightPath);
416
+ if (!leftRaw) throw new Error(`Cannot read left artifact: ${leftPath}`);
417
+ if (!rightRaw) throw new Error(`Cannot read right artifact: ${rightPath}`);
418
+ const entries = [];
419
+ const allKeys = /* @__PURE__ */ new Set([...Object.keys(leftRaw), ...Object.keys(rightRaw)]);
420
+ const excluded = /* @__PURE__ */ new Set(["contentHash", "artifactId", "lineage"]);
421
+ for (const key of [...allKeys].sort()) {
422
+ if (excluded.has(key)) continue;
423
+ const leftVal = leftRaw[key];
424
+ const rightVal = rightRaw[key];
425
+ const leftStr = leftVal !== void 0 ? JSON.stringify(leftVal) : void 0;
426
+ const rightStr = rightVal !== void 0 ? JSON.stringify(rightVal) : void 0;
427
+ if (leftStr === rightStr) continue;
428
+ let kind;
429
+ if (leftStr === void 0) kind = "added";
430
+ else if (rightStr === void 0) kind = "removed";
431
+ else if (typeof leftVal !== typeof rightVal) kind = "type-change";
432
+ else kind = "value-change";
433
+ entries.push({ field: key, left: leftStr, right: rightStr, kind });
434
+ }
435
+ const result = {
436
+ leftPath,
437
+ rightPath,
438
+ leftSchema: leftRaw.schema || "unknown",
439
+ rightSchema: rightRaw.schema || "unknown",
440
+ identical: entries.length === 0,
441
+ entries
442
+ };
443
+ return {
444
+ domain: "artifacts",
445
+ op: "diff",
446
+ items: [result],
447
+ total: 1,
448
+ truncated: false,
449
+ deterministic: true,
450
+ queryHash: computeQueryHash([result]),
451
+ annotations: {
452
+ executedAt: (/* @__PURE__ */ new Date()).toISOString(),
453
+ executionMs: Date.now() - start,
454
+ filesScanned: 2
455
+ }
456
+ };
457
+ }
458
+ // -------------------------------------------------------------------------
459
+ // Verify — deep verification with optional explain
460
+ // -------------------------------------------------------------------------
461
+ async executeVerify(request) {
462
+ return this.executeInspect(request);
463
+ }
464
+ // -------------------------------------------------------------------------
465
+ // Helpers
466
+ // -------------------------------------------------------------------------
467
+ async scanArtifactFiles() {
468
+ const files = [];
469
+ await this.walkDir(this.rootDir, files);
470
+ return files.sort();
471
+ }
472
+ async walkDir(dir, out) {
473
+ let entries;
474
+ try {
475
+ entries = await fs.readdir(dir, { withFileTypes: true });
476
+ } catch {
477
+ return;
478
+ }
479
+ for (const entry of entries) {
480
+ const full = path.join(dir, entry.name);
481
+ if (entry.isDirectory()) {
482
+ if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "keystores") continue;
483
+ await this.walkDir(full, out);
484
+ } else if (entry.name.endsWith(".json") && !entry.name.endsWith(".enc.json")) {
485
+ out.push(full);
486
+ }
487
+ }
488
+ }
489
+ async readJsonSafe(filePath) {
490
+ try {
491
+ const content = await fs.readFile(filePath, "utf-8");
492
+ return JSON.parse(content);
493
+ } catch {
494
+ return null;
495
+ }
496
+ }
497
+ async resolveTarget(target) {
498
+ if (target.includes("/") || target.includes("\\") || target.endsWith(".json")) {
499
+ return path.resolve(target);
500
+ }
501
+ const files = await this.scanArtifactFiles();
502
+ for (const f of files) {
503
+ const raw = await this.readJsonSafe(f);
504
+ if (raw?.contentHash === target) return f;
505
+ if (raw?.lineage?.artifactId === target) return f;
506
+ }
507
+ throw new Error(`No artifact found with hash or ID: ${target}`);
508
+ }
509
+ sortItems(items, sort) {
510
+ const sorted = [...items];
511
+ if (sort) {
512
+ sorted.sort((a, b) => {
513
+ const aVal = String(a[sort.field] ?? "");
514
+ const bVal = String(b[sort.field] ?? "");
515
+ const cmp = aVal.localeCompare(bVal);
516
+ return sort.direction === "desc" ? -cmp : cmp;
517
+ });
518
+ } else {
519
+ sorted.sort((a, b) => {
520
+ const cmp = b.createdAt.localeCompare(a.createdAt);
521
+ return cmp !== 0 ? cmp : a.schema.localeCompare(b.schema);
522
+ });
523
+ }
524
+ return sorted;
525
+ }
526
+ };
527
+ function toArtifactQueryItem(raw, filePath) {
528
+ return {
529
+ filePath,
530
+ schema: raw.schema || "unknown",
531
+ version: raw.version || "unknown",
532
+ networkId: raw.networkId || "unknown",
533
+ mode: raw.mode || "unknown",
534
+ createdAt: raw.createdAt || "",
535
+ contentHash: raw.contentHash,
536
+ from: raw.from,
537
+ to: raw.to,
538
+ amountSompi: raw.amountSompi,
539
+ status: raw.status,
540
+ lineage: raw.lineage ? {
541
+ artifactId: raw.lineage.artifactId,
542
+ parentArtifactId: raw.lineage.parentArtifactId,
543
+ rootArtifactId: raw.lineage.rootArtifactId,
544
+ lineageId: raw.lineage.lineageId,
545
+ sequence: raw.lineage.sequence
546
+ } : void 0
547
+ };
548
+ }
549
+ function classifyStaleness(hours) {
550
+ if (hours < 1) return "fresh";
551
+ if (hours < 24) return "aging";
552
+ if (hours < 168) return "stale";
553
+ return "expired";
554
+ }
555
+
556
+ // src/adapters/lineage-adapter.ts
557
+ import fs2 from "fs/promises";
558
+ import path2 from "path";
559
+ var VALID_TRANSITIONS2 = {
560
+ "hardkas.snapshot": ["hardkas.txPlan"],
561
+ "hardkas.txPlan": ["hardkas.signedTx"],
562
+ "hardkas.signedTx": ["hardkas.txReceipt"]
563
+ };
564
+ var LineageQueryAdapter = class {
565
+ domain = "lineage";
566
+ rootDir;
567
+ constructor(rootDir) {
568
+ this.rootDir = rootDir;
569
+ }
570
+ supportedOps() {
571
+ return ["chain", "transitions", "orphans"];
572
+ }
573
+ supportedFilters() {
574
+ return ["schema", "networkId", "mode", "rootArtifactId", "lineageId"];
575
+ }
576
+ async execute(request) {
577
+ switch (request.op) {
578
+ case "chain":
579
+ return this.executeChain(request);
580
+ case "transitions":
581
+ return this.executeTransitions(request);
582
+ case "orphans":
583
+ return this.executeOrphans(request);
584
+ default:
585
+ throw new Error(`Unknown lineage op: ${request.op}`);
586
+ }
587
+ }
588
+ // -------------------------------------------------------------------------
589
+ // Chain — walk ancestors or descendants from an anchor
590
+ // -------------------------------------------------------------------------
591
+ async executeChain(request) {
592
+ const start = Date.now();
593
+ const anchor = request.params["anchor"];
594
+ if (!anchor) throw new Error("chain requires params.anchor (contentHash or artifactId)");
595
+ const direction = request.params["direction"] ?? "ancestors";
596
+ const graph = await this.buildGraph();
597
+ const anchorNode = graph.byArtifactId.get(anchor) ?? graph.byContentHash.get(anchor);
598
+ if (!anchorNode) throw new Error(`Anchor not found: ${anchor}`);
599
+ const nodes = [];
600
+ const transitions = [];
601
+ if (direction === "ancestors") {
602
+ this.walkAncestors(anchorNode, graph, nodes, transitions);
603
+ } else {
604
+ this.walkDescendants(anchorNode, graph, nodes, transitions);
605
+ }
606
+ const complete = direction === "ancestors" ? nodes.length > 0 && !nodes[0].parentArtifactId : true;
607
+ const result = {
608
+ anchor,
609
+ direction,
610
+ nodes,
611
+ transitions,
612
+ complete
613
+ };
614
+ let explain;
615
+ if (request.explain) {
616
+ explain = transitions.map((t) => explainTransition(t));
617
+ }
618
+ return {
619
+ domain: "lineage",
620
+ op: "chain",
621
+ items: [result],
622
+ total: 1,
623
+ truncated: false,
624
+ deterministic: true,
625
+ queryHash: computeQueryHash([result]),
626
+ explain,
627
+ annotations: {
628
+ executedAt: (/* @__PURE__ */ new Date()).toISOString(),
629
+ executionMs: Date.now() - start,
630
+ filesScanned: graph.totalFiles
631
+ }
632
+ };
633
+ }
634
+ // -------------------------------------------------------------------------
635
+ // Transitions — list all edges in a lineage tree
636
+ // -------------------------------------------------------------------------
637
+ async executeTransitions(request) {
638
+ const start = Date.now();
639
+ const graph = await this.buildGraph();
640
+ const rootId = request.params["root"];
641
+ const transitions = [];
642
+ for (const node of graph.nodes) {
643
+ if (!node.parentArtifactId) continue;
644
+ if (rootId && node.rootArtifactId !== rootId) continue;
645
+ const parent = graph.byArtifactId.get(node.parentArtifactId);
646
+ if (!parent) continue;
647
+ const allowed = VALID_TRANSITIONS2[parent.schema] ?? [];
648
+ const valid = allowed.includes(node.schema);
649
+ transitions.push({
650
+ from: parent,
651
+ to: node,
652
+ valid,
653
+ rule: valid ? `${parent.schema} \u2192 ${node.schema} (valid)` : `${parent.schema} \u2192 ${node.schema} (INVALID \u2014 allowed: ${allowed.join(", ") || "none"})`
654
+ });
655
+ }
656
+ transitions.sort((a, b) => {
657
+ const seqA = a.from.sequence ?? 0;
658
+ const seqB = b.from.sequence ?? 0;
659
+ if (seqA !== seqB) return seqA - seqB;
660
+ return a.from.contentHash.localeCompare(b.from.contentHash);
661
+ });
662
+ const paged = transitions.slice(request.offset, request.offset + request.limit);
663
+ let explain;
664
+ if (request.explain) {
665
+ explain = paged.map((t) => explainTransition(t));
666
+ }
667
+ return {
668
+ domain: "lineage",
669
+ op: "transitions",
670
+ items: paged,
671
+ total: transitions.length,
672
+ truncated: transitions.length > request.offset + request.limit,
673
+ deterministic: true,
674
+ queryHash: computeQueryHash(paged),
675
+ explain,
676
+ annotations: {
677
+ executedAt: (/* @__PURE__ */ new Date()).toISOString(),
678
+ executionMs: Date.now() - start,
679
+ filesScanned: graph.totalFiles
680
+ }
681
+ };
682
+ }
683
+ // -------------------------------------------------------------------------
684
+ // Orphans — artifacts with broken parent references
685
+ // -------------------------------------------------------------------------
686
+ async executeOrphans(request) {
687
+ const start = Date.now();
688
+ const graph = await this.buildGraph();
689
+ const orphans = [];
690
+ for (const node of graph.nodes) {
691
+ if (!node.parentArtifactId) continue;
692
+ const parent = graph.byArtifactId.get(node.parentArtifactId);
693
+ if (!parent) {
694
+ orphans.push({
695
+ node,
696
+ missingParentId: node.parentArtifactId,
697
+ reason: `Parent artifactId "${node.parentArtifactId}" not found in artifact store`
698
+ });
699
+ }
700
+ }
701
+ orphans.sort((a, b) => a.node.contentHash.localeCompare(b.node.contentHash));
702
+ const paged = orphans.slice(request.offset, request.offset + request.limit);
703
+ let explain;
704
+ if (request.explain) {
705
+ explain = paged.map((o) => explainOrphan(o.node, o.missingParentId));
706
+ }
707
+ return {
708
+ domain: "lineage",
709
+ op: "orphans",
710
+ items: paged,
711
+ total: orphans.length,
712
+ truncated: orphans.length > request.offset + request.limit,
713
+ deterministic: true,
714
+ queryHash: computeQueryHash(paged),
715
+ explain,
716
+ annotations: {
717
+ executedAt: (/* @__PURE__ */ new Date()).toISOString(),
718
+ executionMs: Date.now() - start,
719
+ filesScanned: graph.totalFiles
720
+ }
721
+ };
722
+ }
723
+ // -------------------------------------------------------------------------
724
+ // Graph Construction
725
+ // -------------------------------------------------------------------------
726
+ async buildGraph() {
727
+ const nodes = [];
728
+ const byArtifactId = /* @__PURE__ */ new Map();
729
+ const byContentHash = /* @__PURE__ */ new Map();
730
+ const children = /* @__PURE__ */ new Map();
731
+ const files = await this.scanJsonFiles();
732
+ for (const filePath of files) {
733
+ const raw = await this.readJsonSafe(filePath);
734
+ if (!raw?.schema || !raw.lineage) continue;
735
+ const node = {
736
+ contentHash: raw.contentHash || "",
737
+ schema: raw.schema,
738
+ artifactId: raw.lineage.artifactId || "",
739
+ parentArtifactId: raw.lineage.parentArtifactId,
740
+ rootArtifactId: raw.lineage.rootArtifactId || "",
741
+ lineageId: raw.lineage.lineageId || "",
742
+ sequence: raw.lineage.sequence,
743
+ filePath,
744
+ networkId: raw.networkId || "unknown",
745
+ mode: raw.mode || "unknown",
746
+ createdAt: raw.createdAt || ""
747
+ };
748
+ nodes.push(node);
749
+ if (node.artifactId) byArtifactId.set(node.artifactId, node);
750
+ if (node.contentHash) byContentHash.set(node.contentHash, node);
751
+ if (node.parentArtifactId) {
752
+ const existing = children.get(node.parentArtifactId) ?? [];
753
+ existing.push(node);
754
+ children.set(node.parentArtifactId, existing);
755
+ }
756
+ }
757
+ return { nodes, byArtifactId, byContentHash, children, totalFiles: files.length };
758
+ }
759
+ // -------------------------------------------------------------------------
760
+ // Graph Traversal
761
+ // -------------------------------------------------------------------------
762
+ walkAncestors(node, graph, nodes, transitions) {
763
+ const chain = [node];
764
+ let current = node;
765
+ const visited = /* @__PURE__ */ new Set();
766
+ while (current.parentArtifactId && !visited.has(current.parentArtifactId)) {
767
+ visited.add(current.parentArtifactId);
768
+ const parent = graph.byArtifactId.get(current.parentArtifactId);
769
+ if (!parent) break;
770
+ chain.unshift(parent);
771
+ const allowed = VALID_TRANSITIONS2[parent.schema] ?? [];
772
+ transitions.unshift({
773
+ from: parent,
774
+ to: current,
775
+ valid: allowed.includes(current.schema),
776
+ rule: allowed.includes(current.schema) ? `${parent.schema} \u2192 ${current.schema} (valid)` : `${parent.schema} \u2192 ${current.schema} (INVALID)`
777
+ });
778
+ current = parent;
779
+ }
780
+ nodes.push(...chain);
781
+ }
782
+ walkDescendants(node, graph, nodes, transitions) {
783
+ const queue = [node];
784
+ const visited = /* @__PURE__ */ new Set();
785
+ nodes.push(node);
786
+ while (queue.length > 0) {
787
+ const current = queue.shift();
788
+ if (visited.has(current.artifactId)) continue;
789
+ visited.add(current.artifactId);
790
+ const kids = graph.children.get(current.artifactId) ?? [];
791
+ for (const child of kids) {
792
+ nodes.push(child);
793
+ queue.push(child);
794
+ const allowed = VALID_TRANSITIONS2[current.schema] ?? [];
795
+ transitions.push({
796
+ from: current,
797
+ to: child,
798
+ valid: allowed.includes(child.schema),
799
+ rule: allowed.includes(child.schema) ? `${current.schema} \u2192 ${child.schema} (valid)` : `${current.schema} \u2192 ${child.schema} (INVALID)`
800
+ });
801
+ }
802
+ }
803
+ }
804
+ // -------------------------------------------------------------------------
805
+ // Filesystem
806
+ // -------------------------------------------------------------------------
807
+ async scanJsonFiles() {
808
+ const files = [];
809
+ await this.walkDir(this.rootDir, files);
810
+ return files.sort();
811
+ }
812
+ async walkDir(dir, out) {
813
+ let entries;
814
+ try {
815
+ entries = await fs2.readdir(dir, { withFileTypes: true });
816
+ } catch {
817
+ return;
818
+ }
819
+ for (const entry of entries) {
820
+ const full = path2.join(dir, entry.name);
821
+ if (entry.isDirectory()) {
822
+ if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "keystores") continue;
823
+ await this.walkDir(full, out);
824
+ } else if (entry.name.endsWith(".json") && !entry.name.endsWith(".enc.json")) {
825
+ out.push(full);
826
+ }
827
+ }
828
+ }
829
+ async readJsonSafe(filePath) {
830
+ try {
831
+ const content = await fs2.readFile(filePath, "utf-8");
832
+ return JSON.parse(content);
833
+ } catch {
834
+ return null;
835
+ }
836
+ }
837
+ };
838
+
839
+ // src/adapters/replay-adapter.ts
840
+ import fs3 from "fs/promises";
841
+ import path3 from "path";
842
+ import { calculateContentHash as calculateContentHash2 } from "@hardkas/artifacts";
843
+ var ReplayQueryAdapter = class {
844
+ domain = "replay";
845
+ rootDir;
846
+ constructor(rootDir) {
847
+ this.rootDir = rootDir;
848
+ }
849
+ supportedOps() {
850
+ return ["list", "summary", "divergences", "invariants"];
851
+ }
852
+ supportedFilters() {
853
+ return ["txId", "status", "networkId", "mode", "daaScore", "from.address", "to.address"];
854
+ }
855
+ async execute(request) {
856
+ switch (request.op) {
857
+ case "list":
858
+ return this.executeList(request);
859
+ case "summary":
860
+ return this.executeSummary(request);
861
+ case "divergences":
862
+ return this.executeDivergences(request);
863
+ case "invariants":
864
+ return this.executeInvariants(request);
865
+ default:
866
+ throw new Error(`Unknown replay op: ${request.op}`);
867
+ }
868
+ }
869
+ // -------------------------------------------------------------------------
870
+ // List — enumerate all stored receipts
871
+ // -------------------------------------------------------------------------
872
+ async executeList(request) {
873
+ const start = Date.now();
874
+ const receipts = await this.loadAllReceipts();
875
+ const traces = await this.loadAllTraces();
876
+ const traceMap = new Map(traces.map((t) => [t.txId, t]));
877
+ const items = [];
878
+ for (const r of receipts) {
879
+ const trace = traceMap.get(r.txId);
880
+ const item = toSummary(r, trace);
881
+ if (evaluateFilters(item, request.filters)) {
882
+ items.push(item);
883
+ }
884
+ }
885
+ items.sort((a, b) => {
886
+ const cmp = b.daaScore.localeCompare(a.daaScore);
887
+ return cmp !== 0 ? cmp : a.txId.localeCompare(b.txId);
888
+ });
889
+ const total = items.length;
890
+ const paged = items.slice(request.offset, request.offset + request.limit);
891
+ return {
892
+ domain: "replay",
893
+ op: "list",
894
+ items: paged,
895
+ total,
896
+ truncated: total > request.offset + request.limit,
897
+ deterministic: true,
898
+ queryHash: computeQueryHash(paged),
899
+ annotations: {
900
+ executedAt: (/* @__PURE__ */ new Date()).toISOString(),
901
+ executionMs: Date.now() - start,
902
+ filesScanned: receipts.length + traces.length
903
+ }
904
+ };
905
+ }
906
+ // -------------------------------------------------------------------------
907
+ // Summary — detailed summary for a specific txId
908
+ // -------------------------------------------------------------------------
909
+ async executeSummary(request) {
910
+ const start = Date.now();
911
+ const txId = request.params["txId"];
912
+ if (!txId) throw new Error("summary requires params.txId");
913
+ const receipt = await this.loadReceipt(txId);
914
+ if (!receipt) throw new Error(`Receipt not found for txId: ${txId}`);
915
+ const trace = await this.loadTrace(txId);
916
+ const item = toSummary(receipt, trace);
917
+ return {
918
+ domain: "replay",
919
+ op: "summary",
920
+ items: [item],
921
+ total: 1,
922
+ truncated: false,
923
+ deterministic: true,
924
+ queryHash: computeQueryHash([item]),
925
+ annotations: {
926
+ executedAt: (/* @__PURE__ */ new Date()).toISOString(),
927
+ executionMs: Date.now() - start,
928
+ filesScanned: trace ? 2 : 1
929
+ }
930
+ };
931
+ }
932
+ // -------------------------------------------------------------------------
933
+ // Divergences — detect receipts that show signs of non-determinism
934
+ // -------------------------------------------------------------------------
935
+ async executeDivergences(request) {
936
+ const start = Date.now();
937
+ const receipts = await this.loadAllReceipts();
938
+ const divergences = [];
939
+ for (const receipt of receipts) {
940
+ if (receipt.contentHash) {
941
+ const recomputed = computeContentHashSafe(receipt);
942
+ if (recomputed !== receipt.contentHash) {
943
+ divergences.push({
944
+ txId: receipt.txId,
945
+ kind: "state-hash-mismatch",
946
+ field: "contentHash",
947
+ expected: receipt.contentHash,
948
+ actual: recomputed
949
+ });
950
+ }
951
+ }
952
+ if (receipt.preStateHash && receipt.postStateHash && receipt.preStateHash === receipt.postStateHash && receipt.status === "confirmed") {
953
+ divergences.push({
954
+ txId: receipt.txId,
955
+ kind: "state-hash-mismatch",
956
+ field: "stateTransition",
957
+ expected: "preStateHash !== postStateHash for confirmed tx",
958
+ actual: `both are ${receipt.preStateHash}`
959
+ });
960
+ }
961
+ if (receipt.mass && receipt.feeSompi) {
962
+ const mass = BigInt(receipt.mass);
963
+ const fee = BigInt(receipt.feeSompi);
964
+ if (mass > 0n && fee > 0n && fee > mass * 10n) {
965
+ divergences.push({
966
+ txId: receipt.txId,
967
+ kind: "fee-mismatch",
968
+ field: "feeSompi",
969
+ expected: `fee proportional to mass (mass=${mass})`,
970
+ actual: `fee=${fee} (ratio=${fee / mass})`
971
+ });
972
+ }
973
+ }
974
+ const spentCount = receipt.spentUtxoIds?.length ?? 0;
975
+ const createdCount = receipt.createdUtxoIds?.length ?? 0;
976
+ if (receipt.status === "confirmed" && spentCount === 0) {
977
+ divergences.push({
978
+ txId: receipt.txId,
979
+ kind: "utxo-count-mismatch",
980
+ field: "spentUtxoIds",
981
+ expected: "at least 1 spent UTXO for confirmed tx",
982
+ actual: `${spentCount} spent`
983
+ });
984
+ }
985
+ if (receipt.status === "failed" && receipt.postStateHash && receipt.postStateHash !== receipt.preStateHash) {
986
+ divergences.push({
987
+ txId: receipt.txId,
988
+ kind: "status-mismatch",
989
+ field: "status",
990
+ expected: "failed tx should not change state",
991
+ actual: `state changed: ${receipt.preStateHash} \u2192 ${receipt.postStateHash}`
992
+ });
993
+ }
994
+ }
995
+ divergences.sort((a, b) => a.txId.localeCompare(b.txId));
996
+ const paged = divergences.slice(request.offset, request.offset + request.limit);
997
+ let explain;
998
+ if (request.explain) {
999
+ explain = paged.map((d) => explainDivergence(d));
1000
+ }
1001
+ return {
1002
+ domain: "replay",
1003
+ op: "divergences",
1004
+ items: paged,
1005
+ total: divergences.length,
1006
+ truncated: divergences.length > request.offset + request.limit,
1007
+ deterministic: true,
1008
+ queryHash: computeQueryHash(paged),
1009
+ explain,
1010
+ annotations: {
1011
+ executedAt: (/* @__PURE__ */ new Date()).toISOString(),
1012
+ executionMs: Date.now() - start,
1013
+ filesScanned: receipts.length
1014
+ }
1015
+ };
1016
+ }
1017
+ // -------------------------------------------------------------------------
1018
+ // Invariants — check replay invariants for a specific txId
1019
+ // -------------------------------------------------------------------------
1020
+ async executeInvariants(request) {
1021
+ const start = Date.now();
1022
+ const txId = request.params["txId"];
1023
+ if (!txId) throw new Error("invariants requires params.txId");
1024
+ const receipt = await this.loadReceipt(txId);
1025
+ if (!receipt) throw new Error(`Receipt not found for txId: ${txId}`);
1026
+ const issues = [];
1027
+ const planIntegrity = receipt.contentHash ? computeContentHashSafe(receipt) === receipt.contentHash : true;
1028
+ if (!planIntegrity) {
1029
+ issues.push("Receipt contentHash does not match recomputed hash");
1030
+ }
1031
+ const receiptReproducible = !!(receipt.preStateHash && receipt.postStateHash);
1032
+ if (!receiptReproducible) {
1033
+ issues.push("Missing pre/post state hashes \u2014 replay comparison not possible");
1034
+ }
1035
+ let stateTransitionValid = true;
1036
+ if (receipt.status === "confirmed") {
1037
+ if (receipt.preStateHash === receipt.postStateHash) {
1038
+ stateTransitionValid = false;
1039
+ issues.push("Confirmed tx did not change state (pre === post)");
1040
+ }
1041
+ } else if (receipt.status === "failed") {
1042
+ if (receipt.preStateHash && receipt.postStateHash && receipt.preStateHash !== receipt.postStateHash) {
1043
+ stateTransitionValid = false;
1044
+ issues.push("Failed tx changed state (pre !== post)");
1045
+ }
1046
+ }
1047
+ const spentCount = receipt.spentUtxoIds?.length ?? 0;
1048
+ const createdCount = receipt.createdUtxoIds?.length ?? 0;
1049
+ let utxoConservation = true;
1050
+ if (receipt.status === "confirmed") {
1051
+ if (spentCount === 0) {
1052
+ utxoConservation = false;
1053
+ issues.push("Confirmed tx spent 0 UTXOs");
1054
+ }
1055
+ if (createdCount === 0) {
1056
+ utxoConservation = false;
1057
+ issues.push("Confirmed tx created 0 UTXOs");
1058
+ }
1059
+ }
1060
+ const result = {
1061
+ txId,
1062
+ planIntegrity,
1063
+ receiptReproducible,
1064
+ stateTransitionValid,
1065
+ utxoConservation,
1066
+ issues
1067
+ };
1068
+ let explain;
1069
+ if (request.explain) {
1070
+ explain = [explainInvariants(result)];
1071
+ }
1072
+ return {
1073
+ domain: "replay",
1074
+ op: "invariants",
1075
+ items: [result],
1076
+ total: 1,
1077
+ truncated: false,
1078
+ deterministic: true,
1079
+ queryHash: computeQueryHash([result]),
1080
+ explain,
1081
+ annotations: {
1082
+ executedAt: (/* @__PURE__ */ new Date()).toISOString(),
1083
+ executionMs: Date.now() - start,
1084
+ filesScanned: 1
1085
+ }
1086
+ };
1087
+ }
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
+ async readJsonSafe(filePath) {
1123
+ try {
1124
+ const content = await fs3.readFile(filePath, "utf-8");
1125
+ return JSON.parse(content);
1126
+ } catch {
1127
+ return null;
1128
+ }
1129
+ }
1130
+ };
1131
+ function toSummary(receipt, trace) {
1132
+ return {
1133
+ txId: receipt.txId || "unknown",
1134
+ status: receipt.status || "unknown",
1135
+ mode: receipt.mode || "unknown",
1136
+ networkId: receipt.networkId || "unknown",
1137
+ from: receipt.from?.address || "unknown",
1138
+ to: receipt.to?.address || "unknown",
1139
+ amountSompi: receipt.amountSompi || "0",
1140
+ feeSompi: receipt.feeSompi || "0",
1141
+ daaScore: receipt.daaScore || "0",
1142
+ preStateHash: receipt.preStateHash,
1143
+ postStateHash: receipt.postStateHash,
1144
+ spentUtxoCount: receipt.spentUtxoIds?.length ?? 0,
1145
+ createdUtxoCount: receipt.createdUtxoIds?.length ?? 0,
1146
+ hasTrace: !!trace,
1147
+ traceEventCount: trace?.events?.length ?? 0
1148
+ };
1149
+ }
1150
+ function computeContentHashSafe(obj) {
1151
+ try {
1152
+ return calculateContentHash2(obj);
1153
+ } catch {
1154
+ return "error-computing-hash";
1155
+ }
1156
+ }
1157
+ var DIVERGENCE_RULES = {
1158
+ "state-hash-mismatch": "Deterministic replay invariant: identical plan + identical state = identical postStateHash",
1159
+ "fee-mismatch": "Fee must be proportional to estimated mass. Disproportionate fees indicate estimation drift.",
1160
+ "utxo-count-mismatch": "Confirmed transactions must spend at least 1 UTXO (inputs) and create at least 1 (outputs).",
1161
+ "status-mismatch": "Failed transactions must not modify state. Confirmed transactions must modify state.",
1162
+ "txid-mismatch": "Deterministic txId generation: same plan + same state + same daaScore = same txId.",
1163
+ "ordering-divergence": "UTXO selection order must be deterministic. Non-deterministic ordering breaks replay invariants."
1164
+ };
1165
+ function explainDivergence(d) {
1166
+ return {
1167
+ question: `Why is tx ${d.txId.slice(0, 16)}... flagged as divergent?`,
1168
+ conclusion: `Divergence type: ${d.kind}. Field "${d.field}" expected ${d.expected.slice(0, 40)}${d.expected.length > 40 ? "..." : ""}, got ${d.actual.slice(0, 40)}${d.actual.length > 40 ? "..." : ""}.`,
1169
+ steps: [
1170
+ { order: 1, assertion: `Field "${d.field}" does not match expected value`, evidence: `expected: ${d.expected}, actual: ${d.actual}`, rule: DIVERGENCE_RULES[d.kind] },
1171
+ { order: 2, assertion: `Classified as ${d.kind}`, evidence: `Divergence category based on field and semantic context`, rule: "Replay divergence classification (query/replay-adapter.ts)" }
1172
+ ],
1173
+ model: "replay-invariants",
1174
+ confidence: "definitive",
1175
+ references: [d.txId]
1176
+ };
1177
+ }
1178
+ function explainInvariants(result) {
1179
+ const steps = [];
1180
+ let order = 1;
1181
+ steps.push({ order: order++, assertion: result.planIntegrity ? "Plan contentHash is stable" : "Plan contentHash MISMATCH", evidence: `planIntegrity=${result.planIntegrity}`, rule: "canonicalStringify + SHA-256 determinism" });
1182
+ steps.push({ order: order++, assertion: result.receiptReproducible ? "Receipt has pre/post state hashes for comparison" : "Receipt missing state hashes \u2014 replay comparison not possible", evidence: `receiptReproducible=${result.receiptReproducible}`, rule: "preStateHash + postStateHash required for replay verification" });
1183
+ steps.push({ order: order++, assertion: result.stateTransitionValid ? "State transition is consistent with status" : "State transition INCONSISTENT with status", evidence: `stateTransitionValid=${result.stateTransitionValid}`, rule: "Confirmed tx must change state. Failed tx must not." });
1184
+ steps.push({ order: order++, assertion: result.utxoConservation ? "UTXO conservation holds" : "UTXO conservation VIOLATION", evidence: `utxoConservation=${result.utxoConservation}`, rule: "Confirmed tx must consume and produce UTXOs" });
1185
+ const allOk = result.planIntegrity && result.receiptReproducible && result.stateTransitionValid && result.utxoConservation;
1186
+ return {
1187
+ question: `Are replay invariants satisfied for tx ${result.txId.slice(0, 16)}...?`,
1188
+ conclusion: allOk ? "All replay invariants satisfied." : `Invariant violations: ${result.issues.join("; ")}`,
1189
+ steps,
1190
+ model: "replay-invariants",
1191
+ confidence: "definitive",
1192
+ references: [result.txId]
1193
+ };
1194
+ }
1195
+
1196
+ // src/adapters/dag-adapter.ts
1197
+ import fs4 from "fs/promises";
1198
+ import path4 from "path";
1199
+ var DagQueryAdapter = class {
1200
+ domain = "dag";
1201
+ rootDir;
1202
+ constructor(rootDir) {
1203
+ this.rootDir = rootDir;
1204
+ }
1205
+ supportedOps() {
1206
+ return ["conflicts", "displaced", "history", "sink-path", "anomalies"];
1207
+ }
1208
+ supportedFilters() {
1209
+ return ["txId", "blockId", "daaScore"];
1210
+ }
1211
+ async execute(request) {
1212
+ switch (request.op) {
1213
+ case "conflicts":
1214
+ return this.executeConflicts(request);
1215
+ case "displaced":
1216
+ return this.executeDisplaced(request);
1217
+ case "history":
1218
+ return this.executeHistory(request);
1219
+ case "sink-path":
1220
+ return this.executeSinkPath(request);
1221
+ case "anomalies":
1222
+ return this.executeAnomalies(request);
1223
+ default:
1224
+ throw new Error(`Unknown dag op: ${request.op}`);
1225
+ }
1226
+ }
1227
+ // -------------------------------------------------------------------------
1228
+ // Conflicts — double-spend conflict analysis
1229
+ // -------------------------------------------------------------------------
1230
+ async executeConflicts(request) {
1231
+ const start = Date.now();
1232
+ const dag = await this.loadDag();
1233
+ if (!dag) {
1234
+ return emptyDagResult("conflicts", start);
1235
+ }
1236
+ const items = dag.conflictSet.map((c) => ({
1237
+ outpoint: c.outpoint,
1238
+ winnerTxId: c.winnerTxId,
1239
+ loserTxIds: c.loserTxIds
1240
+ }));
1241
+ items.sort((a, b) => a.outpoint.localeCompare(b.outpoint));
1242
+ const paged = items.slice(request.offset, request.offset + request.limit);
1243
+ let explain;
1244
+ if (request.explain) {
1245
+ explain = paged.map((c) => explainConflict(c, dag));
1246
+ }
1247
+ return {
1248
+ domain: "dag",
1249
+ op: "conflicts",
1250
+ items: paged,
1251
+ total: items.length,
1252
+ truncated: items.length > request.offset + request.limit,
1253
+ deterministic: true,
1254
+ queryHash: computeQueryHash(paged),
1255
+ explain,
1256
+ annotations: { executedAt: (/* @__PURE__ */ new Date()).toISOString(), executionMs: Date.now() - start }
1257
+ };
1258
+ }
1259
+ // -------------------------------------------------------------------------
1260
+ // Displaced — transactions that were accepted then displaced
1261
+ // -------------------------------------------------------------------------
1262
+ async executeDisplaced(request) {
1263
+ const start = Date.now();
1264
+ const dag = await this.loadDag();
1265
+ if (!dag) {
1266
+ return emptyDagResult("displaced", start);
1267
+ }
1268
+ const items = dag.displacedTxIds.map((txId) => {
1269
+ const conflict = dag.conflictSet.find((c) => c.loserTxIds.includes(txId));
1270
+ const reason = conflict ? `Double-spend on outpoint ${conflict.outpoint}. Winner: ${conflict.winnerTxId}` : "Displaced by DAG reorganization";
1271
+ return {
1272
+ txId,
1273
+ reason,
1274
+ currentlyAccepted: dag.acceptedTxIds.includes(txId)
1275
+ };
1276
+ });
1277
+ items.sort((a, b) => a.txId.localeCompare(b.txId));
1278
+ const paged = items.slice(request.offset, request.offset + request.limit);
1279
+ let explain;
1280
+ if (request.explain) {
1281
+ explain = paged.map((d) => explainDisplacement(d, dag));
1282
+ }
1283
+ return {
1284
+ domain: "dag",
1285
+ op: "displaced",
1286
+ items: paged,
1287
+ total: items.length,
1288
+ truncated: items.length > request.offset + request.limit,
1289
+ deterministic: true,
1290
+ queryHash: computeQueryHash(paged),
1291
+ explain,
1292
+ annotations: { executedAt: (/* @__PURE__ */ new Date()).toISOString(), executionMs: Date.now() - start }
1293
+ };
1294
+ }
1295
+ // -------------------------------------------------------------------------
1296
+ // History — full lifecycle of a tx through the DAG
1297
+ // -------------------------------------------------------------------------
1298
+ async executeHistory(request) {
1299
+ const start = Date.now();
1300
+ const txId = request.params["txId"];
1301
+ if (!txId) throw new Error("history requires params.txId");
1302
+ const dag = await this.loadDag();
1303
+ if (!dag) throw new Error("No DAG state found. Run a simulation first.");
1304
+ const entries = [];
1305
+ for (const [blockId, block] of Object.entries(dag.blocks)) {
1306
+ if (!block) continue;
1307
+ if (block.acceptedTxIds.includes(txId)) {
1308
+ entries.push({
1309
+ txId,
1310
+ blockId,
1311
+ accepted: dag.acceptedTxIds.includes(txId),
1312
+ displaced: dag.displacedTxIds.includes(txId),
1313
+ inSinkPath: dag.selectedPathToSink.includes(blockId),
1314
+ daaScore: block.daaScore
1315
+ });
1316
+ }
1317
+ }
1318
+ if (entries.length === 0) {
1319
+ if (dag.displacedTxIds.includes(txId)) {
1320
+ entries.push({
1321
+ txId,
1322
+ blockId: "unknown",
1323
+ accepted: false,
1324
+ displaced: true,
1325
+ inSinkPath: false,
1326
+ daaScore: "0"
1327
+ });
1328
+ }
1329
+ }
1330
+ entries.sort((a, b) => a.daaScore.localeCompare(b.daaScore));
1331
+ let explain;
1332
+ if (request.explain) {
1333
+ explain = entries.map((e) => explainTxHistory(e, dag));
1334
+ }
1335
+ return {
1336
+ domain: "dag",
1337
+ op: "history",
1338
+ items: entries,
1339
+ total: entries.length,
1340
+ truncated: false,
1341
+ deterministic: true,
1342
+ queryHash: computeQueryHash(entries),
1343
+ explain,
1344
+ annotations: { executedAt: (/* @__PURE__ */ new Date()).toISOString(), executionMs: Date.now() - start }
1345
+ };
1346
+ }
1347
+ // -------------------------------------------------------------------------
1348
+ // Sink Path — current selected path from genesis to sink
1349
+ // -------------------------------------------------------------------------
1350
+ async executeSinkPath(request) {
1351
+ const start = Date.now();
1352
+ const dag = await this.loadDag();
1353
+ if (!dag) {
1354
+ return emptyDagResult("sink-path", start);
1355
+ }
1356
+ const nodes = dag.selectedPathToSink.map((blockId) => {
1357
+ const block = dag.blocks[blockId];
1358
+ return {
1359
+ blockId,
1360
+ daaScore: block?.daaScore ?? "0",
1361
+ acceptedTxCount: block?.acceptedTxIds.length ?? 0,
1362
+ isGenesis: block?.isGenesis ?? false
1363
+ };
1364
+ });
1365
+ const result = {
1366
+ nodes,
1367
+ sink: dag.sink,
1368
+ depth: nodes.length
1369
+ };
1370
+ return {
1371
+ domain: "dag",
1372
+ op: "sink-path",
1373
+ items: [result],
1374
+ total: 1,
1375
+ truncated: false,
1376
+ deterministic: true,
1377
+ queryHash: computeQueryHash([result]),
1378
+ annotations: { executedAt: (/* @__PURE__ */ new Date()).toISOString(), executionMs: Date.now() - start }
1379
+ };
1380
+ }
1381
+ // -------------------------------------------------------------------------
1382
+ // Anomalies — transactions in unexpected states
1383
+ // -------------------------------------------------------------------------
1384
+ async executeAnomalies(request) {
1385
+ const start = Date.now();
1386
+ const dag = await this.loadDag();
1387
+ if (!dag) {
1388
+ return emptyDagResult("anomalies", start);
1389
+ }
1390
+ const anomalies = [];
1391
+ for (const txId of dag.displacedTxIds) {
1392
+ if (!dag.acceptedTxIds.includes(txId)) {
1393
+ anomalies.push({
1394
+ kind: "displaced-never-reaccepted",
1395
+ description: `Transaction ${txId} was displaced and has not been re-accepted`,
1396
+ txId
1397
+ });
1398
+ }
1399
+ }
1400
+ const reachable = /* @__PURE__ */ new Set();
1401
+ const stack = [dag.sink];
1402
+ while (stack.length > 0) {
1403
+ const id = stack.pop();
1404
+ if (reachable.has(id)) continue;
1405
+ reachable.add(id);
1406
+ const block = dag.blocks[id];
1407
+ if (block) {
1408
+ for (const p of block.parents) stack.push(p);
1409
+ }
1410
+ }
1411
+ for (const blockId of Object.keys(dag.blocks)) {
1412
+ if (!reachable.has(blockId)) {
1413
+ anomalies.push({
1414
+ kind: "unreachable-block",
1415
+ description: `Block ${blockId} is not reachable from current sink ${dag.sink}`,
1416
+ blockId
1417
+ });
1418
+ }
1419
+ }
1420
+ for (const txId of dag.acceptedTxIds) {
1421
+ if (dag.displacedTxIds.includes(txId)) {
1422
+ anomalies.push({
1423
+ kind: "invariant-violation",
1424
+ description: `Transaction ${txId} is in both acceptedTxIds and displacedTxIds`,
1425
+ txId
1426
+ });
1427
+ }
1428
+ }
1429
+ anomalies.sort((a, b) => a.kind.localeCompare(b.kind));
1430
+ const paged = anomalies.slice(request.offset, request.offset + request.limit);
1431
+ let explain;
1432
+ if (request.explain) {
1433
+ explain = paged.map((a) => explainAnomaly(a, dag));
1434
+ }
1435
+ return {
1436
+ domain: "dag",
1437
+ op: "anomalies",
1438
+ items: paged,
1439
+ total: anomalies.length,
1440
+ truncated: anomalies.length > request.offset + request.limit,
1441
+ deterministic: true,
1442
+ queryHash: computeQueryHash(paged),
1443
+ explain,
1444
+ annotations: { executedAt: (/* @__PURE__ */ new Date()).toISOString(), executionMs: Date.now() - start }
1445
+ };
1446
+ }
1447
+ // -------------------------------------------------------------------------
1448
+ // Load DAG from state.json
1449
+ // -------------------------------------------------------------------------
1450
+ async loadDag() {
1451
+ const statePath = path4.join(this.rootDir, ".hardkas", "state.json");
1452
+ try {
1453
+ const content = await fs4.readFile(statePath, "utf-8");
1454
+ const state = JSON.parse(content);
1455
+ return state.dag ?? null;
1456
+ } catch {
1457
+ return null;
1458
+ }
1459
+ }
1460
+ };
1461
+ function emptyDagResult(op, start) {
1462
+ return {
1463
+ domain: "dag",
1464
+ op,
1465
+ items: [],
1466
+ total: 0,
1467
+ truncated: false,
1468
+ deterministic: true,
1469
+ queryHash: computeQueryHash([]),
1470
+ annotations: { executedAt: (/* @__PURE__ */ new Date()).toISOString(), executionMs: Date.now() - start }
1471
+ };
1472
+ }
1473
+ function explainConflict(conflict, dag) {
1474
+ const steps = [];
1475
+ let order = 1;
1476
+ let winnerBlockId;
1477
+ let winnerDaaScore = "0";
1478
+ let winnerInSinkPath = false;
1479
+ for (const [blockId, block] of Object.entries(dag.blocks)) {
1480
+ if (block?.acceptedTxIds.includes(conflict.winnerTxId)) {
1481
+ winnerBlockId = blockId;
1482
+ winnerDaaScore = block.daaScore;
1483
+ winnerInSinkPath = dag.selectedPathToSink.includes(blockId);
1484
+ break;
1485
+ }
1486
+ }
1487
+ steps.push({
1488
+ order: order++,
1489
+ assertion: `Outpoint ${conflict.outpoint} was spent by multiple transactions`,
1490
+ evidence: `Winner: ${conflict.winnerTxId}, Losers: ${conflict.loserTxIds.join(", ")}`,
1491
+ rule: "UTXO double-spend detection (dag.ts:moveSink)"
1492
+ });
1493
+ if (winnerBlockId) {
1494
+ steps.push({
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({
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)"
1505
+ });
1506
+ }
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
+ return {
1514
+ question: `Why did ${conflict.winnerTxId.slice(0, 16)}... win the conflict on outpoint ${conflict.outpoint}?`,
1515
+ conclusion: `Winner has ${winnerInSinkPath ? "sink-path priority" : "block ordering priority"}. ${conflict.loserTxIds.length} tx(s) displaced. Model: deterministic-light-model (NOT GHOSTDAG).`,
1516
+ steps,
1517
+ model: "deterministic-light-model",
1518
+ confidence: "definitive",
1519
+ references: [conflict.winnerTxId, ...conflict.loserTxIds]
1520
+ };
1521
+ }
1522
+ function explainDisplacement(d, _dag) {
1523
+ return {
1524
+ question: `Why was tx ${d.txId.slice(0, 16)}... displaced?`,
1525
+ conclusion: `${d.reason}. Currently accepted: ${d.currentlyAccepted}. Model: deterministic-light-model (NOT GHOSTDAG).`,
1526
+ steps: [
1527
+ { order: 1, assertion: `Transaction ${d.txId.slice(0, 16)}... is in displacedTxIds`, evidence: `dag.displacedTxIds includes "${d.txId}"`, rule: "DAG sink movement (dag.ts:moveSink)" },
1528
+ { order: 2, assertion: d.reason, evidence: d.reason, rule: "Conflict resolution or DAG reorganization" }
1529
+ ],
1530
+ model: "deterministic-light-model",
1531
+ confidence: "definitive",
1532
+ references: [d.txId]
1533
+ };
1534
+ }
1535
+ function explainTxHistory(entry, _dag) {
1536
+ const status = entry.accepted ? "ACCEPTED" : entry.displaced ? "DISPLACED" : "UNKNOWN";
1537
+ return {
1538
+ question: `What is the DAG lifecycle status of tx ${entry.txId.slice(0, 16)}...?`,
1539
+ conclusion: `Status: ${status}. Block: ${entry.blockId} (daaScore: ${entry.daaScore}). In sink path: ${entry.inSinkPath}. Model: deterministic-light-model (NOT GHOSTDAG).`,
1540
+ steps: [
1541
+ { order: 1, assertion: `Transaction is in block ${entry.blockId}`, evidence: `block.acceptedTxIds includes "${entry.txId}"`, rule: "Block membership scan" },
1542
+ { order: 2, assertion: `Block daaScore = ${entry.daaScore}`, evidence: `dag.blocks["${entry.blockId}"].daaScore = "${entry.daaScore}"` },
1543
+ { order: 3, assertion: entry.inSinkPath ? "Block IS in selected path to sink" : "Block is NOT in selected path to sink", evidence: `selectedPathToSink.includes("${entry.blockId}") = ${entry.inSinkPath}`, rule: "Sink path computation (dag.ts:calculateSelectedPath)" },
1544
+ { order: 4, assertion: `Current status: ${status}`, evidence: `accepted=${entry.accepted}, displaced=${entry.displaced}`, rule: "DAG accepted/displaced set membership" }
1545
+ ],
1546
+ model: "deterministic-light-model",
1547
+ confidence: "definitive",
1548
+ references: [entry.txId]
1549
+ };
1550
+ }
1551
+ function explainAnomaly(a, _dag) {
1552
+ return {
1553
+ question: `Why is this a DAG anomaly?`,
1554
+ conclusion: `${a.description}. Model: deterministic-light-model (NOT GHOSTDAG).`,
1555
+ steps: [
1556
+ { order: 1, assertion: a.description, evidence: `Anomaly kind: ${a.kind}`, rule: "DAG invariant check (query/dag-adapter.ts)" }
1557
+ ],
1558
+ model: "deterministic-light-model",
1559
+ confidence: "definitive",
1560
+ references: [a.txId ?? a.blockId ?? ""]
1561
+ };
1562
+ }
1563
+
1564
+ // src/adapters/events-adapter.ts
1565
+ import fs5 from "fs/promises";
1566
+ import path5 from "path";
1567
+ import { validateEventEnvelope } from "@hardkas/core";
1568
+ var EventsQueryAdapter = class {
1569
+ domain = "events";
1570
+ rootDir;
1571
+ constructor(rootDir) {
1572
+ this.rootDir = rootDir;
1573
+ }
1574
+ supportedOps() {
1575
+ return ["list", "summary"];
1576
+ }
1577
+ supportedFilters() {
1578
+ return ["txId", "workflowId", "domain", "kind", "networkId", "artifactId"];
1579
+ }
1580
+ async execute(request) {
1581
+ switch (request.op) {
1582
+ case "list":
1583
+ return this.executeList(request);
1584
+ default:
1585
+ throw new Error(`Unknown events op: ${request.op}`);
1586
+ }
1587
+ }
1588
+ async executeList(request) {
1589
+ const start = Date.now();
1590
+ const events = await this.loadEvents();
1591
+ const backendUsed = "filesystem";
1592
+ const eventsPath = path5.join(this.rootDir, ".hardkas", "events.jsonl");
1593
+ const filtered = [];
1594
+ const txFilter = request.params["tx"] || request.params["txId"];
1595
+ const effectiveFilters = [...request.filters];
1596
+ if (txFilter) {
1597
+ effectiveFilters.push({ field: "txId", op: "eq", value: txFilter });
1598
+ }
1599
+ for (const event of events) {
1600
+ if (evaluateFilters(event, effectiveFilters)) {
1601
+ filtered.push(event);
1602
+ }
1603
+ }
1604
+ const sorted = [...filtered].sort((a, b) => {
1605
+ const cmp = a.timestamp.localeCompare(b.timestamp);
1606
+ if (cmp !== 0) return cmp;
1607
+ return a.eventId.localeCompare(b.eventId);
1608
+ });
1609
+ const total = sorted.length;
1610
+ const paged = sorted.slice(request.offset, request.offset + request.limit);
1611
+ return {
1612
+ domain: "events",
1613
+ op: "list",
1614
+ items: paged,
1615
+ total,
1616
+ truncated: total > request.offset + request.limit,
1617
+ deterministic: true,
1618
+ queryHash: computeQueryHash(paged),
1619
+ annotations: {
1620
+ executedAt: (/* @__PURE__ */ new Date()).toISOString(),
1621
+ executionMs: Date.now() - start,
1622
+ filesScanned: 1
1623
+ },
1624
+ ...request.explain ? {
1625
+ explain: [{
1626
+ question: "How were events loaded?",
1627
+ conclusion: `Loaded ${events.length} events from ${backendUsed}. Applied ${effectiveFilters.length} filter(s). Returned ${paged.length}/${total} results sorted by timestamp+eventId.`,
1628
+ steps: [
1629
+ { order: 1, assertion: `Backend: ${backendUsed} (events.jsonl)`, evidence: eventsPath, rule: "Filesystem fallback" },
1630
+ { order: 2, assertion: `Filters applied: ${effectiveFilters.length}`, evidence: effectiveFilters.map((f) => `${f.field} ${f.op} ${f.value}`).join(", ") || "none" },
1631
+ { order: 3, assertion: `Ordering: timestamp ASC + eventId tiebreaker`, evidence: "Deterministic" }
1632
+ ],
1633
+ model: "events-query",
1634
+ confidence: "definitive",
1635
+ references: []
1636
+ }]
1637
+ } : {}
1638
+ };
1639
+ }
1640
+ async loadEvents() {
1641
+ const eventsPath = path5.join(this.rootDir, ".hardkas", "events.jsonl");
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() !== "");
1649
+ const events = [];
1650
+ for (const line of lines) {
1651
+ try {
1652
+ const parsed = JSON.parse(line);
1653
+ if (!validateEventEnvelope(parsed)) continue;
1654
+ events.push({
1655
+ eventId: parsed.eventId,
1656
+ kind: parsed.kind,
1657
+ domain: parsed.domain,
1658
+ timestamp: parsed.timestamp || "",
1659
+ workflowId: parsed.workflowId,
1660
+ correlationId: parsed.correlationId,
1661
+ causationId: parsed.causationId,
1662
+ txId: parsed.txId,
1663
+ artifactId: parsed.artifactId,
1664
+ networkId: parsed.networkId,
1665
+ payload: parsed.payload
1666
+ });
1667
+ } catch {
1668
+ }
1669
+ }
1670
+ return events;
1671
+ }
1672
+ };
1673
+
1674
+ // 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
+ var TxQueryAdapter = class {
1679
+ domain = "tx";
1680
+ rootDir;
1681
+ constructor(rootDir) {
1682
+ this.rootDir = rootDir;
1683
+ }
1684
+ supportedOps() {
1685
+ return ["aggregate"];
1686
+ }
1687
+ supportedFilters() {
1688
+ return ["txId"];
1689
+ }
1690
+ async execute(request) {
1691
+ switch (request.op) {
1692
+ case "aggregate":
1693
+ return this.executeAggregate(request);
1694
+ default:
1695
+ throw new Error(`Unknown tx op: ${request.op}`);
1696
+ }
1697
+ }
1698
+ async executeAggregate(request) {
1699
+ const start = Date.now();
1700
+ const txId = request.params["txId"];
1701
+ if (!txId) throw new Error("tx aggregate requires params.txId");
1702
+ const warnings = [];
1703
+ const artifacts = await this.findArtifactsByTxId(txId);
1704
+ const events = await this.findEventsByTxId(txId);
1705
+ if (artifacts.length === 0) warnings.push("No artifacts found for this txId");
1706
+ if (events.length === 0) warnings.push("No events found for this txId");
1707
+ const roles = new Set(artifacts.map((a) => a.role));
1708
+ if (!roles.has("plan")) warnings.push("Missing tx plan artifact");
1709
+ if (!roles.has("signed")) warnings.push("Missing signed tx artifact");
1710
+ if (!roles.has("receipt")) warnings.push("Missing tx receipt artifact (may not exist yet)");
1711
+ const complete = roles.has("plan") && roles.has("signed");
1712
+ const result = {
1713
+ txId,
1714
+ artifacts,
1715
+ events: events.sort((a, b) => a.timestamp.localeCompare(b.timestamp) || a.eventId.localeCompare(b.eventId)),
1716
+ warnings,
1717
+ complete
1718
+ };
1719
+ let explain;
1720
+ if (request.explain) {
1721
+ explain = [{
1722
+ question: `What data exists for transaction ${txId}?`,
1723
+ conclusion: complete ? `Found ${artifacts.length} artifact(s) and ${events.length} event(s). Workflow appears complete.` : `Found ${artifacts.length} artifact(s) and ${events.length} event(s). ${warnings.join(". ")}.`,
1724
+ steps: [
1725
+ { order: 1, assertion: `Backend: filesystem`, evidence: path6.join(this.rootDir, ".hardkas"), rule: "Filesystem scan" },
1726
+ { order: 2, assertion: `Artifacts found: ${artifacts.length}`, evidence: artifacts.map((a) => `${a.schema} (${a.role})`).join(", ") || "none" },
1727
+ { order: 3, assertion: `Events found: ${events.length}`, evidence: events.map((e) => e.kind).join(", ") || "none" },
1728
+ { order: 4, assertion: `Completeness: ${complete ? "yes" : "no"}`, evidence: warnings.join("; ") || "all artifacts present" }
1729
+ ],
1730
+ model: "tx-aggregation",
1731
+ confidence: "definitive",
1732
+ references: [txId]
1733
+ }];
1734
+ }
1735
+ return {
1736
+ domain: "tx",
1737
+ op: "aggregate",
1738
+ items: [result],
1739
+ total: 1,
1740
+ truncated: false,
1741
+ deterministic: true,
1742
+ queryHash: computeQueryHash([result]),
1743
+ explain,
1744
+ annotations: {
1745
+ executedAt: (/* @__PURE__ */ new Date()).toISOString(),
1746
+ executionMs: Date.now() - start
1747
+ }
1748
+ };
1749
+ }
1750
+ async findArtifactsByTxId(txId) {
1751
+ const results = [];
1752
+ const files = await this.scanJsonFiles();
1753
+ for (const filePath of files) {
1754
+ try {
1755
+ const content = await fs6.readFile(filePath, "utf-8");
1756
+ const parsed = JSON.parse(content);
1757
+ if (!parsed.schema) continue;
1758
+ const matchesTx = parsed.txId === txId || parsed.transaction?.id === txId || parsed.lineage?.artifactId && parsed.txId === txId;
1759
+ if (!matchesTx) continue;
1760
+ const schema = String(parsed.schema);
1761
+ let role = "unknown";
1762
+ if (schema.includes("txPlan")) role = "plan";
1763
+ else if (schema.includes("signedTx")) role = "signed";
1764
+ else if (schema.includes("txReceipt")) role = "receipt";
1765
+ results.push({
1766
+ filePath,
1767
+ schema,
1768
+ contentHash: parsed.contentHash,
1769
+ role
1770
+ });
1771
+ } catch {
1772
+ }
1773
+ }
1774
+ return results.sort((a, b) => a.filePath.localeCompare(b.filePath));
1775
+ }
1776
+ async findEventsByTxId(txId) {
1777
+ const eventsPath = path6.join(this.rootDir, ".hardkas", "events.jsonl");
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() !== "");
1785
+ const results = [];
1786
+ for (const line of lines) {
1787
+ try {
1788
+ const parsed = JSON.parse(line);
1789
+ if (!validateEventEnvelope2(parsed)) continue;
1790
+ if (parsed.txId !== txId) continue;
1791
+ results.push({
1792
+ eventId: parsed.eventId,
1793
+ kind: parsed.kind,
1794
+ timestamp: parsed.timestamp || ""
1795
+ });
1796
+ } catch {
1797
+ }
1798
+ }
1799
+ return results;
1800
+ }
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
+ };
1825
+
1826
+ // src/engine.ts
1827
+ var QueryEngine = class {
1828
+ adapters;
1829
+ constructor(options) {
1830
+ 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));
1837
+ }
1838
+ /**
1839
+ * Execute a query request against the appropriate adapter.
1840
+ */
1841
+ async execute(request) {
1842
+ const adapter = this.adapters.get(request.domain);
1843
+ if (!adapter) {
1844
+ throw new Error(`No adapter registered for domain: ${request.domain}`);
1845
+ }
1846
+ if (!adapter.supportedOps().includes(request.op)) {
1847
+ throw new Error(
1848
+ `Operation "${request.op}" is not supported by the "${request.domain}" adapter. Supported: ${adapter.supportedOps().join(", ")}`
1849
+ );
1850
+ }
1851
+ return adapter.execute(request);
1852
+ }
1853
+ /**
1854
+ * List available domains and their operations.
1855
+ */
1856
+ listCapabilities() {
1857
+ const result = [];
1858
+ for (const [domain, adapter] of this.adapters.entries()) {
1859
+ result.push({
1860
+ domain,
1861
+ ops: adapter.supportedOps(),
1862
+ filters: adapter.supportedFilters()
1863
+ });
1864
+ }
1865
+ return result;
1866
+ }
1867
+ };
1868
+ function createQueryRequest(overrides) {
1869
+ return {
1870
+ domain: overrides.domain,
1871
+ op: overrides.op,
1872
+ filters: overrides.filters ?? [],
1873
+ sort: overrides.sort,
1874
+ limit: overrides.limit ?? 100,
1875
+ offset: overrides.offset ?? 0,
1876
+ explain: overrides.explain ?? false,
1877
+ params: overrides.params ?? {}
1878
+ };
1879
+ }
1880
+ export {
1881
+ ArtifactQueryAdapter,
1882
+ DagQueryAdapter,
1883
+ EventsQueryAdapter,
1884
+ LineageQueryAdapter,
1885
+ QueryEngine,
1886
+ ReplayQueryAdapter,
1887
+ TxQueryAdapter,
1888
+ computeQueryHash,
1889
+ createQueryRequest,
1890
+ evaluateFilter,
1891
+ evaluateFilters,
1892
+ explainIntegrity,
1893
+ explainOrphan,
1894
+ explainTransition,
1895
+ formatExplainBrief,
1896
+ formatExplainFull,
1897
+ resolveFieldPath,
1898
+ serializeQueryResult
1899
+ };