@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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024-2026 Mike Argento
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,98 @@
1
+ # @mikeargento/bitgraph-player
2
+
3
+ Deterministic evaluation of causal rules over BitGraph proof bundles.
4
+
5
+ BitGraph records. Player executes.
6
+
7
+ A BitGraph proof bundle establishes facts: these bits were recorded at
8
+ these causal positions, bracketed by these Ethereum anchors. Player
9
+ evaluates a rule over those facts and produces a verdict anyone can
10
+ reproduce from the bundle alone — no network, no clock, no account, no
11
+ trust in the machine that ran it first.
12
+
13
+ evidence + rule = conclusion
14
+
15
+ ## Install
16
+
17
+ npm install @mikeargento/bitgraph-player
18
+
19
+ ## Use
20
+
21
+ bitgraph-play rule.json bundle/ > verdict.json
22
+
23
+ The bundle is a directory, `.tar`, or `.tar.gz` of BitGraph exports (the
24
+ folders the BitGraph Folder writes). Exit codes: `0` TRUE, `1` FALSE,
25
+ `2` UNDETERMINED, `3` error. `--out file` writes the verdict to a file;
26
+ `--summary` prints a bundle reconnaissance to stderr.
27
+
28
+ ## A rule
29
+
30
+ ```json
31
+ {
32
+ "rule": "bitgraph-player/1",
33
+ "id": "po-release-payment",
34
+ "cast": {
35
+ "purchase_order": { "digest": "sha256:…", "means": "PO-4471" },
36
+ "delivery": { "digest": "sha256:…" },
37
+ "approval": { "digest": "sha256:…" },
38
+ "cancellation": { "digest": "sha256:…", "optional": true }
39
+ },
40
+ "world": "closed",
41
+ "requires": { "ordering": "assumption-dependent" },
42
+ "claim": { "all": [
43
+ { "exists": "purchase_order" },
44
+ { "after": ["delivery", "purchase_order"] },
45
+ { "after": ["approval", "delivery"] },
46
+ { "not": { "before": ["cancellation", "approval"] } }
47
+ ]},
48
+ "then": { "label": "release_payment" }
49
+ }
50
+ ```
51
+
52
+ `cast` is everything taken on the rule author's word: which digest means
53
+ what, which occurrence is meant, who is said to have signed it. `claim`
54
+ is only what BitGraph derives. `then` is a label — no field of a rule can
55
+ cause an action. Player decides; whatever stakes money on a TRUE sits
56
+ above it.
57
+
58
+ ## Three answers, not two
59
+
60
+ A claim evaluates to `TRUE`, `FALSE`, or `UNDETERMINED`. Undetermined is
61
+ the honest answer wherever the evidence does not decide: recordings whose
62
+ order the ledger does not establish, a digest recorded more than once
63
+ with no pin selecting the occurrence, evidence below the rule's declared
64
+ trust floor (`requires.ordering`). An evaluator that always answers is
65
+ wrong on some input.
66
+
67
+ The verdict splits `derived` (BitGraph established this) from `declared`
68
+ (a named party asserted this), and its last declared entry is always the
69
+ closed world itself — absence is asserted only among the roles the author
70
+ declared, and nothing establishes that the cast is complete.
71
+
72
+ ## Determinism
73
+
74
+ Same rule bytes, same bundle contents, byte-identical verdict, on any
75
+ machine, years later. No timestamps, no paths, no randomness. The
76
+ normative semantics are in [SPEC.md](./SPEC.md); this package is the
77
+ reference implementation, and a conforming Player in any language must
78
+ agree with it.
79
+
80
+ ## API
81
+
82
+ ```ts
83
+ import { runAudit } from "@mikeargento/bitgraph-audit";
84
+ import {
85
+ parseRule, resolveCast, evaluate, buildVerdict, serializeVerdict,
86
+ } from "@mikeargento/bitgraph-player";
87
+
88
+ const rule = parseRule(ruleText);
89
+ const audit = await runAudit(bundlePath);
90
+ const resolutions = resolveCast(rule.cast, audit);
91
+ const evaluation = evaluate(rule, resolutions, audit);
92
+ const verdict = buildVerdict(rule, ruleSha256Hex, resolutions, evaluation, audit);
93
+ process.stdout.write(serializeVerdict(verdict));
94
+ ```
95
+
96
+ ## License
97
+
98
+ MIT. Verification and evaluation are permissionless by design.
package/SPEC.md ADDED
@@ -0,0 +1,234 @@
1
+ # BitGraph Player specification, version 1
2
+
3
+ This document is the normative semantics of `bitgraph-player/1` rules and
4
+ `bitgraph-player-verdict/1` verdicts. The TypeScript package in this
5
+ directory is the reference implementation; a conforming Player in any
6
+ language MUST reach the same result and, for the serialization defined in
7
+ section 7, the same bytes.
8
+
9
+ BitGraph records. Player executes. Player is a pure function:
10
+
11
+ evaluate(rule, verified evidence) -> verdict
12
+
13
+ The evidence is a BitGraph proof bundle as interpreted by the audit
14
+ pipeline (`bitgraph-audit`), which is the canonical interpretation of
15
+ bundle contents. Player makes no network requests, reads no clock, and
16
+ uses no randomness. Same rule bytes, same bundle contents, same verdict
17
+ bytes, on any machine, at any later time.
18
+
19
+ ## 1. Three-valued results
20
+
21
+ Every claim evaluates to `TRUE`, `FALSE`, or `UNDETERMINED`.
22
+
23
+ `UNDETERMINED` is the required answer wherever the evidence does not
24
+ decide. A conforming Player MUST NOT collapse it into `FALSE` or `TRUE`.
25
+ Composition uses strong Kleene connectives (the grammar in section 5
26
+ requires `all` and `any` to be non-empty, so the empty case never
27
+ arises):
28
+
29
+ all: FALSE if any operand is FALSE; else UNDETERMINED if any is
30
+ UNDETERMINED; else TRUE.
31
+ any: TRUE if any operand is TRUE; else UNDETERMINED if any is
32
+ UNDETERMINED; else FALSE.
33
+ not: swaps TRUE and FALSE; UNDETERMINED is unchanged.
34
+
35
+ Evaluation is a FULL WALK: every sub-claim is evaluated and recorded even
36
+ when an outcome is already forced, so the verdict is a complete trace.
37
+
38
+ ## 2. The rule file
39
+
40
+ A rule is a JSON object with exactly these top-level fields:
41
+
42
+ | field | required | value |
43
+ |---|---|---|
44
+ | `rule` | yes | the string `"bitgraph-player/1"` |
45
+ | `id` | yes | non-empty string naming the rule |
46
+ | `cast` | yes | object of at least one role (section 3) |
47
+ | `world` | yes | the string `"closed"` (the only defined value) |
48
+ | `requires` | yes | `{ "ordering": "hash-linked" \| "assumption-dependent" }` |
49
+ | `claim` | yes | a claim (section 5) |
50
+ | `then` | no | `{ "label": <non-empty string> }` |
51
+
52
+ Unknown fields anywhere in the file are errors. `requires.ordering` has
53
+ no default: a rule that does not declare its trust floor does not parse.
54
+ `then` is a label and nothing else; no field of a rule is capable of
55
+ causing an action. Player decides; it does not enforce.
56
+
57
+ Role names match `[A-Za-z0-9_.-]+` and MUST contain at least one
58
+ non-digit character: pure-integer names do not survive JSON object key
59
+ ordering identically across languages, and the verdict depends on
60
+ declaration order. Claim nesting is bounded at depth 32; deeper rules do
61
+ not parse. A claim MAY reference a role the cast does not declare; that
62
+ is not a parse error and evaluates per section 5.
63
+
64
+ ## 3. Cast
65
+
66
+ The cast is the trust boundary. Everything in it is DECLARED — taken on
67
+ the rule author's word and surfaced in the verdict — never derived.
68
+
69
+ Each role is an object:
70
+
71
+ | field | required | meaning |
72
+ |---|---|---|
73
+ | `digest` | yes | SHA-256 of the bits, as `sha256:<hex>`, bare hex, base64, or base64url; all normalize to one canonical form. Base64 forms MUST be canonical spellings (round-trip byte-exactly); spellings with nonzero trailing padding bits are parse errors, never silently reinterpreted |
74
+ | `means` | no | what the digest means as a business object; echoed, never interpreted |
75
+ | `at` | no | occurrence pin: `{ "proofHash": s }` or `{ "epochId": s, "counter": decimal-string }` |
76
+ | `signedBy` | no | external identity evidence; echoed verbatim, `verifiedHere: false`. SIGNED_BY is not a BitGraph primitive |
77
+ | `optional` | no | boolean, default false |
78
+
79
+ ### 3.1 Resolution
80
+
81
+ A digest identifies bits; a recording identifies an occurrence of those
82
+ bits at a causal position. A role resolves against the bundle's verified
83
+ recordings of its digest — proofs whose canonical verification PASSED:
84
+ audit status `"verified"` (the full-tier pass, artifact bytes present and
85
+ matching) or `"artifact-unavailable"` (the integrity-tier pass, verified
86
+ bytes-free; the digest is inside the signed body either way). Recordings
87
+ whose verification failed are not evidence. Digest matching is by decoded
88
+ BYTES, not string equality: a bundle proof may spell its digest in any
89
+ accepted form, and "only broken recordings exist" must not masquerade as
90
+ "no recordings exist".
91
+
92
+ | verified matches | pin | resolution |
93
+ |---|---|---|
94
+ | exactly 1 | — | resolved |
95
+ | 0, and matches exist unverified | — | invalid (evaluates UNDETERMINED): "only broken recordings" does not support an absence claim |
96
+ | 0, none at all, `optional: true` | — | definitely absent (a fact under the closed world) |
97
+ | 0, none at all, required | — | absent-required (evaluates UNDETERMINED) |
98
+ | 2 or more | none | ambiguous (evaluates UNDETERMINED; never a silent pick) |
99
+ | 2 or more | selects exactly 1 | resolved |
100
+ | any | selects 0 | invalid (evaluates UNDETERMINED) |
101
+ | any | selects 2+ | ambiguous |
102
+
103
+ ## 4. Ordering
104
+
105
+ An artifact's causal position is its COMMIT position. The slot is
106
+ evidence about creation, never an alternative position.
107
+
108
+ Commit counters obey the canonical strict-decimal grammar `[0-9]+` (no
109
+ sign, no whitespace, no base prefix, non-empty). Any other counter string
110
+ is NO counter evidence, exactly as the audit pipeline treats it — never
111
+ parsed leniently, never a crash.
112
+
113
+ Given two resolved recordings A and B, precedence is decided by the first
114
+ applicable row and no other source:
115
+
116
+ | situation | answer | basis | tier |
117
+ |---|---|---|---|
118
+ | same recording | not-before (contributes FALSE to `before`) | — | — |
119
+ | either recording holds no causal position (unchained) | unordered | — | — |
120
+ | same partition (signer key, epochId, chainId), a DIRECTED prevB64 path through observed proofs connects the two, in both directions (cycle) | unordered (anomaly) | — | — |
121
+ | same partition, a directed prevB64 path connects the two, and both counters parse with an order CONTRADICTING the path | unordered (anomaly) | — | — |
122
+ | same partition, a directed prevB64 path connects the two | the path decides the direction (ancestor precedes descendant) | `chain-link` | hash-linked |
123
+ | same partition, no path, both counters parse, distinct | strict order of commit counters as integers | `counter-order` | assumption-dependent |
124
+ | same partition, no path, counters missing/unparseable or equal | unordered | — | — |
125
+ | different partitions, same epochId string | unordered (a self-declared epochId is not epoch membership) | — | — |
126
+ | different epochs, a chain of hard epochLink edges COVERS the pair (section 4.2) | A before B along the chain | `epoch-lineage` | hash-linked, or assumption-dependent when predecessor-side coverage rests on counters |
127
+ | different epochs, lineage coverage holds in both directions | unordered (contradictory) | — | — |
128
+ | different epochs, a not-after anchor bound on A's segment strictly precedes a not-before anchor bound on B's segment, and NOT also the reverse | A before B | `anchor-bounds` | assumption-dependent |
129
+ | different epochs, strict anchor separation in both directions | unordered (contradictory) | — | — |
130
+ | otherwise | unordered | — | — |
131
+
132
+ Undirected co-membership in a prevB64 component is NOT chain-link
133
+ evidence: two fork branches share a component with no path between them,
134
+ and their relative order rests only on counter discipline — which the
135
+ fork itself demonstrates is broken.
136
+
137
+ Strict anchor precedence means strictly smaller block number (or, when a
138
+ block number is absent, strictly smaller verified witness timestamp).
139
+ Equality proves nothing: A precedes the anchor COMMIT that consumed block
140
+ N while B follows the MINING of block N, and the gap between mining and
141
+ commit is exactly where they could swap. The not-after side always rests
142
+ on the anchor-freshness assumption, which is why every `anchor-bounds`
143
+ answer is assumption-dependent. A `weaker` flag is carried when the
144
+ evidence additionally rests on counter-order rather than a chain-link
145
+ path (on either side of an anchor comparison, or for `counter-order`
146
+ itself).
147
+
148
+ ### 4.2 Lineage coverage
149
+
150
+ A hard epochLink edge proves one thing: the successor epoch's key was
151
+ created (at init) after the referenced predecessor proof existed. It is
152
+ applied at recording granularity, never as whole-epoch ordering by
153
+ epochId string — epochId is self-declared, and recordings the evidence
154
+ does not cover get nothing from it.
155
+
156
+ A chain of hard edges proves A-before-B when:
157
+
158
+ - the first edge's observed predecessor proof P is in A's partition,
159
+ and A is P itself or a directed prevB64 ancestor of P (hash-linked),
160
+ or A's parseable counter is strictly smaller than P's (downgrades the
161
+ whole answer to the assumption-dependent tier);
162
+ - each subsequent edge's observed predecessor proof is in the partition
163
+ the previous edge's via proof belongs to (free of assumptions: a
164
+ proof signed by that key postdates the key's creation);
165
+ - the last edge's via proof is in B's partition (B is covered by key:
166
+ it cannot predate its own key's creation).
167
+
168
+ ### 4.1 The evidence floor
169
+
170
+ `requires.ordering` is a floor on the tier of ordering evidence the rule
171
+ accepts. An ordering answer whose tier is below the floor makes the
172
+ predicate `UNDETERMINED` — in BOTH directions. The floor gates evidence,
173
+ not polarity: distrusted evidence may neither prove nor refute.
174
+
175
+ ## 5. Claims
176
+
177
+ | claim | semantics |
178
+ |---|---|
179
+ | `{ "exists": r }` | TRUE if r resolved; FALSE if r definitely absent; else UNDETERMINED |
180
+ | `{ "before": [x, y] }` | TRUE/FALSE from precedence of x's and y's recordings, gated by the floor; FALSE when either is definitely absent; UNDETERMINED when either is undeclared, absent-required, ambiguous, invalid, or the pair is unordered |
181
+ | `{ "after": [x, y] }` | exactly `before(y, x)` |
182
+ | `{ "between": [s, a, b] }` | `all(after(s, a), before(s, b))`, recorded as its two halves |
183
+ | `{ "all": [...] }`, `{ "any": [...] }`, `{ "not": c }` | strong Kleene (section 1) |
184
+
185
+ The closed world is scoped to the cast: definite absence exists only for
186
+ declared optional roles whose digest has no verified recording in the
187
+ bundle. Positive claims over a definitely absent role are FALSE, which is
188
+ what makes negatives over it hold. A role the cast never declared is
189
+ UNDETERMINED wherever it appears; the closed-world declaration cannot
190
+ speak about bits the author never named.
191
+
192
+ ## 6. The verdict
193
+
194
+ A verdict is a JSON object with fields in exactly this order:
195
+
196
+ verdict "bitgraph-player-verdict/1"
197
+ result TRUE | FALSE | UNDETERMINED
198
+ rule { id, sha256 } (sha256: lowercase hex of the rule file bytes)
199
+ then? echoed from the rule
200
+ weakestEvidence? weakest tier among ordering answers that decided a
201
+ step ("assumption-dependent" if any, else
202
+ "hash-linked"); absent when no ordering decided
203
+ anything
204
+ cast per role, in declaration order: digestB64,
205
+ resolution label, and when resolved: proofHash,
206
+ epochId?, chainId, counter?, slotCounter?
207
+ derived every evaluated step, in evaluation order — what
208
+ BitGraph established
209
+ declared what was taken on somebody's word — every `means`,
210
+ every `at` pin, every `signedBy`, each with
211
+ verifiedHere: false, and LAST the closed-world
212
+ entry: { assertion: "closed-world", verifiedHere:
213
+ false, castSize, recordingsInBundle, claim }
214
+ evaluator { name, version }
215
+ network "none"
216
+
217
+ The closed-world entry is mandatory. Nothing in BitGraph establishes that
218
+ a declared cast is complete; without this entry a verdict would pass
219
+ "there is no X before Y" off as established when what was established is
220
+ "among the recordings the author declared, no X precedes Y".
221
+
222
+ ## 7. Determinism
223
+
224
+ Serialization: UTF-8 JSON, two-space indent, key order as constructed
225
+ per section 6, one trailing newline. A verdict MUST NOT contain a run
226
+ timestamp, a filesystem path, a hostname, or any other machine- or
227
+ run-local value. Two evaluations of the same rule bytes over the same
228
+ bundle contents MUST be byte-identical.
229
+
230
+ ## 8. Exit codes (command-line Players)
231
+
232
+ 0 TRUE 1 FALSE 2 UNDETERMINED 3 error
233
+
234
+ Diagnostics go to stderr. Stdout carries verdict bytes only.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=cast-order.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cast-order.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/cast-order.test.ts"],"names":[],"mappings":""}