@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.
Files changed (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +98 -0
  3. package/SPEC.md +234 -0
  4. package/dist/__tests__/cast-order.test.d.ts +2 -0
  5. package/dist/__tests__/cast-order.test.d.ts.map +1 -0
  6. package/dist/__tests__/cast-order.test.js +434 -0
  7. package/dist/__tests__/cast-order.test.js.map +1 -0
  8. package/dist/__tests__/evaluate-verdict.test.d.ts +2 -0
  9. package/dist/__tests__/evaluate-verdict.test.d.ts.map +1 -0
  10. package/dist/__tests__/evaluate-verdict.test.js +228 -0
  11. package/dist/__tests__/evaluate-verdict.test.js.map +1 -0
  12. package/dist/__tests__/fixtures.d.ts +63 -0
  13. package/dist/__tests__/fixtures.d.ts.map +1 -0
  14. package/dist/__tests__/fixtures.js +150 -0
  15. package/dist/__tests__/fixtures.js.map +1 -0
  16. package/dist/__tests__/logic-rule.test.d.ts +2 -0
  17. package/dist/__tests__/logic-rule.test.d.ts.map +1 -0
  18. package/dist/__tests__/logic-rule.test.js +190 -0
  19. package/dist/__tests__/logic-rule.test.js.map +1 -0
  20. package/dist/cast.d.ts +29 -0
  21. package/dist/cast.d.ts.map +1 -0
  22. package/dist/cast.js +85 -0
  23. package/dist/cast.js.map +1 -0
  24. package/dist/cli.d.ts +3 -0
  25. package/dist/cli.d.ts.map +1 -0
  26. package/dist/cli.js +130 -0
  27. package/dist/cli.js.map +1 -0
  28. package/dist/evaluate.d.ts +30 -0
  29. package/dist/evaluate.d.ts.map +1 -0
  30. package/dist/evaluate.js +177 -0
  31. package/dist/evaluate.js.map +1 -0
  32. package/dist/index.d.ts +22 -0
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/index.js +9 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/logic.d.ts +16 -0
  37. package/dist/logic.d.ts.map +1 -0
  38. package/dist/logic.js +29 -0
  39. package/dist/logic.js.map +1 -0
  40. package/dist/order.d.ts +50 -0
  41. package/dist/order.d.ts.map +1 -0
  42. package/dist/order.js +290 -0
  43. package/dist/order.js.map +1 -0
  44. package/dist/rule.d.ts +40 -0
  45. package/dist/rule.d.ts.map +1 -0
  46. package/dist/rule.js +324 -0
  47. package/dist/rule.js.map +1 -0
  48. package/dist/types.d.ts +205 -0
  49. package/dist/types.d.ts.map +1 -0
  50. package/dist/types.js +16 -0
  51. package/dist/types.js.map +1 -0
  52. package/dist/verdict.d.ts +14 -0
  53. package/dist/verdict.d.ts.map +1 -0
  54. package/dist/verdict.js +146 -0
  55. package/dist/verdict.js.map +1 -0
  56. package/package.json +42 -0
  57. package/src/__tests__/cast-order.test.ts +473 -0
  58. package/src/__tests__/evaluate-verdict.test.ts +266 -0
  59. package/src/__tests__/fixtures.ts +206 -0
  60. package/src/__tests__/logic-rule.test.ts +221 -0
  61. package/src/cast.ts +120 -0
  62. package/src/cli.ts +143 -0
  63. package/src/evaluate.ts +238 -0
  64. package/src/index.ts +38 -0
  65. package/src/logic.ts +39 -0
  66. package/src/order.ts +380 -0
  67. package/src/rule.ts +340 -0
  68. package/src/types.ts +224 -0
  69. 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
+ }