@mikeargento/bitgraph-player 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/LICENSE +21 -0
- package/README.md +98 -0
- package/SPEC.md +234 -0
- package/dist/__tests__/cast-order.test.d.ts +2 -0
- package/dist/__tests__/cast-order.test.d.ts.map +1 -0
- package/dist/__tests__/cast-order.test.js +434 -0
- package/dist/__tests__/cast-order.test.js.map +1 -0
- package/dist/__tests__/evaluate-verdict.test.d.ts +2 -0
- package/dist/__tests__/evaluate-verdict.test.d.ts.map +1 -0
- package/dist/__tests__/evaluate-verdict.test.js +228 -0
- package/dist/__tests__/evaluate-verdict.test.js.map +1 -0
- package/dist/__tests__/fixtures.d.ts +63 -0
- package/dist/__tests__/fixtures.d.ts.map +1 -0
- package/dist/__tests__/fixtures.js +150 -0
- package/dist/__tests__/fixtures.js.map +1 -0
- package/dist/__tests__/logic-rule.test.d.ts +2 -0
- package/dist/__tests__/logic-rule.test.d.ts.map +1 -0
- package/dist/__tests__/logic-rule.test.js +190 -0
- package/dist/__tests__/logic-rule.test.js.map +1 -0
- package/dist/cast.d.ts +29 -0
- package/dist/cast.d.ts.map +1 -0
- package/dist/cast.js +85 -0
- package/dist/cast.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +130 -0
- package/dist/cli.js.map +1 -0
- package/dist/evaluate.d.ts +30 -0
- package/dist/evaluate.d.ts.map +1 -0
- package/dist/evaluate.js +177 -0
- package/dist/evaluate.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/logic.d.ts +16 -0
- package/dist/logic.d.ts.map +1 -0
- package/dist/logic.js +29 -0
- package/dist/logic.js.map +1 -0
- package/dist/order.d.ts +50 -0
- package/dist/order.d.ts.map +1 -0
- package/dist/order.js +290 -0
- package/dist/order.js.map +1 -0
- package/dist/rule.d.ts +40 -0
- package/dist/rule.d.ts.map +1 -0
- package/dist/rule.js +324 -0
- package/dist/rule.js.map +1 -0
- package/dist/types.d.ts +205 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +16 -0
- package/dist/types.js.map +1 -0
- package/dist/verdict.d.ts +14 -0
- package/dist/verdict.d.ts.map +1 -0
- package/dist/verdict.js +146 -0
- package/dist/verdict.js.map +1 -0
- package/package.json +42 -0
- package/src/__tests__/cast-order.test.ts +473 -0
- package/src/__tests__/evaluate-verdict.test.ts +266 -0
- package/src/__tests__/fixtures.ts +206 -0
- package/src/__tests__/logic-rule.test.ts +221 -0
- package/src/cast.ts +120 -0
- package/src/cli.ts +143 -0
- package/src/evaluate.ts +238 -0
- package/src/index.ts +38 -0
- package/src/logic.ts +39 -0
- package/src/order.ts +380 -0
- package/src/rule.ts +340 -0
- package/src/types.ts +224 -0
- package/src/verdict.ts +159 -0
package/src/verdict.ts
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// Copyright (c) 2024-2026 Mike Argento. Licensed under the MIT License. See LICENSE.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Verdict construction and serialization.
|
|
5
|
+
*
|
|
6
|
+
* Determinism is the product claim, so this module is where it is
|
|
7
|
+
* enforced: no wall-clock reads, no random values, no filesystem paths,
|
|
8
|
+
* and a fixed field order. Two machines evaluating the same rule bytes
|
|
9
|
+
* over the same bundle contents must produce byte-identical verdicts.
|
|
10
|
+
*
|
|
11
|
+
* The verdict labels its own trust boundary. `derived` is what BitGraph
|
|
12
|
+
* established; `declared` is what a named party asserted and Player took
|
|
13
|
+
* on faith — including the closed world itself, which nothing in BitGraph
|
|
14
|
+
* establishes: "there is no cancellation before approval" and "among the
|
|
15
|
+
* recordings you told me to consider, none precedes approval" are
|
|
16
|
+
* different claims, and the closed-world entry is what keeps the verdict
|
|
17
|
+
* from passing the first off as the second.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { readFileSync } from "node:fs";
|
|
21
|
+
import type { AuditResult } from "@mikeargento/bitgraph-audit";
|
|
22
|
+
import type { Evaluation } from "./evaluate.js";
|
|
23
|
+
import { normalizeDigest } from "./rule.js";
|
|
24
|
+
import type { DeclaredEntry, Resolution, Rule, Verdict } from "./types.js";
|
|
25
|
+
|
|
26
|
+
let cachedVersion: string | undefined;
|
|
27
|
+
|
|
28
|
+
/** The player package's own version, read once from its package.json. */
|
|
29
|
+
export function playerVersion(): string {
|
|
30
|
+
if (cachedVersion === undefined) {
|
|
31
|
+
const raw = readFileSync(new URL("../package.json", import.meta.url), "utf8");
|
|
32
|
+
cachedVersion = (JSON.parse(raw) as { version: string }).version;
|
|
33
|
+
}
|
|
34
|
+
return cachedVersion;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function resolutionLabel(res: Resolution): string {
|
|
38
|
+
switch (res.kind) {
|
|
39
|
+
case "resolved":
|
|
40
|
+
return "resolved";
|
|
41
|
+
case "absent":
|
|
42
|
+
return res.optional ? "absent-optional" : "absent-required";
|
|
43
|
+
case "ambiguous":
|
|
44
|
+
return "ambiguous";
|
|
45
|
+
case "invalid":
|
|
46
|
+
return "invalid";
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function buildVerdict(
|
|
51
|
+
rule: Rule,
|
|
52
|
+
ruleSha256Hex: string,
|
|
53
|
+
resolutions: Map<string, Resolution>,
|
|
54
|
+
evaluation: Evaluation,
|
|
55
|
+
audit: AuditResult
|
|
56
|
+
): Verdict {
|
|
57
|
+
// Cast summary, in declaration order. Null prototype so a role named
|
|
58
|
+
// "__proto__" serializes as an ordinary key.
|
|
59
|
+
const cast: Verdict["cast"] = Object.create(null) as Verdict["cast"];
|
|
60
|
+
for (const [role, entry] of Object.entries(rule.cast)) {
|
|
61
|
+
const res = resolutions.get(role) as Resolution;
|
|
62
|
+
const summary: Verdict["cast"][string] = {
|
|
63
|
+
digestB64: normalizeDigest(entry.digest) as string,
|
|
64
|
+
resolution: resolutionLabel(res),
|
|
65
|
+
};
|
|
66
|
+
if (res.kind === "resolved") {
|
|
67
|
+
summary.proofHash = res.proof.proofHash;
|
|
68
|
+
if (res.proof.epochId !== undefined) summary.epochId = res.proof.epochId;
|
|
69
|
+
summary.chainId = res.proof.chainId;
|
|
70
|
+
if (res.proof.counter !== undefined) summary.counter = res.proof.counter;
|
|
71
|
+
if (res.proof.slotCounter !== undefined) summary.slotCounter = res.proof.slotCounter;
|
|
72
|
+
}
|
|
73
|
+
cast[role] = summary;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Declared assertions, in cast declaration order, closed world last.
|
|
77
|
+
const declared: DeclaredEntry[] = [];
|
|
78
|
+
for (const [role, entry] of Object.entries(rule.cast)) {
|
|
79
|
+
if (entry.means !== undefined) {
|
|
80
|
+
declared.push({
|
|
81
|
+
assertion: "means",
|
|
82
|
+
role,
|
|
83
|
+
verifiedHere: false,
|
|
84
|
+
means: entry.means,
|
|
85
|
+
digestB64: normalizeDigest(entry.digest) as string,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
for (const [role, entry] of Object.entries(rule.cast)) {
|
|
90
|
+
if (entry.at !== undefined) {
|
|
91
|
+
declared.push({
|
|
92
|
+
assertion: "pinned-occurrence",
|
|
93
|
+
role,
|
|
94
|
+
verifiedHere: false,
|
|
95
|
+
at: entry.at,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
for (const [role, entry] of Object.entries(rule.cast)) {
|
|
100
|
+
if (entry.signedBy !== undefined) {
|
|
101
|
+
declared.push({
|
|
102
|
+
assertion: "signedBy",
|
|
103
|
+
role,
|
|
104
|
+
verifiedHere: false,
|
|
105
|
+
evidence: entry.signedBy,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
declared.push({
|
|
110
|
+
assertion: "closed-world",
|
|
111
|
+
verifiedHere: false,
|
|
112
|
+
castSize: Object.keys(rule.cast).length,
|
|
113
|
+
recordingsInBundle: audit.ingest.counts.observed,
|
|
114
|
+
claim:
|
|
115
|
+
"Absence is asserted only among the declared cast. Nothing in BitGraph establishes that the declared cast is complete; that completeness is the rule author's assertion.",
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const verdict: Verdict = {
|
|
119
|
+
verdict: "bitgraph-player-verdict/1",
|
|
120
|
+
result: evaluation.result,
|
|
121
|
+
rule: { id: rule.id, sha256: ruleSha256Hex },
|
|
122
|
+
cast,
|
|
123
|
+
derived: evaluation.steps,
|
|
124
|
+
declared,
|
|
125
|
+
evaluator: { name: "@mikeargento/bitgraph-player", version: playerVersion() },
|
|
126
|
+
network: "none",
|
|
127
|
+
};
|
|
128
|
+
if (rule.then !== undefined) verdict.then = rule.then;
|
|
129
|
+
if (evaluation.weakestEvidence !== undefined) {
|
|
130
|
+
verdict.weakestEvidence = evaluation.weakestEvidence;
|
|
131
|
+
}
|
|
132
|
+
return verdict;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Deterministic bytes. Fixed key order (construction order), two-space
|
|
137
|
+
* indent, trailing newline. No timestamp: a `generatedAt` field would
|
|
138
|
+
* break byte-identical reproduction, which is the entire product claim.
|
|
139
|
+
* If a run timestamp is wanted it belongs in a sidecar, never here.
|
|
140
|
+
*/
|
|
141
|
+
export function serializeVerdict(verdict: Verdict): string {
|
|
142
|
+
// Emit top-level fields in the canonical order regardless of
|
|
143
|
+
// construction history, so optional fields land in a fixed place.
|
|
144
|
+
const ordered: Record<string, unknown> = {
|
|
145
|
+
verdict: verdict.verdict,
|
|
146
|
+
result: verdict.result,
|
|
147
|
+
rule: verdict.rule,
|
|
148
|
+
};
|
|
149
|
+
if (verdict.then !== undefined) ordered["then"] = verdict.then;
|
|
150
|
+
if (verdict.weakestEvidence !== undefined) {
|
|
151
|
+
ordered["weakestEvidence"] = verdict.weakestEvidence;
|
|
152
|
+
}
|
|
153
|
+
ordered["cast"] = verdict.cast;
|
|
154
|
+
ordered["derived"] = verdict.derived;
|
|
155
|
+
ordered["declared"] = verdict.declared;
|
|
156
|
+
ordered["evaluator"] = verdict.evaluator;
|
|
157
|
+
ordered["network"] = verdict.network;
|
|
158
|
+
return JSON.stringify(ordered, null, 2) + "\n";
|
|
159
|
+
}
|