@mikeargento/bitgraph-player 0.7.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=fuse-check.test.d.ts.map
@@ -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,161 @@
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("a Frame beside the fused bytes: the nested proof is the recording, the Frame is not an artifact", async () => {
27
+ const report = await checkIngest(await ingestEntries(await entries({ "photo.bitgraph-fuse.json": "trailer.bitgraph-fuse.json", "photo.bin": "fused-trailer.bin" })));
28
+ assert.equal(report.recordings.length, 1, "the Frame's nested proof is observed");
29
+ const rec = report.recordings[0];
30
+ assert.equal(rec.lines.find((l) => l.name === "file")?.result, "TRUE");
31
+ assert.equal(rec.lines.find((l) => l.name === "fused")?.result, "TRUE");
32
+ assert.equal(rec.fused?.category, "FUSED_DIRECT");
33
+ assert.ok(!report.notes.some((n) => /photo\.bitgraph-fuse\.json/.test(n)), "the Frame is not listed as an unmatched file");
34
+ });
35
+ it("fused bytes beside the proof: fused line TRUE, floor undetermined without anchors, statements carried", async () => {
36
+ const report = await checkIngest(await ingestEntries(await entries({ "proof.json": "trailer.proof.json", "photo.bin": "fused-trailer.bin" })));
37
+ assert.equal(report.recordings.length, 1);
38
+ const rec = report.recordings[0];
39
+ const fused = rec.lines.find((l) => l.name === "fused");
40
+ assert.ok(fused, "a fused line exists");
41
+ assert.equal(fused.result, "TRUE", fused.detail);
42
+ assert.equal(rec.lines.find((l) => l.name === "file")?.result, "TRUE");
43
+ assert.equal(rec.lines.find((l) => l.name === "signature")?.result, "TRUE");
44
+ assert.ok(rec.fused);
45
+ assert.equal(rec.fused.category, "FUSED_DIRECT");
46
+ assert.equal(rec.fused.evidence, "fused-bytes");
47
+ assert.equal(rec.fused.placement, "trailer/1");
48
+ assert.equal(rec.fused.floor, null);
49
+ assert.match(rec.fused.floorDetail, /floor undetermined: no anchor precedes this slot in its epoch/);
50
+ assert.equal(rec.fused.statements.length, 2);
51
+ assert.match(rec.fused.statements[1], /at position \d+, and were committed no later than position \d+/);
52
+ assert.ok(rec.fused.span && BigInt(rec.fused.span.positions) >= 1n);
53
+ // The harness attests with a fake document, so the attestation line is FALSE
54
+ // here; that is the harness, not the fused check, and it is reported on its
55
+ // own line. The fused line above stands regardless.
56
+ assert.notEqual(rec.lines.find((l) => l.name === "attestation")?.result, "TRUE");
57
+ const text = renderCheckText(report);
58
+ assert.match(text, /fused floor floor undetermined/);
59
+ assert.match(text, /fused span slot \d+ to commit \d+/);
60
+ });
61
+ it("the original beside the proof: rebuilt, fused line TRUE, the original is noted as such and not as unmatched", async () => {
62
+ const report = await checkIngest(await ingestEntries(await entries({ "proof.json": "trailer.proof.json", "original.txt": "original.txt" })));
63
+ const rec = report.recordings[0];
64
+ const fused = rec.lines.find((l) => l.name === "fused");
65
+ assert.equal(fused.result, "TRUE", fused.detail);
66
+ assert.equal(rec.fused.category, "FUSED_FROM_ORIGIN");
67
+ assert.equal(rec.fused.evidence, "original");
68
+ // The committed bytes are not in hand, so the file line stays UNDETERMINED: the
69
+ // original receives the ceiling, the fused bytes the interval.
70
+ assert.equal(rec.lines.find((l) => l.name === "file")?.result, "UNDETERMINED");
71
+ assert.ok(report.notes.some((n) => /original of a fused recording: original\.txt/.test(n)), report.notes.join(" | "));
72
+ assert.ok(!report.notes.some((n) => /matches no recording here: original\.txt/.test(n)), "the original is not called unmatched");
73
+ });
74
+ it("neither bytes present: fused line UNDETERMINED, never FALSE", async () => {
75
+ const report = await checkIngest(await ingestEntries(await entries({ "proof.json": "trailer.proof.json" })));
76
+ const rec = report.recordings[0];
77
+ const fused = rec.lines.find((l) => l.name === "fused");
78
+ assert.equal(fused.result, "UNDETERMINED");
79
+ assert.match(fused.detail, /neither the fused bytes nor the original/);
80
+ assert.equal(rec.fused.category, "NO_EVIDENCE");
81
+ assert.equal(rec.fused.evidence, null);
82
+ });
83
+ it("a file committed under a different slot than its commitment names: fused line FALSE", async () => {
84
+ const report = await checkIngest(await ingestEntries(await entries({ "proof.json": "wrong-slot.proof.json", "photo.bin": "fused-wrong-slot.bin" })));
85
+ const rec = report.recordings[0];
86
+ assert.equal(rec.lines.find((l) => l.name === "fused")?.result, "FALSE");
87
+ assert.equal(rec.fused.category, "INVALID_SLOT_COMMITMENT");
88
+ assert.equal(report.result, "FALSE");
89
+ });
90
+ it("an unregistered placement: UNDETERMINED with the placement named", async () => {
91
+ const report = await checkIngest(await ingestEntries(await entries({ "proof.json": "unregistered.proof.json", "photo.bin": "fused-unregistered.bin" })));
92
+ const rec = report.recordings[0];
93
+ const fused = rec.lines.find((l) => l.name === "fused");
94
+ assert.equal(fused.result, "UNDETERMINED");
95
+ assert.match(fused.detail, /xmp\/9/);
96
+ });
97
+ it("an ordinary recording has no fused line and no fused block", async () => {
98
+ const report = await checkIngest(await ingestEntries(await entries({ "proof.json": "recorded.proof.json", "original.txt": "original.txt" })));
99
+ const rec = report.recordings[0];
100
+ assert.equal(rec.lines.find((l) => l.name === "fused"), undefined);
101
+ assert.equal(rec.fused, undefined);
102
+ assert.equal(rec.lines.find((l) => l.name === "file")?.result, "TRUE");
103
+ });
104
+ it("container/1 from the PNG alone and produced/1 from its own bytes", async () => {
105
+ const a = await checkIngest(await ingestEntries(await entries({ "proof.json": "container.proof.json", "image.png": "image.png" })));
106
+ assert.equal(a.recordings[0].fused.category, "FUSED_FROM_ORIGIN");
107
+ assert.equal(a.recordings[0].fused.placement, "container/1");
108
+ const b = await checkIngest(await ingestEntries(await entries({ "proof.json": "produced-origin.proof.json", "record.json": "produced-origin.json" })));
109
+ assert.equal(b.recordings[0].fused.category, "FUSED_DIRECT");
110
+ assert.equal(b.recordings[0].fused.placement, "produced/1");
111
+ });
112
+ it("disk and memory ingests of the same fused bundle give byte-identical reports", async () => {
113
+ const dir = await mkdtemp(join(tmpdir(), "bitgraph-fuse-check-"));
114
+ try {
115
+ await mkdir(dir, { recursive: true });
116
+ await writeFile(join(dir, "proof.json"), await read("trailer.proof.json"));
117
+ await writeFile(join(dir, "original.txt"), await read("original.txt"));
118
+ const fromDisk = await checkIngest(await ingestBundle(dir));
119
+ const fromMemory = await checkIngest(await ingestEntries(await entries({ "proof.json": "trailer.proof.json", "original.txt": "original.txt" })));
120
+ assert.equal(serializeCheckReport(fromDisk), serializeCheckReport(fromMemory));
121
+ assert.equal(fromDisk.recordings[0].fused.category, "FUSED_FROM_ORIGIN");
122
+ }
123
+ finally {
124
+ await rm(dir, { recursive: true, force: true });
125
+ }
126
+ });
127
+ });
128
+ describe("fusedFloor", () => {
129
+ const bound = (anchorProofHash, blockNumber, timestamp) => ({
130
+ kind: "not-before", anchorProofHash, blockNumber, blockHash: "0x" + "ab".repeat(32), timestamp,
131
+ evidence: "counter-order", weaker: true, basis: "block-hash-unpredictability", boundClass: "evidence", claim: "",
132
+ });
133
+ const anchor = (proofHash, counter) => ({ proofHash, chainId: "bitgraph:main", counter, metadataCorroboration: "absent" });
134
+ const segment = (lower) => ({ lowerBounds: lower });
135
+ it("picks the last verified anchor whose counter precedes the SLOT, not the commit", () => {
136
+ const anchors = new Map([["a1", anchor("a1", "10")], ["a2", anchor("a2", "40")], ["a3", anchor("a3", "45")]]);
137
+ const seg = segment([bound("a1", "100", 1000), bound("a2", "200", 2000), bound("a3", "300", 3000)]);
138
+ // slot 42, commit 50: a3 (counter 45) precedes the commit but not the slot.
139
+ const floor = fusedFloor(seg, anchors, "42");
140
+ assert.equal(floor?.anchorProofHash, "a2");
141
+ assert.equal(floor?.blockNumber, "200");
142
+ assert.equal(floor?.evidence, "counter-order");
143
+ });
144
+ it("no anchor before the slot: null (UNDET), never a following anchor", () => {
145
+ const anchors = new Map([["a3", anchor("a3", "45")]]);
146
+ assert.equal(fusedFloor(segment([bound("a3", "300", 3000)]), anchors, "42"), null);
147
+ assert.equal(fusedFloor(undefined, anchors, "42"), null);
148
+ assert.equal(fusedFloor(segment([bound("a3", "300", 3000)]), anchors, undefined), null);
149
+ });
150
+ it("a slot off the anchored chain has no floor", () => {
151
+ const anchors = new Map([["a1", anchor("a1", "10")]]);
152
+ assert.equal(fusedFloor(segment([bound("a1", "100", 1000)]), anchors, "42", "global"), null);
153
+ assert.equal(fusedFloor(segment([bound("a1", "100", 1000)]), anchors, "42", "bitgraph:main")?.anchorProofHash, "a1");
154
+ });
155
+ it("only not-before bounds count; not-after bounds are never a ceiling", () => {
156
+ const anchors = new Map([["a9", anchor("a9", "5")]]);
157
+ const after = { ...bound("a9", "900", 9000), kind: "not-after" };
158
+ assert.equal(fusedFloor(segment([after]), anchors, "42"), null);
159
+ });
160
+ });
161
+ //# 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;
@@ -1 +1 @@
1
- {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../src/check.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../src/check.ts"],"names":[],"mappings":"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"}
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 unmatched = audit.ingest.artifacts.filter((a) => a.matchedProofHashes.length === 0);
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) => {