@mikeargento/bitgraph-player 0.7.0 → 0.8.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/__tests__/fuse-check.test.d.ts +2 -0
- package/dist/__tests__/fuse-check.test.d.ts.map +1 -0
- package/dist/__tests__/fuse-check.test.js +152 -0
- package/dist/__tests__/fuse-check.test.js.map +1 -0
- package/dist/check.d.ts +53 -2
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +182 -6
- package/dist/check.js.map +1 -1
- package/dist/cli.js +0 -0
- package/dist/verdict.d.ts +1 -1
- package/dist/verdict.js +1 -1
- package/dist-web/verify.html +5 -5
- package/package.json +4 -4
- package/src/__tests__/fuse-check.test.ts +168 -0
- package/src/check.ts +232 -7
- package/src/verdict.ts +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fuse-check.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/fuse-check.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// Copyright (c) 2024-2026 Mike Argento. Licensed under the MIT License. See LICENSE.
|
|
2
|
+
/**
|
|
3
|
+
* `check` over fused recordings (profile bitgraph-fuse/1, working name).
|
|
4
|
+
* Fixtures are real proofs minted through the local enclave harness
|
|
5
|
+
* (real signatures, fake PCR0), read-only here; no ledger, no network.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, it } from "node:test";
|
|
8
|
+
import * as assert from "node:assert/strict";
|
|
9
|
+
import { readFile, mkdtemp, writeFile, rm, mkdir } from "node:fs/promises";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
import { ingestBundle, ingestEntries } from "@mikeargento/bitgraph-audit";
|
|
14
|
+
import { checkIngest, fusedFloor, renderCheckText, serializeCheckReport } from "../check.js";
|
|
15
|
+
const FIX = fileURLToPath(new URL("../../../../src/__tests__/fuse-fixtures/", import.meta.url));
|
|
16
|
+
const read = (name) => readFile(join(FIX, name)).then((b) => new Uint8Array(b));
|
|
17
|
+
async function entries(spec) {
|
|
18
|
+
const out = [];
|
|
19
|
+
for (const [path, fixture] of Object.entries(spec)) {
|
|
20
|
+
const bytes = await read(fixture);
|
|
21
|
+
out.push({ path, open: () => bytes });
|
|
22
|
+
}
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
describe("fused recordings in check", () => {
|
|
26
|
+
it("fused bytes beside the proof: fused line TRUE, floor undetermined without anchors, statements carried", async () => {
|
|
27
|
+
const report = await checkIngest(await ingestEntries(await entries({ "proof.json": "trailer.proof.json", "photo.bin": "fused-trailer.bin" })));
|
|
28
|
+
assert.equal(report.recordings.length, 1);
|
|
29
|
+
const rec = report.recordings[0];
|
|
30
|
+
const fused = rec.lines.find((l) => l.name === "fused");
|
|
31
|
+
assert.ok(fused, "a fused line exists");
|
|
32
|
+
assert.equal(fused.result, "TRUE", fused.detail);
|
|
33
|
+
assert.equal(rec.lines.find((l) => l.name === "file")?.result, "TRUE");
|
|
34
|
+
assert.equal(rec.lines.find((l) => l.name === "signature")?.result, "TRUE");
|
|
35
|
+
assert.ok(rec.fused);
|
|
36
|
+
assert.equal(rec.fused.category, "FUSED_DIRECT");
|
|
37
|
+
assert.equal(rec.fused.evidence, "fused-bytes");
|
|
38
|
+
assert.equal(rec.fused.placement, "trailer/1");
|
|
39
|
+
assert.equal(rec.fused.floor, null);
|
|
40
|
+
assert.match(rec.fused.floorDetail, /floor undetermined: no anchor precedes this slot in its epoch/);
|
|
41
|
+
assert.equal(rec.fused.statements.length, 2);
|
|
42
|
+
assert.match(rec.fused.statements[1], /at position \d+, and were committed no later than position \d+/);
|
|
43
|
+
assert.ok(rec.fused.span && BigInt(rec.fused.span.positions) >= 1n);
|
|
44
|
+
// The harness attests with a fake document, so the attestation line is FALSE
|
|
45
|
+
// here; that is the harness, not the fused check, and it is reported on its
|
|
46
|
+
// own line. The fused line above stands regardless.
|
|
47
|
+
assert.notEqual(rec.lines.find((l) => l.name === "attestation")?.result, "TRUE");
|
|
48
|
+
const text = renderCheckText(report);
|
|
49
|
+
assert.match(text, /fused floor floor undetermined/);
|
|
50
|
+
assert.match(text, /fused span slot \d+ to commit \d+/);
|
|
51
|
+
});
|
|
52
|
+
it("the original beside the proof: rebuilt, fused line TRUE, the original is noted as such and not as unmatched", async () => {
|
|
53
|
+
const report = await checkIngest(await ingestEntries(await entries({ "proof.json": "trailer.proof.json", "original.txt": "original.txt" })));
|
|
54
|
+
const rec = report.recordings[0];
|
|
55
|
+
const fused = rec.lines.find((l) => l.name === "fused");
|
|
56
|
+
assert.equal(fused.result, "TRUE", fused.detail);
|
|
57
|
+
assert.equal(rec.fused.category, "FUSED_FROM_ORIGIN");
|
|
58
|
+
assert.equal(rec.fused.evidence, "original");
|
|
59
|
+
// The committed bytes are not in hand, so the file line stays UNDETERMINED: the
|
|
60
|
+
// original receives the ceiling, the fused bytes the interval.
|
|
61
|
+
assert.equal(rec.lines.find((l) => l.name === "file")?.result, "UNDETERMINED");
|
|
62
|
+
assert.ok(report.notes.some((n) => /original of a fused recording: original\.txt/.test(n)), report.notes.join(" | "));
|
|
63
|
+
assert.ok(!report.notes.some((n) => /matches no recording here: original\.txt/.test(n)), "the original is not called unmatched");
|
|
64
|
+
});
|
|
65
|
+
it("neither bytes present: fused line UNDETERMINED, never FALSE", async () => {
|
|
66
|
+
const report = await checkIngest(await ingestEntries(await entries({ "proof.json": "trailer.proof.json" })));
|
|
67
|
+
const rec = report.recordings[0];
|
|
68
|
+
const fused = rec.lines.find((l) => l.name === "fused");
|
|
69
|
+
assert.equal(fused.result, "UNDETERMINED");
|
|
70
|
+
assert.match(fused.detail, /neither the fused bytes nor the original/);
|
|
71
|
+
assert.equal(rec.fused.category, "NO_EVIDENCE");
|
|
72
|
+
assert.equal(rec.fused.evidence, null);
|
|
73
|
+
});
|
|
74
|
+
it("a file committed under a different slot than its commitment names: fused line FALSE", async () => {
|
|
75
|
+
const report = await checkIngest(await ingestEntries(await entries({ "proof.json": "wrong-slot.proof.json", "photo.bin": "fused-wrong-slot.bin" })));
|
|
76
|
+
const rec = report.recordings[0];
|
|
77
|
+
assert.equal(rec.lines.find((l) => l.name === "fused")?.result, "FALSE");
|
|
78
|
+
assert.equal(rec.fused.category, "INVALID_SLOT_COMMITMENT");
|
|
79
|
+
assert.equal(report.result, "FALSE");
|
|
80
|
+
});
|
|
81
|
+
it("an unregistered placement: UNDETERMINED with the placement named", async () => {
|
|
82
|
+
const report = await checkIngest(await ingestEntries(await entries({ "proof.json": "unregistered.proof.json", "photo.bin": "fused-unregistered.bin" })));
|
|
83
|
+
const rec = report.recordings[0];
|
|
84
|
+
const fused = rec.lines.find((l) => l.name === "fused");
|
|
85
|
+
assert.equal(fused.result, "UNDETERMINED");
|
|
86
|
+
assert.match(fused.detail, /xmp\/9/);
|
|
87
|
+
});
|
|
88
|
+
it("an ordinary recording has no fused line and no fused block", async () => {
|
|
89
|
+
const report = await checkIngest(await ingestEntries(await entries({ "proof.json": "recorded.proof.json", "original.txt": "original.txt" })));
|
|
90
|
+
const rec = report.recordings[0];
|
|
91
|
+
assert.equal(rec.lines.find((l) => l.name === "fused"), undefined);
|
|
92
|
+
assert.equal(rec.fused, undefined);
|
|
93
|
+
assert.equal(rec.lines.find((l) => l.name === "file")?.result, "TRUE");
|
|
94
|
+
});
|
|
95
|
+
it("container/1 from the PNG alone and produced/1 from its own bytes", async () => {
|
|
96
|
+
const a = await checkIngest(await ingestEntries(await entries({ "proof.json": "container.proof.json", "image.png": "image.png" })));
|
|
97
|
+
assert.equal(a.recordings[0].fused.category, "FUSED_FROM_ORIGIN");
|
|
98
|
+
assert.equal(a.recordings[0].fused.placement, "container/1");
|
|
99
|
+
const b = await checkIngest(await ingestEntries(await entries({ "proof.json": "produced-origin.proof.json", "record.json": "produced-origin.json" })));
|
|
100
|
+
assert.equal(b.recordings[0].fused.category, "FUSED_DIRECT");
|
|
101
|
+
assert.equal(b.recordings[0].fused.placement, "produced/1");
|
|
102
|
+
});
|
|
103
|
+
it("disk and memory ingests of the same fused bundle give byte-identical reports", async () => {
|
|
104
|
+
const dir = await mkdtemp(join(tmpdir(), "bitgraph-fuse-check-"));
|
|
105
|
+
try {
|
|
106
|
+
await mkdir(dir, { recursive: true });
|
|
107
|
+
await writeFile(join(dir, "proof.json"), await read("trailer.proof.json"));
|
|
108
|
+
await writeFile(join(dir, "original.txt"), await read("original.txt"));
|
|
109
|
+
const fromDisk = await checkIngest(await ingestBundle(dir));
|
|
110
|
+
const fromMemory = await checkIngest(await ingestEntries(await entries({ "proof.json": "trailer.proof.json", "original.txt": "original.txt" })));
|
|
111
|
+
assert.equal(serializeCheckReport(fromDisk), serializeCheckReport(fromMemory));
|
|
112
|
+
assert.equal(fromDisk.recordings[0].fused.category, "FUSED_FROM_ORIGIN");
|
|
113
|
+
}
|
|
114
|
+
finally {
|
|
115
|
+
await rm(dir, { recursive: true, force: true });
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
describe("fusedFloor", () => {
|
|
120
|
+
const bound = (anchorProofHash, blockNumber, timestamp) => ({
|
|
121
|
+
kind: "not-before", anchorProofHash, blockNumber, blockHash: "0x" + "ab".repeat(32), timestamp,
|
|
122
|
+
evidence: "counter-order", weaker: true, basis: "block-hash-unpredictability", boundClass: "evidence", claim: "",
|
|
123
|
+
});
|
|
124
|
+
const anchor = (proofHash, counter) => ({ proofHash, chainId: "bitgraph:main", counter, metadataCorroboration: "absent" });
|
|
125
|
+
const segment = (lower) => ({ lowerBounds: lower });
|
|
126
|
+
it("picks the last verified anchor whose counter precedes the SLOT, not the commit", () => {
|
|
127
|
+
const anchors = new Map([["a1", anchor("a1", "10")], ["a2", anchor("a2", "40")], ["a3", anchor("a3", "45")]]);
|
|
128
|
+
const seg = segment([bound("a1", "100", 1000), bound("a2", "200", 2000), bound("a3", "300", 3000)]);
|
|
129
|
+
// slot 42, commit 50: a3 (counter 45) precedes the commit but not the slot.
|
|
130
|
+
const floor = fusedFloor(seg, anchors, "42");
|
|
131
|
+
assert.equal(floor?.anchorProofHash, "a2");
|
|
132
|
+
assert.equal(floor?.blockNumber, "200");
|
|
133
|
+
assert.equal(floor?.evidence, "counter-order");
|
|
134
|
+
});
|
|
135
|
+
it("no anchor before the slot: null (UNDET), never a following anchor", () => {
|
|
136
|
+
const anchors = new Map([["a3", anchor("a3", "45")]]);
|
|
137
|
+
assert.equal(fusedFloor(segment([bound("a3", "300", 3000)]), anchors, "42"), null);
|
|
138
|
+
assert.equal(fusedFloor(undefined, anchors, "42"), null);
|
|
139
|
+
assert.equal(fusedFloor(segment([bound("a3", "300", 3000)]), anchors, undefined), null);
|
|
140
|
+
});
|
|
141
|
+
it("a slot off the anchored chain has no floor", () => {
|
|
142
|
+
const anchors = new Map([["a1", anchor("a1", "10")]]);
|
|
143
|
+
assert.equal(fusedFloor(segment([bound("a1", "100", 1000)]), anchors, "42", "global"), null);
|
|
144
|
+
assert.equal(fusedFloor(segment([bound("a1", "100", 1000)]), anchors, "42", "bitgraph:main")?.anchorProofHash, "a1");
|
|
145
|
+
});
|
|
146
|
+
it("only not-before bounds count; not-after bounds are never a ceiling", () => {
|
|
147
|
+
const anchors = new Map([["a9", anchor("a9", "5")]]);
|
|
148
|
+
const after = { ...bound("a9", "900", 9000), kind: "not-after" };
|
|
149
|
+
assert.equal(fusedFloor(segment([after]), anchors, "42"), null);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
//# sourceMappingURL=fuse-check.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fuse-check.test.js","sourceRoot":"","sources":["../../src/__tests__/fuse-check.test.ts"],"names":[],"mappings":"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"}
|
package/dist/check.d.ts
CHANGED
|
@@ -50,7 +50,8 @@
|
|
|
50
50
|
* no run-local value; bundle-relative entry paths are bundle content and
|
|
51
51
|
* are allowed. Same bundle bytes, same report bytes.
|
|
52
52
|
*/
|
|
53
|
-
import type { AuditResult, IngestResult } from "@mikeargento/bitgraph-audit";
|
|
53
|
+
import type { AnchorRecord, AuditResult, IngestResult, TemporalSegment } from "@mikeargento/bitgraph-audit";
|
|
54
|
+
import type { FuseVerifyResult } from "@mikeargento/bitgraph-verify";
|
|
54
55
|
import type { ThreeValued } from "./types.js";
|
|
55
56
|
/**
|
|
56
57
|
* Published BitGraph enclave PCR0 measurements, per PINS.md, oldest first.
|
|
@@ -65,7 +66,7 @@ export declare const KNOWN_ENCLAVE_MEASUREMENTS: ReadonlyArray<{
|
|
|
65
66
|
}>;
|
|
66
67
|
/** One checked property, three-valued, with a plain-language reason. */
|
|
67
68
|
export interface CheckLine {
|
|
68
|
-
name: "file" | "signature" | "attestation" | "enclave" | "witness" | "contradiction" | "domain";
|
|
69
|
+
name: "file" | "signature" | "attestation" | "enclave" | "witness" | "contradiction" | "domain" | "fused";
|
|
69
70
|
result: ThreeValued;
|
|
70
71
|
detail: string;
|
|
71
72
|
}
|
|
@@ -85,6 +86,38 @@ export interface CheckBounds {
|
|
|
85
86
|
notAfter?: CheckBound;
|
|
86
87
|
detail: string;
|
|
87
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* The wall-clock floor of a fused artifact: the last verified anchored
|
|
91
|
+
* block preceding its SLOT counter in the same epoch chain. Counter-order
|
|
92
|
+
* evidence by construction (no hash path reaches a slot record), reported
|
|
93
|
+
* with the same "genuine block" assumption as every other bound.
|
|
94
|
+
*/
|
|
95
|
+
export interface CheckFloor {
|
|
96
|
+
blockNumber?: string;
|
|
97
|
+
blockHash: string;
|
|
98
|
+
/** Unix seconds from the verified block header. */
|
|
99
|
+
timestamp: number;
|
|
100
|
+
anchorProofHash: string;
|
|
101
|
+
evidence: "counter-order";
|
|
102
|
+
}
|
|
103
|
+
/** What the bundle establishes about a recording marked fused (profile bitgraph-fuse/1, working name). */
|
|
104
|
+
export interface CheckFused {
|
|
105
|
+
category: FuseVerifyResult["category"] | "NO_EVIDENCE";
|
|
106
|
+
placement: string | null;
|
|
107
|
+
originDigestB64: string | null;
|
|
108
|
+
/** Which bytes in the bundle the commitment check ran against. */
|
|
109
|
+
evidence: "fused-bytes" | "original" | null;
|
|
110
|
+
/** The bounded statements, carrying the floor clause when a floor exists. */
|
|
111
|
+
statements: string[];
|
|
112
|
+
floor: CheckFloor | null;
|
|
113
|
+
floorDetail: string;
|
|
114
|
+
/** The causal span [N, M]. */
|
|
115
|
+
span: {
|
|
116
|
+
slotCounter: string;
|
|
117
|
+
commitCounter: string;
|
|
118
|
+
positions: string;
|
|
119
|
+
} | null;
|
|
120
|
+
}
|
|
88
121
|
/** A non-anchor recording in the bundle. */
|
|
89
122
|
export interface CheckRecording {
|
|
90
123
|
proofHash: string;
|
|
@@ -100,6 +133,8 @@ export interface CheckRecording {
|
|
|
100
133
|
/** Kleene all over `lines`. */
|
|
101
134
|
result: ThreeValued;
|
|
102
135
|
bounds: CheckBounds;
|
|
136
|
+
/** Present exactly when the proof's signed attribution marks it fused. */
|
|
137
|
+
fused?: CheckFused;
|
|
103
138
|
}
|
|
104
139
|
/** An Ethereum anchor recording in the bundle. */
|
|
105
140
|
export interface CheckAnchor {
|
|
@@ -184,6 +219,14 @@ export interface CheckOptions {
|
|
|
184
219
|
* embedders may supply additional candidates.
|
|
185
220
|
*/
|
|
186
221
|
sigEvidence?: ReadonlyMap<string, Uint8Array>;
|
|
222
|
+
/**
|
|
223
|
+
* Fuse evidence by proofHash: the verifyFuse result over the fused bytes
|
|
224
|
+
* (preferred) or the original, whichever the bundle holds. checkIngest
|
|
225
|
+
* collects this from the bundle; embedders may supply it.
|
|
226
|
+
*/
|
|
227
|
+
fuseEvidence?: ReadonlyMap<string, FuseVerifyResult>;
|
|
228
|
+
/** Content hashes (hex) of files that served as the original of a fused recording; not "unmatched". */
|
|
229
|
+
fuseOriginHexes?: ReadonlySet<string>;
|
|
187
230
|
}
|
|
188
231
|
/**
|
|
189
232
|
* Check an already-ingested bundle: runs the audit's pure tail (no
|
|
@@ -193,6 +236,14 @@ export interface CheckOptions {
|
|
|
193
236
|
export declare function checkIngest(ingest: IngestResult, options?: CheckOptions): Promise<CheckReport>;
|
|
194
237
|
/** The pure report builder over an AuditResult. */
|
|
195
238
|
export declare function buildCheckReport(audit: AuditResult, options?: CheckOptions): CheckReport;
|
|
239
|
+
/**
|
|
240
|
+
* The fused floor: among the segment's verified not-before anchors, those
|
|
241
|
+
* whose commit counter precedes the SLOT counter, tightest last (largest
|
|
242
|
+
* block number, else latest timestamp). Null when no verified anchor
|
|
243
|
+
* precedes the slot in its epoch chain.
|
|
244
|
+
*/
|
|
245
|
+
export declare const ANCHORED_CHAIN = "bitgraph:main";
|
|
246
|
+
export declare function fusedFloor(segment: TemporalSegment | undefined, anchorByHash: ReadonlyMap<string, AnchorRecord> | undefined, slotCounter: string | undefined, chainId?: string): CheckFloor | null;
|
|
196
247
|
export declare function renderCheckText(report: CheckReport): string;
|
|
197
248
|
/** Deterministic JSON bytes for the report: two-space indent, one trailing newline. */
|
|
198
249
|
export declare function serializeCheckReport(report: CheckReport): string;
|
package/dist/check.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../src/check.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AAEH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../src/check.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,WAAW,EAEX,YAAY,EAIZ,eAAe,EAChB,MAAM,6BAA6B,CAAC;AAGrC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAS9C;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,EAAE,aAAa,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAqBrG,CAAC;AAMF,wEAAwE;AACxE,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,aAAa,GAAG,SAAS,GAAG,SAAS,GAAG,eAAe,GAAG,QAAQ,GAAG,OAAO,CAAC;IAC1G,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,8EAA8E;AAC9E,MAAM,WAAW,UAAU;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,YAAY,GAAG,eAAe,CAAC;IACzC,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,qCAAqC,GAAG,eAAe,GAAG,eAAe,GAAG,YAAY,CAAC;IACjG,SAAS,CAAC,EAAE,UAAU,CAAC;IACvB,QAAQ,CAAC,EAAE,UAAU,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,eAAe,CAAC;CAC3B;AAED,0GAA0G;AAC1G,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,gBAAgB,CAAC,UAAU,CAAC,GAAG,aAAa,CAAC;IACvD,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,kEAAkE;IAClE,QAAQ,EAAE,aAAa,GAAG,UAAU,GAAG,IAAI,CAAC;IAC5C,6EAA6E;IAC7E,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,8BAA8B;IAC9B,IAAI,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CAChF;AAED,4CAA4C;AAC5C,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iFAAiF;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,+BAA+B;IAC/B,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,WAAW,CAAC;IACpB,0EAA0E;IAC1E,KAAK,CAAC,EAAE,UAAU,CAAC;CACpB;AAED,kDAAkD;AAClD,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,MAAM,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,6EAA6E;IAC7E,KAAK,EAAE,kBAAkB,GAAG,kBAAkB,CAAC;IAC/C,uEAAuE;IACvE,IAAI,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,MAAM,EAAE,WAAW,CAAC;IACpB,6DAA6D;IAC7D,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,kFAAkF;IAClF,cAAc,EAAE,SAAS,EAAE,CAAC;IAC5B,gFAAgF;IAChF,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,uEAAuE;IACvE,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,SAAS,EAAE;QAAE,IAAI,EAAE,iBAAiB,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IACvE,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IAChD;;;;OAIG;IACH,gBAAgB,CAAC,eAAe,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,MAAM,GAAG,SAAS,CAAC;CAC1G;AAED,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;;OAIG;IACH,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAC9C;;;;OAIG;IACH,YAAY,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IACrD,uGAAuG;IACvG,eAAe,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CACvC;AAwBD;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CAmBpG;AA8CD,mDAAmD;AACnD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,WAAW,CA8ExF;AAmID;;;;;GAKG;AACH,eAAO,MAAM,cAAc,kBAAkB,CAAC;AAE9C,wBAAgB,UAAU,CACxB,OAAO,EAAE,eAAe,GAAG,SAAS,EACpC,YAAY,EAAE,WAAW,CAAC,MAAM,EAAE,YAAY,CAAC,GAAG,SAAS,EAC3D,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,OAAO,GAAE,MAAuB,GAC/B,UAAU,GAAG,IAAI,CAyBnB;AAgjBD,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CA+C3D;AAED,uFAAuF;AACvF,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAEhE"}
|
package/dist/check.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Copyright (c) 2024-2026 Mike Argento. Licensed under the MIT License. See LICENSE.
|
|
2
|
-
import { auditIngest, AUDIT_VERSION, streamMatchedArtifacts } from "@mikeargento/bitgraph-audit";
|
|
2
|
+
import { auditIngest, AUDIT_VERSION, streamMatchedArtifacts, streamArtifactsByHash } from "@mikeargento/bitgraph-audit";
|
|
3
|
+
import { readFuseAttribution, verifyFuse, base64ToBytes, bytesToHex, resetEpochLinkState } from "@mikeargento/bitgraph-verify";
|
|
3
4
|
import { SIG_EVIDENCE_MAX_BYTES } from "./types.js";
|
|
4
5
|
import { kleeneAll } from "./logic.js";
|
|
5
6
|
import { PLAYER_VERSION } from "./verdict.js";
|
|
@@ -74,8 +75,61 @@ export async function checkIngest(ingest, options) {
|
|
|
74
75
|
opts = { ...opts, sigEvidence: collected };
|
|
75
76
|
}
|
|
76
77
|
const audit = await auditIngest(ingest, { startedAt: "" });
|
|
78
|
+
if (opts?.fuseEvidence === undefined) {
|
|
79
|
+
const collected = await collectFuseEvidence(ingest);
|
|
80
|
+
opts = { ...opts, fuseEvidence: collected.evidence, fuseOriginHexes: collected.originHexes };
|
|
81
|
+
}
|
|
77
82
|
return buildCheckReport(audit, opts);
|
|
78
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* For every proof marked fused, find the bytes the check can run against:
|
|
86
|
+
* the committed (fused) bytes if the bundle holds them, else the original
|
|
87
|
+
* named by the signed origin digest. Runs after the audit pass and resets
|
|
88
|
+
* the verify library's epoch-link state around its own verifications.
|
|
89
|
+
*/
|
|
90
|
+
async function collectFuseEvidence(ingest) {
|
|
91
|
+
const evidence = new Map();
|
|
92
|
+
const originHexes = new Set();
|
|
93
|
+
const wanted = [];
|
|
94
|
+
const hexes = new Set();
|
|
95
|
+
for (const proof of ingest.proofs) {
|
|
96
|
+
const marker = readFuseAttribution(proof.proof);
|
|
97
|
+
if (marker === null)
|
|
98
|
+
continue;
|
|
99
|
+
const artifactBytes = base64ToBytes(proof.proof.artifact.digestB64);
|
|
100
|
+
const artifactHex = artifactBytes !== null ? bytesToHex(artifactBytes) : null;
|
|
101
|
+
const originHex = marker.originDigest !== undefined ? bytesToHex(marker.originDigest) : null;
|
|
102
|
+
wanted.push({ proof, artifactHex, originHex });
|
|
103
|
+
if (artifactHex !== null)
|
|
104
|
+
hexes.add(artifactHex);
|
|
105
|
+
if (originHex !== null)
|
|
106
|
+
hexes.add(originHex);
|
|
107
|
+
}
|
|
108
|
+
if (wanted.length === 0)
|
|
109
|
+
return { evidence, originHexes };
|
|
110
|
+
const bytesByHex = new Map();
|
|
111
|
+
for await (const artifact of streamArtifactsByHash(ingest, hexes)) {
|
|
112
|
+
if (!bytesByHex.has(artifact.sha256Hex))
|
|
113
|
+
bytesByHex.set(artifact.sha256Hex, artifact.bytes);
|
|
114
|
+
}
|
|
115
|
+
resetEpochLinkState();
|
|
116
|
+
try {
|
|
117
|
+
for (const w of wanted) {
|
|
118
|
+
const direct = w.artifactHex !== null ? bytesByHex.get(w.artifactHex) : undefined;
|
|
119
|
+
const original = w.originHex !== null ? bytesByHex.get(w.originHex) : undefined;
|
|
120
|
+
if (original !== undefined && w.originHex !== null)
|
|
121
|
+
originHexes.add(w.originHex);
|
|
122
|
+
const bytes = direct ?? original;
|
|
123
|
+
if (bytes === undefined)
|
|
124
|
+
continue;
|
|
125
|
+
evidence.set(w.proof.proofHash, await verifyFuse({ proof: w.proof.proof, bytes }));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
finally {
|
|
129
|
+
resetEpochLinkState();
|
|
130
|
+
}
|
|
131
|
+
return { evidence, originHexes };
|
|
132
|
+
}
|
|
79
133
|
/** The pure report builder over an AuditResult. */
|
|
80
134
|
export function buildCheckReport(audit, options) {
|
|
81
135
|
const webCrypto = options?.webCryptoAvailable ?? true;
|
|
@@ -110,13 +164,13 @@ export function buildCheckReport(audit, options) {
|
|
|
110
164
|
anchors.push(buildAnchor(proof, anchor, witnessOutcomesByAnchor.get(proof.proofHash) ?? []));
|
|
111
165
|
}
|
|
112
166
|
else {
|
|
113
|
-
recordings.push(buildRecording(proof, attestationByHash.get(proof.proofHash), segmentByHash.get(proof.proofHash), artifactPathByProof.get(proof.proofHash), webCrypto, options?.from, options?.sigEvidence));
|
|
167
|
+
recordings.push(buildRecording(proof, attestationByHash.get(proof.proofHash), segmentByHash.get(proof.proofHash), artifactPathByProof.get(proof.proofHash), webCrypto, options?.from, options?.sigEvidence, options?.fuseEvidence, anchorByHash));
|
|
114
168
|
}
|
|
115
169
|
}
|
|
116
170
|
sortByPosition(recordings);
|
|
117
171
|
sortByPosition(anchors);
|
|
118
172
|
const contradictions = collectContradictions(audit);
|
|
119
|
-
const notes = collectNotes(audit, recordings, anchors);
|
|
173
|
+
const notes = collectNotes(audit, recordings, anchors, options?.fuseOriginHexes);
|
|
120
174
|
const notChecked = collectNotChecked(anchors.length > 0, options?.from?.domain);
|
|
121
175
|
const allLines = [
|
|
122
176
|
...recordings.map((r) => r.result),
|
|
@@ -164,7 +218,7 @@ function sortByPosition(items) {
|
|
|
164
218
|
// ---------------------------------------------------------------------------
|
|
165
219
|
// Recordings
|
|
166
220
|
// ---------------------------------------------------------------------------
|
|
167
|
-
function buildRecording(proof, attestation, segment, filePath, webCrypto, from, sigEvidence) {
|
|
221
|
+
function buildRecording(proof, attestation, segment, filePath, webCrypto, from, sigEvidence, fuseEvidence, anchorByHash) {
|
|
168
222
|
const lines = [];
|
|
169
223
|
const v = proof.verification;
|
|
170
224
|
const digestB64 = proof.proof.artifact.digestB64;
|
|
@@ -224,6 +278,18 @@ function buildRecording(proof, attestation, segment, filePath, webCrypto, from,
|
|
|
224
278
|
const verified = v !== undefined && v.status !== "failed";
|
|
225
279
|
lines.push(domainLine(proof, verified, from, sigEvidence ?? EMPTY_EVIDENCE));
|
|
226
280
|
}
|
|
281
|
+
// fused: the proof's signed attribution marks a fused artifact (profile
|
|
282
|
+
// bitgraph-fuse/1). The commitment check runs over the fused bytes, or
|
|
283
|
+
// over the original by reconstruction; its floor is the last verified
|
|
284
|
+
// anchor preceding the SLOT, not the commit.
|
|
285
|
+
const marker = readFuseAttribution(proof.proof);
|
|
286
|
+
let fused;
|
|
287
|
+
if (marker !== null) {
|
|
288
|
+
const floor = fusedFloor(segment, anchorByHash, proof.slotCounter, proof.chainId);
|
|
289
|
+
const built = fusedLine(marker.placement, marker.originDigest !== undefined ? proof.proof.attribution?.message ?? null : null, fuseEvidence?.get(proof.proofHash), floor, proof);
|
|
290
|
+
lines.push(built.line);
|
|
291
|
+
fused = built.fused;
|
|
292
|
+
}
|
|
227
293
|
const result = kleeneAll(lines.map((l) => l.result));
|
|
228
294
|
return {
|
|
229
295
|
proofHash: proof.proofHash,
|
|
@@ -237,8 +303,107 @@ function buildRecording(proof, attestation, segment, filePath, webCrypto, from,
|
|
|
237
303
|
lines,
|
|
238
304
|
result,
|
|
239
305
|
bounds: boundsFor(segment),
|
|
306
|
+
...(fused !== undefined ? { fused } : {}),
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* The fused floor: among the segment's verified not-before anchors, those
|
|
311
|
+
* whose commit counter precedes the SLOT counter, tightest last (largest
|
|
312
|
+
* block number, else latest timestamp). Null when no verified anchor
|
|
313
|
+
* precedes the slot in its epoch chain.
|
|
314
|
+
*/
|
|
315
|
+
export const ANCHORED_CHAIN = "bitgraph:main";
|
|
316
|
+
export function fusedFloor(segment, anchorByHash, slotCounter, chainId = ANCHORED_CHAIN) {
|
|
317
|
+
// Anchors exist only on the anchored chain and the anchor index carries no
|
|
318
|
+
// chain segment, so a slot on any other chain compares to nothing.
|
|
319
|
+
if (chainId !== ANCHORED_CHAIN)
|
|
320
|
+
return null;
|
|
321
|
+
if (segment === undefined || anchorByHash === undefined || slotCounter === undefined || !/^[0-9]+$/.test(slotCounter))
|
|
322
|
+
return null;
|
|
323
|
+
const slot = BigInt(slotCounter);
|
|
324
|
+
let best;
|
|
325
|
+
for (const b of segment.lowerBounds) {
|
|
326
|
+
if (b.kind !== "not-before")
|
|
327
|
+
continue;
|
|
328
|
+
const anchor = anchorByHash.get(b.anchorProofHash);
|
|
329
|
+
if (anchor === undefined || anchor.counter === undefined || !/^[0-9]+$/.test(anchor.counter))
|
|
330
|
+
continue;
|
|
331
|
+
if (BigInt(anchor.counter) >= slot)
|
|
332
|
+
continue;
|
|
333
|
+
if (best === undefined) {
|
|
334
|
+
best = b;
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
const bn = b.blockNumber !== undefined ? BigInt(b.blockNumber) : BigInt(b.timestamp);
|
|
338
|
+
const bb = best.blockNumber !== undefined ? BigInt(best.blockNumber) : BigInt(best.timestamp);
|
|
339
|
+
if (bn > bb)
|
|
340
|
+
best = b;
|
|
341
|
+
}
|
|
342
|
+
if (best === undefined)
|
|
343
|
+
return null;
|
|
344
|
+
return {
|
|
345
|
+
...(best.blockNumber !== undefined ? { blockNumber: best.blockNumber } : {}),
|
|
346
|
+
blockHash: best.blockHash,
|
|
347
|
+
timestamp: best.timestamp,
|
|
348
|
+
anchorProofHash: best.anchorProofHash,
|
|
349
|
+
evidence: "counter-order",
|
|
240
350
|
};
|
|
241
351
|
}
|
|
352
|
+
const FLOOR_UNDETERMINED = "floor undetermined: no anchor precedes this slot in its epoch";
|
|
353
|
+
function floorClause(floor) {
|
|
354
|
+
const block = floor.blockNumber !== undefined ? `#${floor.blockNumber}` : floor.blockHash;
|
|
355
|
+
return `anchored block ${block} (${new Date(floor.timestamp * 1000).toISOString()})`;
|
|
356
|
+
}
|
|
357
|
+
function fusedLine(placement, originMessage, ev, floor, proof) {
|
|
358
|
+
const slot = proof.slotCounter ?? "?";
|
|
359
|
+
const where = placement !== null ? ` (placement ${placement})` : " (placement undeclared)";
|
|
360
|
+
const floorDetail = floor !== null
|
|
361
|
+
? `assembled after ${floorClause(floor)}, the last verified anchor preceding slot ${slot} in this epoch`
|
|
362
|
+
: proof.chainId !== ANCHORED_CHAIN
|
|
363
|
+
? `floor undetermined: the slot is on chain "${proof.chainId}", not the anchored chain`
|
|
364
|
+
: FLOOR_UNDETERMINED;
|
|
365
|
+
const span = ev?.span !== undefined && ev.span !== null
|
|
366
|
+
? { slotCounter: ev.span.slotCounter, commitCounter: ev.span.commitCounter, positions: ev.span.positions }
|
|
367
|
+
: proof.slotCounter !== undefined && proof.counter !== undefined
|
|
368
|
+
? { slotCounter: proof.slotCounter, commitCounter: proof.counter, positions: (BigInt(proof.counter) - BigInt(proof.slotCounter)).toString() }
|
|
369
|
+
: null;
|
|
370
|
+
const statements = (ev?.statements ?? []).map((s) => floor !== null ? s.replace(`at position ${slot},`, `at position ${slot}, which followed ${floorClause(floor)},`) : s);
|
|
371
|
+
const base = (category, evidence) => ({
|
|
372
|
+
category,
|
|
373
|
+
placement: ev?.placement ?? placement,
|
|
374
|
+
originDigestB64: ev?.originDigestB64 ?? originMessage,
|
|
375
|
+
evidence,
|
|
376
|
+
statements,
|
|
377
|
+
floor,
|
|
378
|
+
floorDetail,
|
|
379
|
+
span,
|
|
380
|
+
});
|
|
381
|
+
if (ev === undefined) {
|
|
382
|
+
return {
|
|
383
|
+
line: { name: "fused", result: "UNDETERMINED", detail: `marked fused${where}, but neither the fused bytes nor ${originMessage !== null ? "the original" : "an original"} is in this bundle, so the commitment to slot ${slot} cannot be checked` },
|
|
384
|
+
fused: base("NO_EVIDENCE", null),
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
const evidence = ev.category === "FUSED_FROM_ORIGIN" || (ev.fileDigestB64 !== ev.artifactDigestB64 && ev.category !== "NO_MATCH") ? "original" : "fused-bytes";
|
|
388
|
+
switch (ev.category) {
|
|
389
|
+
case "FUSED_DIRECT":
|
|
390
|
+
return { line: { name: "fused", result: "TRUE", detail: `the fused bytes carry the commitment to slot ${slot}${where}` }, fused: base(ev.category, "fused-bytes") };
|
|
391
|
+
case "FUSED_FROM_ORIGIN":
|
|
392
|
+
return { line: { name: "fused", result: "TRUE", detail: `the original rebuilds the committed fused bytes${where}; they carry the commitment to slot ${slot}` }, fused: base(ev.category, "original") };
|
|
393
|
+
case "INVALID_SLOT_COMMITMENT":
|
|
394
|
+
return { line: { name: "fused", result: "FALSE", detail: `the fused bytes do not carry the commitment to this proof's slot ${slot}: ${ev.reason ?? ""}` }, fused: base(ev.category, evidence) };
|
|
395
|
+
case "INVALID_ORIGIN_ATTRIBUTION":
|
|
396
|
+
return { line: { name: "fused", result: "FALSE", detail: `the declared origin contradicts the origin inside the fused bytes: ${ev.reason ?? ""}` }, fused: base(ev.category, evidence) };
|
|
397
|
+
case "RECONSTRUCTION_MISMATCH":
|
|
398
|
+
return { line: { name: "fused", result: "FALSE", detail: `the file matching the declared origin does not rebuild the committed fused bytes: ${ev.reason ?? ""}` }, fused: base(ev.category, "original") };
|
|
399
|
+
case "UNDETERMINED_PLACEMENT":
|
|
400
|
+
return { line: { name: "fused", result: "UNDETERMINED", detail: `${ev.reason ?? "the placement is not registered"}; this verifier cannot check the commitment` }, fused: base(ev.category, evidence) };
|
|
401
|
+
case "INVALID_UNDERLYING_PROOF":
|
|
402
|
+
return { line: { name: "fused", result: "UNDETERMINED", detail: "not checked: the proof itself does not verify (see the signature line)" }, fused: base(ev.category, evidence) };
|
|
403
|
+
default:
|
|
404
|
+
return { line: { name: "fused", result: "UNDETERMINED", detail: `the commitment could not be checked: ${ev.reason ?? ev.category}` }, fused: base(ev.category, evidence) };
|
|
405
|
+
}
|
|
406
|
+
}
|
|
242
407
|
function attestationLine(record, webCrypto) {
|
|
243
408
|
if (record === undefined || !record.documentPresent) {
|
|
244
409
|
return {
|
|
@@ -564,7 +729,7 @@ function collectContradictions(audit) {
|
|
|
564
729
|
}
|
|
565
730
|
return out;
|
|
566
731
|
}
|
|
567
|
-
function collectNotes(audit, recordings, anchors) {
|
|
732
|
+
function collectNotes(audit, recordings, anchors, fuseOriginHexes) {
|
|
568
733
|
const notes = [];
|
|
569
734
|
// Excerpt gaps, said once, plainly.
|
|
570
735
|
let missingPositions = 0n;
|
|
@@ -587,7 +752,11 @@ function collectNotes(audit, recordings, anchors) {
|
|
|
587
752
|
}
|
|
588
753
|
// Files that match no recording: the human hint for an edited file or a
|
|
589
754
|
// wrong drop, without claiming which.
|
|
590
|
-
const
|
|
755
|
+
const originals = audit.ingest.artifacts.filter((a) => a.matchedProofHashes.length === 0 && fuseOriginHexes?.has(a.sha256Hex) === true);
|
|
756
|
+
if (originals.length > 0) {
|
|
757
|
+
notes.push(`${originals.length} file${originals.length === 1 ? "" : "s"} in this bundle ${originals.length === 1 ? "is" : "are"} the original of a fused recording: ${originals.slice(0, 5).map((a) => a.paths[0] ?? "(unnamed)").join(", ")}${originals.length > 5 ? ", …" : ""}. The original itself receives only the ceiling (it existed no later than the commit); the fused bytes receive the interval`);
|
|
758
|
+
}
|
|
759
|
+
const unmatched = audit.ingest.artifacts.filter((a) => a.matchedProofHashes.length === 0 && fuseOriginHexes?.has(a.sha256Hex) !== true);
|
|
591
760
|
if (unmatched.length > 0 && recordings.length > 0) {
|
|
592
761
|
const listed = unmatched
|
|
593
762
|
.slice(0, 5)
|
|
@@ -683,6 +852,13 @@ export function renderCheckText(report) {
|
|
|
683
852
|
for (const line of rec.lines)
|
|
684
853
|
out.push(` ${mark(line.result)} ${pad(line.name, 12)} ${line.detail}`);
|
|
685
854
|
out.push(` bounds ${rec.bounds.detail}`);
|
|
855
|
+
if (rec.fused !== undefined) {
|
|
856
|
+
out.push(` fused floor ${rec.fused.floorDetail}`);
|
|
857
|
+
if (rec.fused.span !== null)
|
|
858
|
+
out.push(` fused span slot ${rec.fused.span.slotCounter} to commit ${rec.fused.span.commitCounter} (${rec.fused.span.positions} positions)`);
|
|
859
|
+
for (const s of rec.fused.statements)
|
|
860
|
+
out.push(` statement ${s}`);
|
|
861
|
+
}
|
|
686
862
|
out.push("");
|
|
687
863
|
});
|
|
688
864
|
report.anchors.forEach((a, i) => {
|