@mikeargento/bitgraph-player 0.14.0 → 0.16.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/LICENSE +1 -1
- package/README.md +32 -18
- package/dist/__tests__/base-floor-check.test.d.ts +2 -0
- package/dist/__tests__/base-floor-check.test.d.ts.map +1 -0
- package/dist/__tests__/base-floor-check.test.js +163 -0
- package/dist/__tests__/base-floor-check.test.js.map +1 -0
- package/dist/__tests__/cast-order.test.js +1 -1
- package/dist/__tests__/cast-order.test.js.map +1 -1
- package/dist/__tests__/check.test.js +2 -2
- package/dist/__tests__/check.test.js.map +1 -1
- package/dist/__tests__/evaluate-verdict.test.js +1 -1
- package/dist/__tests__/evaluate-verdict.test.js.map +1 -1
- package/dist/__tests__/fixtures.js +1 -1
- package/dist/__tests__/fixtures.js.map +1 -1
- package/dist/__tests__/fuse-check.test.js +2 -2
- package/dist/__tests__/fuse-check.test.js.map +1 -1
- package/dist/__tests__/init-play.test.js +1 -1
- package/dist/__tests__/init-play.test.js.map +1 -1
- package/dist/__tests__/logic-rule.test.js +1 -1
- package/dist/__tests__/logic-rule.test.js.map +1 -1
- package/dist/__tests__/sig-v2.test.js +1 -1
- package/dist/__tests__/sig-v2.test.js.map +1 -1
- package/dist/cast.js +1 -1
- package/dist/cast.js.map +1 -1
- package/dist/check.d.ts +58 -9
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +102 -32
- package/dist/check.js.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/evaluate.js +1 -1
- package/dist/evaluate.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/init.js +1 -1
- package/dist/init.js.map +1 -1
- package/dist/logic.js +1 -1
- package/dist/logic.js.map +1 -1
- package/dist/order.d.ts +4 -1
- package/dist/order.d.ts.map +1 -1
- package/dist/order.js +10 -1
- package/dist/order.js.map +1 -1
- package/dist/play.js +1 -1
- package/dist/play.js.map +1 -1
- package/dist/rule.js +1 -1
- package/dist/rule.js.map +1 -1
- package/dist/sig.js +1 -1
- package/dist/sig.js.map +1 -1
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/dist/verdict.d.ts +1 -1
- package/dist/verdict.js +2 -2
- package/dist/verdict.js.map +1 -1
- package/dist-web/verify.html +3 -3
- package/package.json +15 -7
- package/src/__tests__/base-floor-check.test.ts +187 -0
- package/src/__tests__/cast-order.test.ts +1 -1
- package/src/__tests__/check.test.ts +2 -2
- package/src/__tests__/evaluate-verdict.test.ts +1 -1
- package/src/__tests__/fixtures.ts +1 -1
- package/src/__tests__/fuse-check.test.ts +2 -2
- package/src/__tests__/init-play.test.ts +1 -1
- package/src/__tests__/logic-rule.test.ts +1 -1
- package/src/__tests__/sig-v2.test.ts +1 -1
- package/src/cast.ts +1 -1
- package/src/check.ts +167 -40
- package/src/cli.ts +1 -1
- package/src/evaluate.ts +1 -1
- package/src/index.ts +1 -1
- package/src/init.ts +1 -1
- package/src/logic.ts +1 -1
- package/src/order.ts +13 -2
- package/src/play.ts +1 -1
- package/src/rule.ts +1 -1
- package/src/sig.ts +1 -1
- package/src/types.ts +1 -1
- package/src/verdict.ts +2 -2
package/package.json
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikeargento/bitgraph-player",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Deterministic evaluation of causal rules over BitGraph proof bundles: a pure function from (rule, verified evidence) to a reproducible verdict.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"bitgraph",
|
|
7
|
+
"rules",
|
|
8
|
+
"deterministic",
|
|
9
|
+
"evidence",
|
|
10
|
+
"ai-audit",
|
|
11
|
+
"verification"
|
|
12
|
+
],
|
|
5
13
|
"license": "MIT",
|
|
6
14
|
"type": "module",
|
|
7
15
|
"main": "./dist/index.js",
|
|
8
16
|
"types": "./dist/index.d.ts",
|
|
9
17
|
"bin": {
|
|
10
|
-
"bitgraph-play": "
|
|
18
|
+
"bitgraph-play": "dist/cli.js"
|
|
11
19
|
},
|
|
12
20
|
"exports": {
|
|
13
21
|
".": {
|
|
@@ -20,23 +28,23 @@
|
|
|
20
28
|
"dist-web",
|
|
21
29
|
"src",
|
|
22
30
|
"SPEC.md",
|
|
23
|
-
|
|
31
|
+
"README.md",
|
|
24
32
|
"LICENSE"
|
|
25
33
|
],
|
|
26
34
|
"scripts": {
|
|
27
35
|
"build": "tsc",
|
|
28
36
|
"prepack": "tsc && npm run build:web",
|
|
29
37
|
"typecheck": "tsc --noEmit",
|
|
30
|
-
"test": "tsc && node --test
|
|
31
|
-
"build:web": "node scripts/build-verify-html.mjs
|
|
38
|
+
"test": "tsc && node --test dist/__tests__/*.test.js",
|
|
39
|
+
"build:web": "node scripts/build-verify-html.mjs",
|
|
32
40
|
"typecheck:web": "tsc -p web/tsconfig.json"
|
|
33
41
|
},
|
|
34
42
|
"engines": {
|
|
35
43
|
"node": ">=20"
|
|
36
44
|
},
|
|
37
45
|
"dependencies": {
|
|
38
|
-
"@mikeargento/bitgraph-audit": "^0.
|
|
39
|
-
"@mikeargento/bitgraph-verify": "^1.
|
|
46
|
+
"@mikeargento/bitgraph-audit": "^0.10.0",
|
|
47
|
+
"@mikeargento/bitgraph-verify": "^1.17.0"
|
|
40
48
|
},
|
|
41
49
|
"devDependencies": {
|
|
42
50
|
"@noble/curves": "^2.3.0",
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// Copyright (c) 2024-2026 Argento Computing Inc. Licensed under the MIT License. See LICENSE.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Base floors (enclave v10) in `check` and `compare`.
|
|
5
|
+
*
|
|
6
|
+
* From enclave v10 a recording signs its floor as a Base block
|
|
7
|
+
* (commit.slotFloor) instead of an Ethereum anchor. An audit that reads it
|
|
8
|
+
* reports a not-before segment bound with source "signed-floor" and chain
|
|
9
|
+
* "base", a TemporalAnalysis.signedFloors record per floor, and
|
|
10
|
+
* floorProblems. The Player reads those fields structurally (it still builds
|
|
11
|
+
* against an older audit), so these tests hand it AuditResult shapes with
|
|
12
|
+
* them, exactly as the fixtures elsewhere do. No ledger writes, no network.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { describe, it } from "node:test";
|
|
16
|
+
import * as assert from "node:assert/strict";
|
|
17
|
+
import type { AuditResult, ObservedProof, SegmentBound, TemporalSegment } from "@mikeargento/bitgraph-audit";
|
|
18
|
+
import { buildCheckReport, renderCheckText } from "../check.js";
|
|
19
|
+
import type { SignedFloorRecord } from "../check.js";
|
|
20
|
+
import { compare } from "../order.js";
|
|
21
|
+
import { digestFor, makeAudit, makeBound, proofHashOf } from "./fixtures.js";
|
|
22
|
+
|
|
23
|
+
const BASE_BLOCK = 52271417;
|
|
24
|
+
const BASE_TIME = 1791332181;
|
|
25
|
+
const BASE_HASH = "0x71d926aeae9847fcc15fad2d4de8871923f56862d42db7a7463e95a838f8f9b0";
|
|
26
|
+
|
|
27
|
+
function complete(audit: AuditResult, temporal: Record<string, unknown>): AuditResult {
|
|
28
|
+
return {
|
|
29
|
+
...audit,
|
|
30
|
+
anomalies: { anomalies: [], divergences: [], boundaryEntryPoints: [] },
|
|
31
|
+
authorities: { groups: [], anomalies: [], sharedSignersAcrossEpochs: [] },
|
|
32
|
+
anchors: { anchors: [], metadataOnlyProofHashes: [], findings: [] },
|
|
33
|
+
witnesses: { outcomes: [], findings: [] },
|
|
34
|
+
attestations: {
|
|
35
|
+
records: [],
|
|
36
|
+
findings: [],
|
|
37
|
+
counts: {
|
|
38
|
+
proofsWithDeclaredMeasurement: 0, proofsWithDocument: 0, documentsValidated: 0, documentsFailed: 0,
|
|
39
|
+
pcr0Matches: 0, pcr0Mismatches: 0, userDataBound: 0, userDataUnbound: 0,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
temporal: { ...audit.temporal, ...temporal },
|
|
43
|
+
} as unknown as AuditResult;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function baseBound(proofName: string, opts?: Partial<{ timeSource: "header" | "signed"; evidence: SegmentBound["evidence"] | "signed-floor"; blockNumber: number; timestamp: number }>): SegmentBound {
|
|
47
|
+
return {
|
|
48
|
+
...makeBound({
|
|
49
|
+
kind: "not-before",
|
|
50
|
+
anchorProofHash: proofHashOf(proofName),
|
|
51
|
+
blockHash: BASE_HASH,
|
|
52
|
+
blockNumber: String(opts?.blockNumber ?? BASE_BLOCK),
|
|
53
|
+
timestamp: opts?.timestamp ?? BASE_TIME,
|
|
54
|
+
evidence: (opts?.evidence ?? "signed-floor") as SegmentBound["evidence"],
|
|
55
|
+
claim: "fixture Base floor bound",
|
|
56
|
+
}),
|
|
57
|
+
source: "signed-floor",
|
|
58
|
+
chain: "base",
|
|
59
|
+
timeSource: opts?.timeSource ?? "header",
|
|
60
|
+
} as SegmentBound;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function floorRecord(proofName: string, opts?: Partial<SignedFloorRecord>): SignedFloorRecord {
|
|
64
|
+
return {
|
|
65
|
+
proofHash: proofHashOf(proofName),
|
|
66
|
+
chain: "base",
|
|
67
|
+
blockNumber: BASE_BLOCK,
|
|
68
|
+
blockHash: BASE_HASH,
|
|
69
|
+
blockTimestamp: BASE_TIME,
|
|
70
|
+
header: "checked",
|
|
71
|
+
headerPath: "base-floor/floor-header.json",
|
|
72
|
+
bound: "not-before",
|
|
73
|
+
...(opts ?? {}),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function segment(names: string[], lower: SegmentBound[], upper: SegmentBound[] = [], epochId = "E10"): TemporalSegment {
|
|
78
|
+
return {
|
|
79
|
+
partition: { publicKeyB64: "key-A", chainId: "bitgraph:main", epochId },
|
|
80
|
+
memberProofHashes: names.map(proofHashOf),
|
|
81
|
+
status: lower.length > 0 && upper.length > 0 ? "lower-bounded-with-following-anchor" : lower.length > 0 ? "lower-bounded" : upper.length > 0 ? "upper-bounded" : "ordered-but-unanchored",
|
|
82
|
+
lowerBounds: lower,
|
|
83
|
+
upperBounds: upper,
|
|
84
|
+
} as TemporalSegment;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function oneRecording(temporal: Record<string, unknown>, attribution?: Record<string, string>): AuditResult {
|
|
88
|
+
const audit = makeAudit({ proofs: [{ name: "r", digestB64: digestFor("r"), epochId: "E10", counter: "4", slotCounter: "3" }] });
|
|
89
|
+
if (attribution !== undefined) {
|
|
90
|
+
(audit.ingest.proofs[0]!.proof as unknown as Record<string, unknown>)["attribution"] = attribution;
|
|
91
|
+
}
|
|
92
|
+
return complete(audit, temporal);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
describe("check: a recording that signs a Base floor", () => {
|
|
96
|
+
it("is bounded after its Base block, with the header checked, and no upper bound", () => {
|
|
97
|
+
const report = buildCheckReport(oneRecording({ segments: [segment(["r"], [baseBound("r")])], signedFloors: [floorRecord("r")] }));
|
|
98
|
+
const rec = report.recordings[0]!;
|
|
99
|
+
assert.equal(rec.bounds.status, "lower-bounded");
|
|
100
|
+
assert.equal(rec.bounds.notBefore?.chain, "base");
|
|
101
|
+
assert.equal(rec.bounds.notBefore?.evidence, "signed-floor");
|
|
102
|
+
assert.equal(rec.bounds.notBefore?.blockNumber, String(BASE_BLOCK));
|
|
103
|
+
assert.equal(rec.bounds.notAfter, undefined);
|
|
104
|
+
assert.match(rec.bounds.detail, /after Base block 52271417 \(its floor, header checked in this bundle\)/);
|
|
105
|
+
assert.doesNotMatch(rec.bounds.detail, /Ethereum/);
|
|
106
|
+
assert.ok(report.notChecked.some((n) => /Base floor blocks are Base's own/.test(n)));
|
|
107
|
+
assert.doesNotMatch(renderCheckText(report), /[–—]/);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("without the header, says the block needs a Base lookup", () => {
|
|
111
|
+
const report = buildCheckReport(
|
|
112
|
+
oneRecording({ segments: [segment(["r"], [baseBound("r", { timeSource: "signed" })])], signedFloors: [floorRecord("r", { header: "not-carried" })] })
|
|
113
|
+
);
|
|
114
|
+
assert.match(report.recordings[0]!.bounds.detail, /confirming the block needs a Base lookup/);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("compares bounds across chains by time, never by block number", () => {
|
|
118
|
+
// Ethereum block 25,000,000 is later in time than this Base block,
|
|
119
|
+
// whose NUMBER is larger: the tightest not-before is the Ethereum one.
|
|
120
|
+
const eth = makeBound({ kind: "not-before", anchorProofHash: "anchor-eth", blockNumber: "25000000", timestamp: BASE_TIME + 600 });
|
|
121
|
+
const report = buildCheckReport(oneRecording({ segments: [segment(["r"], [baseBound("r"), eth])], signedFloors: [floorRecord("r")] }));
|
|
122
|
+
const nb = report.recordings[0]!.bounds.notBefore!;
|
|
123
|
+
assert.equal(nb.chain, undefined);
|
|
124
|
+
assert.equal(nb.blockNumber, "25000000");
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("a fuse/3 recording's fused floor is the Base block it signs", () => {
|
|
128
|
+
const report = buildCheckReport(
|
|
129
|
+
oneRecording({ segments: [segment(["r"], [baseBound("r")])], signedFloors: [floorRecord("r")] }, { name: "bitgraph-fuse/3", title: "trailer/1" })
|
|
130
|
+
);
|
|
131
|
+
const fused = report.recordings[0]!.fused!;
|
|
132
|
+
assert.equal(fused.floor?.chain, "base");
|
|
133
|
+
assert.equal(fused.floor?.evidence, "signed-floor");
|
|
134
|
+
assert.equal(fused.floor?.blockNumber, String(BASE_BLOCK));
|
|
135
|
+
assert.match(fused.floorDetail, /Base block #52271417 .*the Base block this recording signs as its floor/);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("a withheld floor bounds nothing and says why", () => {
|
|
139
|
+
const why = "the floor block is stamped after the attestation document was made";
|
|
140
|
+
const report = buildCheckReport(
|
|
141
|
+
oneRecording({ segments: [segment(["r"], [])], signedFloors: [floorRecord("r", { bound: "withheld", withheldReason: why })] }, { name: "bitgraph-fuse/3", title: "trailer/1" })
|
|
142
|
+
);
|
|
143
|
+
const rec = report.recordings[0]!;
|
|
144
|
+
assert.equal(rec.bounds.notBefore, undefined);
|
|
145
|
+
assert.match(rec.bounds.detail, /not used as a bound/);
|
|
146
|
+
assert.equal(rec.fused!.floor, null);
|
|
147
|
+
assert.match(rec.fused!.floorDetail, /not used as a bound/);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("a floor problem (a recording that signs two floors) is a contradiction: FALSE", () => {
|
|
151
|
+
const report = buildCheckReport(
|
|
152
|
+
oneRecording({
|
|
153
|
+
segments: [segment(["r"], [])],
|
|
154
|
+
floorProblems: [{ code: "floor-ambiguous", proofHash: proofHashOf("r"), message: "the proof signs two floors" }],
|
|
155
|
+
})
|
|
156
|
+
);
|
|
157
|
+
assert.equal(report.result, "FALSE");
|
|
158
|
+
assert.ok(report.contradictions.some((c) => c.detail.startsWith("floor-ambiguous")));
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe("compare: a Base floor orders no epoch", () => {
|
|
163
|
+
it("an anchor's not-after in one epoch and a later Base floor in another answer unordered", () => {
|
|
164
|
+
const base = makeAudit({
|
|
165
|
+
proofs: [
|
|
166
|
+
{ name: "old", digestB64: digestFor("old"), epochId: "E9", counter: "2" },
|
|
167
|
+
{ name: "new", digestB64: digestFor("new"), epochId: "E10", counter: "2", publicKeyB64: "key-B" },
|
|
168
|
+
],
|
|
169
|
+
partitions: [
|
|
170
|
+
{ epochId: "E9", members: ["old"] },
|
|
171
|
+
{ epochId: "E10", members: ["new"], publicKeyB64: "key-B" },
|
|
172
|
+
],
|
|
173
|
+
});
|
|
174
|
+
const anchorUpper = makeBound({ kind: "not-after", anchorProofHash: "anchor-9", blockNumber: "25000000", timestamp: 1_700_000_000 });
|
|
175
|
+
const audit = complete(base, {
|
|
176
|
+
segments: [segment(["old"], [], [anchorUpper], "E9"), segment(["new"], [baseBound("new")], [], "E10")],
|
|
177
|
+
signedFloors: [floorRecord("new")],
|
|
178
|
+
});
|
|
179
|
+
const byName = (n: string): ObservedProof => audit.ingest.proofs.find((p) => p.proofHash === proofHashOf(n))!;
|
|
180
|
+
assert.equal(compare(byName("old"), byName("new"), audit).relation, "unordered");
|
|
181
|
+
assert.equal(compare(byName("new"), byName("old"), audit).relation, "unordered");
|
|
182
|
+
// The same shape with an anchor not-before on the new side does order them (old behaviour kept).
|
|
183
|
+
const anchorLower = makeBound({ kind: "not-before", anchorProofHash: "anchor-10", blockNumber: "25000100", timestamp: 1_700_001_200 });
|
|
184
|
+
const anchored = complete(base, { segments: [segment(["old"], [], [anchorUpper], "E9"), segment(["new"], [anchorLower], [], "E10")] });
|
|
185
|
+
assert.equal(compare(byName("old"), byName("new"), anchored).relation, "before");
|
|
186
|
+
});
|
|
187
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Copyright (c) 2024-2026
|
|
1
|
+
// Copyright (c) 2024-2026 Argento Computing Inc. Licensed under the MIT License. See LICENSE.
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* `check` tests.
|
|
@@ -147,7 +147,7 @@ describe("check: real export fixture (disk)", () => {
|
|
|
147
147
|
it("recognizes the enclave v5 measurement from PINS.md", () => {
|
|
148
148
|
const enclave = report.recordings[0]!.lines.find((l) => l.name === "enclave")!;
|
|
149
149
|
assert.match(enclave.detail, /enclave v5/);
|
|
150
|
-
assert.equal(KNOWN_ENCLAVE_MEASUREMENTS.length,
|
|
150
|
+
assert.equal(KNOWN_ENCLAVE_MEASUREMENTS.length, 9);
|
|
151
151
|
});
|
|
152
152
|
|
|
153
153
|
it("serializes deterministically: no timestamps, no machine paths", async () => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Copyright (c) 2024-2026
|
|
1
|
+
// Copyright (c) 2024-2026 Argento Computing Inc. Licensed under the MIT License. See LICENSE.
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* `check` over fused recordings (profile bitgraph-fuse/1, working name).
|
|
@@ -55,7 +55,7 @@ describe("fused recordings in check", () => {
|
|
|
55
55
|
assert.equal(rec.fused!.floor, null);
|
|
56
56
|
assert.match(rec.fused!.floorDetail, /floor undetermined: no anchor precedes this slot in its epoch/);
|
|
57
57
|
assert.equal(rec.fused!.statements.length, 2);
|
|
58
|
-
assert.match(rec.fused!.statements[1]!, /
|
|
58
|
+
assert.match(rec.fused!.statements[1]!, /position \d+ was reserved and signed for them, and were committed no later than position \d+/);
|
|
59
59
|
assert.ok(rec.fused!.span && BigInt(rec.fused!.span.positions) >= 1n);
|
|
60
60
|
// The harness attests with a fake document, so the attestation line is FALSE
|
|
61
61
|
// here; that is the harness, not the fused check, and it is reported on its
|
package/src/cast.ts
CHANGED