@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
@@ -0,0 +1,190 @@
1
+ // Copyright (c) 2024-2026 Mike Argento. Licensed under the MIT License. See LICENSE.
2
+ import { strict as assert } from "node:assert";
3
+ import { test } from "node:test";
4
+ import { kleeneAll, kleeneAny, kleeneNot } from "../logic.js";
5
+ import { normalizeDigest, parseRule, RuleError } from "../rule.js";
6
+ const T = "TRUE";
7
+ const F = "FALSE";
8
+ const U = "UNDETERMINED";
9
+ const VALUES = [T, F, U];
10
+ // ---------------------------------------------------------------------------
11
+ // Kleene tables, exhaustively for the binary projections.
12
+ // ---------------------------------------------------------------------------
13
+ test("kleeneNot table", () => {
14
+ assert.equal(kleeneNot(T), F);
15
+ assert.equal(kleeneNot(F), T);
16
+ assert.equal(kleeneNot(U), U);
17
+ });
18
+ test("kleeneAll pairwise table", () => {
19
+ const expect = {
20
+ "TRUE,TRUE": T,
21
+ "TRUE,FALSE": F,
22
+ "TRUE,UNDETERMINED": U,
23
+ "FALSE,TRUE": F,
24
+ "FALSE,FALSE": F,
25
+ "FALSE,UNDETERMINED": F,
26
+ "UNDETERMINED,TRUE": U,
27
+ "UNDETERMINED,FALSE": F,
28
+ "UNDETERMINED,UNDETERMINED": U,
29
+ };
30
+ for (const a of VALUES) {
31
+ for (const b of VALUES) {
32
+ assert.equal(kleeneAll([a, b]), expect[`${a},${b}`], `all([${a}, ${b}])`);
33
+ }
34
+ }
35
+ assert.equal(kleeneAll([]), T, "empty conjunction is vacuously TRUE");
36
+ });
37
+ test("kleeneAny pairwise table", () => {
38
+ const expect = {
39
+ "TRUE,TRUE": T,
40
+ "TRUE,FALSE": T,
41
+ "TRUE,UNDETERMINED": T,
42
+ "FALSE,TRUE": T,
43
+ "FALSE,FALSE": F,
44
+ "FALSE,UNDETERMINED": U,
45
+ "UNDETERMINED,TRUE": T,
46
+ "UNDETERMINED,FALSE": U,
47
+ "UNDETERMINED,UNDETERMINED": U,
48
+ };
49
+ for (const a of VALUES) {
50
+ for (const b of VALUES) {
51
+ assert.equal(kleeneAny([a, b]), expect[`${a},${b}`], `any([${a}, ${b}])`);
52
+ }
53
+ }
54
+ assert.equal(kleeneAny([]), F, "empty disjunction is vacuously FALSE");
55
+ });
56
+ test("UNDETERMINED never launders into FALSE through not", () => {
57
+ // The single easiest way to make Player dishonest is a boolean evaluator.
58
+ assert.notEqual(kleeneNot(U), T);
59
+ assert.notEqual(kleeneNot(U), F);
60
+ });
61
+ // ---------------------------------------------------------------------------
62
+ // Digest normalization: four spellings, one canonical form.
63
+ // ---------------------------------------------------------------------------
64
+ test("normalizeDigest accepts hex, sha256:hex, base64, base64url identically", () => {
65
+ const bytes = Buffer.alloc(32, 7);
66
+ const hex = bytes.toString("hex");
67
+ const b64 = bytes.toString("base64");
68
+ const b64url = bytes.toString("base64url");
69
+ assert.equal(normalizeDigest(hex), b64);
70
+ assert.equal(normalizeDigest(`sha256:${hex}`), b64);
71
+ assert.equal(normalizeDigest(b64), b64);
72
+ assert.equal(normalizeDigest(b64url), b64);
73
+ });
74
+ test("normalizeDigest rejects malformed input", () => {
75
+ assert.equal(normalizeDigest("not-a-digest"), undefined);
76
+ assert.equal(normalizeDigest("sha256:abcd"), undefined);
77
+ assert.equal(normalizeDigest(""), undefined);
78
+ });
79
+ // ---------------------------------------------------------------------------
80
+ // Rule parsing.
81
+ // ---------------------------------------------------------------------------
82
+ const DIGEST = Buffer.alloc(32, 1).toString("base64");
83
+ function baseRule() {
84
+ return {
85
+ rule: "bitgraph-player/1",
86
+ id: "test-rule",
87
+ cast: { a: { digest: DIGEST } },
88
+ world: "closed",
89
+ requires: { ordering: "hash-linked" },
90
+ claim: { exists: "a" },
91
+ };
92
+ }
93
+ test("a well-formed rule parses", () => {
94
+ const rule = parseRule(JSON.stringify(baseRule()));
95
+ assert.equal(rule.id, "test-rule");
96
+ assert.equal(rule.requires.ordering, "hash-linked");
97
+ });
98
+ test("a rule with no requires.ordering does not parse (mandatory trust floor)", () => {
99
+ const raw = baseRule();
100
+ delete raw["requires"];
101
+ assert.throws(() => parseRule(JSON.stringify(raw)), RuleError);
102
+ const raw2 = baseRule();
103
+ raw2["requires"] = {};
104
+ assert.throws(() => parseRule(JSON.stringify(raw2)), RuleError);
105
+ });
106
+ test("world is mandatory and must be closed", () => {
107
+ const raw = baseRule();
108
+ delete raw["world"];
109
+ assert.throws(() => parseRule(JSON.stringify(raw)), RuleError);
110
+ raw["world"] = "open";
111
+ assert.throws(() => parseRule(JSON.stringify(raw)), RuleError);
112
+ });
113
+ test("unknown fields are rejected at every level", () => {
114
+ const top = baseRule();
115
+ top["webhook"] = "https://example.com";
116
+ assert.throws(() => parseRule(JSON.stringify(top)), RuleError);
117
+ const castLevel = baseRule();
118
+ castLevel["cast"]["a"] = { digest: DIGEST, url: "x" };
119
+ assert.throws(() => parseRule(JSON.stringify(castLevel)), RuleError);
120
+ });
121
+ test("then accepts a label and nothing capable of causing an action", () => {
122
+ const ok = baseRule();
123
+ ok["then"] = { label: "release_payment" };
124
+ assert.equal(parseRule(JSON.stringify(ok)).then?.label, "release_payment");
125
+ // Constraint 3 enforced by the format: the first request for a webhook
126
+ // field is a request to make Player an enforcer.
127
+ const bad = baseRule();
128
+ bad["then"] = { label: "x", webhook: "https://example.com/pay" };
129
+ assert.throws(() => parseRule(JSON.stringify(bad)), RuleError);
130
+ });
131
+ test("claims must have exactly one known operator", () => {
132
+ const bad = baseRule();
133
+ bad["claim"] = { exists: "a", before: ["a", "a"] };
134
+ assert.throws(() => parseRule(JSON.stringify(bad)), RuleError);
135
+ const unknown = baseRule();
136
+ unknown["claim"] = { precedes: ["a", "b"] };
137
+ assert.throws(() => parseRule(JSON.stringify(unknown)), RuleError);
138
+ });
139
+ test("a claim referencing an undeclared role still parses (evaluates to UNDETERMINED, not a parse error)", () => {
140
+ const raw = baseRule();
141
+ raw["claim"] = { not: { before: ["ghost", "a"] } };
142
+ const rule = parseRule(JSON.stringify(raw));
143
+ assert.ok(rule);
144
+ });
145
+ test("malformed digests are parse errors", () => {
146
+ const raw = baseRule();
147
+ raw["cast"]["a"] = { digest: "zzzz" };
148
+ assert.throws(() => parseRule(JSON.stringify(raw)), RuleError);
149
+ });
150
+ test("a role named __proto__ is an ordinary own key, never a prototype assignment", () => {
151
+ // Built as JSON text: a JS object literal cannot even express an own
152
+ // "__proto__" key by assignment, which is exactly the trap the parser
153
+ // must not fall into on its side.
154
+ const json = `{"rule":"bitgraph-player/1","id":"t",` +
155
+ `"cast":{"a":{"digest":"${DIGEST}"},"__proto__":{"digest":"${DIGEST}"}},` +
156
+ `"world":"closed","requires":{"ordering":"hash-linked"},"claim":{"exists":"__proto__"}}`;
157
+ const rule = parseRule(json);
158
+ assert.ok(Object.keys(rule.cast).includes("__proto__"), "role survives as an own key");
159
+ assert.ok(Object.keys(rule.cast).includes("a"));
160
+ assert.equal(Object.getPrototypeOf(rule.cast), null);
161
+ });
162
+ test("pure-integer role names are rejected (declaration order would not survive)", () => {
163
+ const raw = baseRule();
164
+ raw["cast"]["2"] = { digest: DIGEST };
165
+ assert.throws(() => parseRule(JSON.stringify(raw)), RuleError);
166
+ });
167
+ test("non-canonical base64 spellings are rejected, not silently reinterpreted", () => {
168
+ // 43 chars + pad decodes to 32 bytes but carries nonzero trailing
169
+ // padding bits: Node's lenient decoder drops them, so accepting the
170
+ // spelling would collapse it into different bytes than written.
171
+ const nonCanonical = "A".repeat(42) + "B=";
172
+ assert.equal(normalizeDigest(nonCanonical), undefined);
173
+ const canonical = Buffer.alloc(32, 7).toString("base64");
174
+ assert.equal(normalizeDigest(canonical), canonical);
175
+ });
176
+ test("at pin must be exactly one of the two forms", () => {
177
+ const both = baseRule();
178
+ both["cast"]["a"] = {
179
+ digest: DIGEST,
180
+ at: { proofHash: "p", epochId: "e", counter: "1" },
181
+ };
182
+ assert.throws(() => parseRule(JSON.stringify(both)), RuleError);
183
+ const position = baseRule();
184
+ position["cast"]["a"] = {
185
+ digest: DIGEST,
186
+ at: { epochId: "e", counter: "12" },
187
+ };
188
+ assert.ok(parseRule(JSON.stringify(position)));
189
+ });
190
+ //# sourceMappingURL=logic-rule.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logic-rule.test.js","sourceRoot":"","sources":["../../src/__tests__/logic-rule.test.ts"],"names":[],"mappings":"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"}
package/dist/cast.d.ts ADDED
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Cast resolution: digest -> recording.
3
+ *
4
+ * A digest identifies bits; a BitGraph recording identifies an OCCURRENCE
5
+ * of those bits at a causal position. The same bits can hold many
6
+ * positions by design (the ledger's by-digest index is one entry per
7
+ * causal position), so a role must resolve to exactly one recording:
8
+ *
9
+ * exactly 1 verified match -> resolved
10
+ * 0 matches, optional -> absent (a definite fact under the closed world)
11
+ * 0 matches, required -> absent (evaluates to UNDETERMINED)
12
+ * 2+ matches, no pin -> ambiguous (UNDETERMINED, never a silent pick)
13
+ * 2+ matches, pin selects exactly 1 -> resolved
14
+ *
15
+ * Only proofs whose canonical verification passed (status "verified", at
16
+ * either tier) count as recordings. A file shaped like a proof that fails
17
+ * verification is noise, not evidence — but if matches exist and ALL fail
18
+ * verification, that is surfaced as invalid rather than silently treated
19
+ * as absence, because "the bundle contains only broken recordings of this
20
+ * digest" and "the bundle contains no recordings of this digest" are
21
+ * different situations and only one of them supports a closed-world
22
+ * absence claim.
23
+ */
24
+ import type { AuditResult } from "@mikeargento/bitgraph-audit";
25
+ import type { CastEntry, Resolution } from "./types.js";
26
+ export declare function resolveRole(role: string, entry: CastEntry, audit: AuditResult): Resolution;
27
+ /** Resolve every declared role. Deterministic: iterates the cast in declaration order. */
28
+ export declare function resolveCast(cast: Record<string, CastEntry>, audit: AuditResult): Map<string, Resolution>;
29
+ //# sourceMappingURL=cast.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cast.d.ts","sourceRoot":"","sources":["../src/cast.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAiB,MAAM,6BAA6B,CAAC;AAE9E,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAkBxD,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,WAAW,GAAG,UAAU,CA6D1F;AAED,0FAA0F;AAC1F,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,EAC/B,KAAK,EAAE,WAAW,GACjB,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAMzB"}
package/dist/cast.js ADDED
@@ -0,0 +1,85 @@
1
+ // Copyright (c) 2024-2026 Mike Argento. Licensed under the MIT License. See LICENSE.
2
+ import { decodeDigestBytes, normalizeDigest } from "./rule.js";
3
+ function matchesPin(proof, pin) {
4
+ if ("proofHash" in pin)
5
+ return proof.proofHash === pin.proofHash;
6
+ return proof.epochId === pin.epochId && proof.counter === pin.counter;
7
+ }
8
+ /**
9
+ * A recording counts when its canonical verification PASSED at either
10
+ * tier: "verified" is the full-tier pass (artifact bytes present and
11
+ * matching), "artifact-unavailable" is the integrity-tier pass (the proof
12
+ * verifies bytes-free; the digest is inside the signed body either way).
13
+ */
14
+ function verificationPassed(proof) {
15
+ const status = proof.verification?.status;
16
+ return status === "verified" || status === "artifact-unavailable";
17
+ }
18
+ export function resolveRole(role, entry, audit) {
19
+ const digestB64 = normalizeDigest(entry.digest);
20
+ if (digestB64 === undefined) {
21
+ // parseRule already rejects malformed digests; this guards direct API use.
22
+ return { kind: "invalid", role, reason: "digest is not a well-formed 32-byte SHA-256" };
23
+ }
24
+ const targetBytes = decodeDigestBytes(digestB64);
25
+ const anyMatch = [];
26
+ const verified = [];
27
+ for (const proof of audit.ingest.proofs) {
28
+ // Byte-level match: a hostile file may spell its digest any way it
29
+ // likes, and "only broken recordings exist" must not masquerade as
30
+ // "no recordings exist".
31
+ const proofDigest = decodeDigestBytes(proof.proof.artifact.digestB64);
32
+ if (proofDigest === undefined || !proofDigest.equals(targetBytes))
33
+ continue;
34
+ anyMatch.push(proof);
35
+ if (verificationPassed(proof))
36
+ verified.push(proof);
37
+ }
38
+ if (verified.length === 0) {
39
+ if (anyMatch.length > 0) {
40
+ return {
41
+ kind: "invalid",
42
+ role,
43
+ reason: `${anyMatch.length} recording(s) of this digest are present but none passed verification`,
44
+ };
45
+ }
46
+ return { kind: "absent", role, optional: entry.optional === true };
47
+ }
48
+ let candidates = verified;
49
+ if (entry.at !== undefined) {
50
+ const pin = entry.at;
51
+ candidates = verified.filter((p) => matchesPin(p, pin));
52
+ if (candidates.length === 0) {
53
+ return {
54
+ kind: "invalid",
55
+ role,
56
+ reason: `"at" pin matches none of the ${verified.length} verified recording(s) of this digest`,
57
+ };
58
+ }
59
+ }
60
+ if (candidates.length > 1) {
61
+ return {
62
+ kind: "ambiguous",
63
+ role,
64
+ matchCount: candidates.length,
65
+ candidates: candidates.map((p) => p.proofHash).sort(),
66
+ };
67
+ }
68
+ const proof = candidates[0];
69
+ return {
70
+ kind: "resolved",
71
+ role,
72
+ proof,
73
+ verificationTier: proof.verification?.tier ?? "integrity",
74
+ matchCount: verified.length,
75
+ };
76
+ }
77
+ /** Resolve every declared role. Deterministic: iterates the cast in declaration order. */
78
+ export function resolveCast(cast, audit) {
79
+ const out = new Map();
80
+ for (const [role, entry] of Object.entries(cast)) {
81
+ out.set(role, resolveRole(role, entry, audit));
82
+ }
83
+ return out;
84
+ }
85
+ //# sourceMappingURL=cast.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cast.js","sourceRoot":"","sources":["../src/cast.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env node
2
+ // Copyright (c) 2024-2026 Mike Argento. Licensed under the MIT License. See LICENSE.
3
+ /**
4
+ * bitgraph-play <rule.json> <bundle> [--out <file>] [--summary]
5
+ *
6
+ * Evaluates a bitgraph-player/1 rule against a proof bundle (directory,
7
+ * .tar, .tar.gz, or .tgz) and writes the verdict JSON to stdout (or
8
+ * --out). Offline: the audit pipeline and the evaluator make no network
9
+ * requests of any kind.
10
+ *
11
+ * Exit codes: 0 TRUE, 1 FALSE, 2 UNDETERMINED, 3 error.
12
+ * Diagnostics go to stderr; stdout carries verdict bytes only.
13
+ *
14
+ * The process exits by setting process.exitCode and returning, never by
15
+ * process.exit(): exiting early would truncate a verdict still draining
16
+ * to a pipe.
17
+ */
18
+ import { createHash } from "node:crypto";
19
+ import { readFileSync, writeFileSync } from "node:fs";
20
+ import { runAudit } from "@mikeargento/bitgraph-audit";
21
+ import { resolveCast } from "./cast.js";
22
+ import { evaluate } from "./evaluate.js";
23
+ import { parseRule, RuleError } from "./rule.js";
24
+ import { buildVerdict, serializeVerdict } from "./verdict.js";
25
+ function usage() {
26
+ process.stderr.write("usage: bitgraph-play <rule.json> <bundle> [--out <file>] [--summary]\n" +
27
+ " exit codes: 0 TRUE, 1 FALSE, 2 UNDETERMINED, 3 error\n");
28
+ return 3;
29
+ }
30
+ function printSummary(audit) {
31
+ const lines = [];
32
+ const c = audit.ingest.counts;
33
+ lines.push(`bundle: ${c.observed} observed proof(s), ${c.artifacts} artifact(s), ${c.witnesses} witness file(s)`);
34
+ for (const partition of audit.reconstruction.partitions) {
35
+ const key = partition.key;
36
+ lines.push(`partition epoch=${key.epochId ?? "(none)"} chain=${key.chainId} key=${key.publicKeyB64.slice(0, 12)}…: ` +
37
+ `${partition.memberProofHashes.length} proof(s) in ${partition.components.length} component(s)`);
38
+ }
39
+ const rel = audit.reconstruction.epochRelationships;
40
+ lines.push(`epochs: ${rel.epochs.length} observed, ${rel.orderedPairs.length} lineage-ordered pair(s), ` +
41
+ `${audit.temporal.anchorOrderedPairs.length} anchor-ordered pair(s)`);
42
+ lines.push(`unchained: ${audit.reconstruction.unchainedProofHashes.length}, unpartitioned: ${audit.reconstruction.unpartitionedProofHashes.length}`);
43
+ process.stderr.write(lines.join("\n") + "\n");
44
+ }
45
+ async function main() {
46
+ const args = process.argv.slice(2);
47
+ const positional = [];
48
+ let outFile;
49
+ let summary = false;
50
+ for (let i = 0; i < args.length; i++) {
51
+ const arg = args[i];
52
+ if (arg === "--out") {
53
+ const next = args[++i];
54
+ if (next === undefined || next.startsWith("-"))
55
+ return usage();
56
+ outFile = next;
57
+ }
58
+ else if (arg === "--summary") {
59
+ summary = true;
60
+ }
61
+ else if (arg.startsWith("-")) {
62
+ return usage();
63
+ }
64
+ else {
65
+ positional.push(arg);
66
+ }
67
+ }
68
+ if (positional.length !== 2)
69
+ return usage();
70
+ const [rulePath, bundlePath] = positional;
71
+ let ruleBytes;
72
+ try {
73
+ ruleBytes = readFileSync(rulePath);
74
+ }
75
+ catch (err) {
76
+ process.stderr.write(`error: cannot read rule file: ${err.message}\n`);
77
+ return 3;
78
+ }
79
+ const ruleSha256Hex = createHash("sha256").update(ruleBytes).digest("hex");
80
+ let rule;
81
+ try {
82
+ rule = parseRule(ruleBytes.toString("utf8"));
83
+ }
84
+ catch (err) {
85
+ if (err instanceof RuleError) {
86
+ process.stderr.write("error: invalid rule\n");
87
+ for (const issue of err.issues)
88
+ process.stderr.write(` - ${issue}\n`);
89
+ }
90
+ else {
91
+ process.stderr.write(`error: ${err.message}\n`);
92
+ }
93
+ return 3;
94
+ }
95
+ let audit;
96
+ try {
97
+ audit = await runAudit(bundlePath);
98
+ }
99
+ catch (err) {
100
+ process.stderr.write(`error: bundle audit failed: ${err.message}\n`);
101
+ return 3;
102
+ }
103
+ const resolutions = resolveCast(rule.cast, audit);
104
+ const evaluation = evaluate(rule, resolutions, audit);
105
+ const verdict = buildVerdict(rule, ruleSha256Hex, resolutions, evaluation, audit);
106
+ const bytes = serializeVerdict(verdict);
107
+ if (outFile !== undefined) {
108
+ writeFileSync(outFile, bytes);
109
+ }
110
+ else {
111
+ process.stdout.write(bytes);
112
+ }
113
+ if (summary)
114
+ printSummary(audit);
115
+ switch (evaluation.result) {
116
+ case "TRUE":
117
+ return 0;
118
+ case "FALSE":
119
+ return 1;
120
+ case "UNDETERMINED":
121
+ return 2;
122
+ }
123
+ }
124
+ main().then((code) => {
125
+ process.exitCode = code;
126
+ }, (err) => {
127
+ process.stderr.write(`error: ${err.message}\n`);
128
+ process.exitCode = 3;
129
+ });
130
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The evaluator: walks the claim tree over resolved recordings and the
3
+ * ordering adapter, under strong Kleene semantics.
4
+ *
5
+ * Deliberate properties:
6
+ *
7
+ * - FULL WALK, no short-circuit. Every sub-claim is evaluated and recorded
8
+ * so the verdict is a complete replayable trace, not just an outcome.
9
+ *
10
+ * - CLOSED WORLD, scoped to the cast. An absent OPTIONAL role is a
11
+ * definite absence within the declared universe: positive claims about
12
+ * it are FALSE, so negatives over it hold. An absent REQUIRED role, an
13
+ * ambiguous role, and a role the cast never declared are UNDETERMINED —
14
+ * the evidence does not decide, and saying FALSE would be a lie.
15
+ *
16
+ * - THE FLOOR GATES EVIDENCE, NOT POLARITY. When an ordering answer rests
17
+ * on a tier below requires.ordering, the answer is UNDETERMINED whether
18
+ * it would have been TRUE or FALSE: the rule author said not to trust
19
+ * that evidence in either direction.
20
+ */
21
+ import type { AuditResult } from "@mikeargento/bitgraph-audit";
22
+ import type { DerivedStep, EvidenceTier, Resolution, Rule, ThreeValued } from "./types.js";
23
+ export interface Evaluation {
24
+ result: ThreeValued;
25
+ steps: DerivedStep[];
26
+ /** Weakest tier among ordering answers that actually decided a step. */
27
+ weakestEvidence?: EvidenceTier;
28
+ }
29
+ export declare function evaluate(rule: Rule, resolutions: Map<string, Resolution>, audit: AuditResult): Evaluation;
30
+ //# sourceMappingURL=evaluate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evaluate.d.ts","sourceRoot":"","sources":["../src/evaluate.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAiB,MAAM,6BAA6B,CAAC;AAI9E,OAAO,KAAK,EAEV,WAAW,EACX,YAAY,EACZ,UAAU,EACV,IAAI,EACJ,WAAW,EACZ,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,WAAW,CAAC;IACpB,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,wEAAwE;IACxE,eAAe,CAAC,EAAE,YAAY,CAAC;CAChC;AAsLD,wBAAgB,QAAQ,CACtB,IAAI,EAAE,IAAI,EACV,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,EACpC,KAAK,EAAE,WAAW,GACjB,UAAU,CAUZ"}