@mikeargento/bitgraph-player
v0.18.1
Published
Deterministic rules over verified BitGraph proofs: the same rule and the same proofs always give the same verdict.
Maintainers
Readme
@mikeargento/bitgraph-player
Deterministic evaluation of causal rules over BitGraph proof bundles.
BitGraph records. Player evaluates.
A BitGraph proof bundle establishes facts: these bits were recorded at these causal positions, tied to a public timeline. Player evaluates a rule over those facts and produces a verdict anyone can reproduce from the bundle alone: no network, no clock, no account, no trust in the machine that ran it first.
evidence + rule = conclusionInstall
npm install @mikeargento/bitgraph-playerUse
bitgraph-play rule.json bundle/ > verdict.jsonThe bundle is a directory, .tar, or .tar.gz of BitGraph proofs:
proof.json files beside the files they record, Frame files
(bitgraph-fuse/1), Ethereum anchor witnesses for proofs made before
the Base floor. This build reads through
@mikeargento/bitgraph-audit 0.7.x, which does not read the export
(bitgraph-export/1) a tree/1 BitGraph is kept with. So Player does not
read exports yet; a file with its export is checked with
npx -p @mikeargento/bitgraph-sdk bitgraph verify <file> <export.json>
or with bitgraph-audit 0.9.0. Exit codes: 0 TRUE, 1 FALSE,
2 UNDETERMINED, 3 error. --out file writes the verdict to a file;
--summary prints a bundle reconnaissance to stderr.
Check a bundle
bitgraph-play check recordings/
bitgraph-play check proof.json photo.jpg --jsonNo rule needed. check reads a bundle (a folder or archive, or a
proof.json beside the file it records) and says, offline, what the bundle
establishes about each recording in it: that the file in hand hashes to
the recorded digest, that the Ed25519 signature and position binding verify,
that the AWS Nitro attestation validates to the AWS root and binds this
exact proof, that the attested PCR0 is a published BitGraph enclave
measurement (enclave v9 included), and, from block headers in the bundle,
the Ethereum block the recording comes after (its floor, a time) and the
later anchor it was committed before (a position, never a clock time).
This build predates the Base floor (commit.slotFloor): it states the
floor of Ethereum-anchored proofs only.
Every line is TRUE, FALSE, or UNDETERMINED:
FALSE only when evidence in hand contradicts the recording (an edited
signature, a block header that does not hash to its anchor), UNDETERMINED
when the evidence does not decide (the file is not in the bundle, a
witness is missing). Absence is never a verdict. The report ends with what
no offline check can establish, stated rather than implied. --json
prints the bitgraph-check/1 report; exit codes match evaluation.
The same check runs in a browser: verify.html, built from this package
to dist-web/verify.html. Open it and choose or drop a bundle, offline,
and it renders the same report from the same code. bitgraph.ing stopped
serving a copy on 8 September 2026: loading a checker from the party
being checked is not an offline check. A copy you already have still
works and always will, since it carries its own code and makes no
network request.
Start a rule
bitgraph-play init po.pdf delivery.jpg approval.pdf --out rule.jsoninit hashes the files you name and writes a rule skeleton with the
cast filled in: one role per file, digests computed, role names from
filenames. The skeleton does not run as written: requires.ordering
is a placeholder you must replace, because the trust floor is the
rule's own security policy and has no default, from the scaffolder or
anywhere else. Choose the floor, say what each digest means, refine the
claim, then run it.
init is a reserved first word as of 0.2.0, check as of 0.4.0, and
pin as of 0.6.0. A rule file literally named like one of them is still
reachable: write it as ./init, or after --, which ends option
parsing (bitgraph-play -- init bundle/). When a file with one of those
names exists in the working directory, the bare spelling is refused as
ambiguous rather than silently picking a mode.
A rule
{
"rule": "bitgraph-player/1",
"id": "po-release-payment",
"cast": {
"purchase_order": { "digest": "sha256:…", "means": "PO-4471" },
"delivery": { "digest": "sha256:…" },
"approval": { "digest": "sha256:…" },
"cancellation": { "digest": "sha256:…", "optional": true }
},
"world": "closed",
"requires": { "ordering": "assumption-dependent" },
"claim": { "all": [
{ "exists": "purchase_order" },
{ "after": ["delivery", "purchase_order"] },
{ "after": ["approval", "delivery"] },
{ "not": { "before": ["cancellation", "approval"] } }
]},
"then": { "label": "release_payment" }
}cast is everything taken on the rule author's word: which digest means
what, which occurrence is meant, who is said to have signed it. claim
is only what BitGraph derives. then is a label: no field of a rule can
cause an action. Player decides; whatever stakes money on a TRUE sits
above it.
Three answers, not two
A claim evaluates to TRUE, FALSE, or UNDETERMINED. Undetermined is
the honest answer wherever the evidence does not decide: recordings whose
order the ledger does not establish, a digest recorded more than once
with no pin selecting the occurrence, evidence below the rule's declared
trust floor (requires.ordering). An evaluator that always answers is
wrong on some input.
The verdict splits derived (BitGraph established this) from declared
(a named party asserted this), and its last declared entry is always the
closed world itself: absence is asserted only among the roles the author
declared, and nothing establishes that the cast is complete.
Determinism
Same rule bytes, same bundle contents, byte-identical verdict, on any machine, years later. No timestamps, no paths, no randomness. The normative semantics are in SPEC.md; this package is the reference implementation, and a conforming Player in any language must agree with it.
API
One call runs the whole pipeline; the CLI is built on the same function, so embedding Player cannot drift from it:
import { play } from "@mikeargento/bitgraph-player";
const { verdict, bytes, exitCode } = await play("rule.json", "bundle/");The pieces are exported individually for callers that already hold an
AuditResult or want to intercept a stage:
import { runAudit } from "@mikeargento/bitgraph-audit";
import {
parseRule, resolveCast, evaluate, buildVerdict, serializeVerdict,
} from "@mikeargento/bitgraph-player";
const rule = parseRule(ruleText);
const audit = await runAudit(bundlePath);
const resolutions = resolveCast(rule.cast, audit);
const evaluation = evaluate(rule, resolutions, audit);
const verdict = buildVerdict(rule, ruleSha256Hex, resolutions, evaluation, audit);
process.stdout.write(serializeVerdict(verdict));check is exported the same way, in two halves that the CLI and the
browser page share:
import { ingestBundle, ingestEntries } from "@mikeargento/bitgraph-audit";
import { checkIngest, renderCheckText } from "@mikeargento/bitgraph-player";
const report = await checkIngest(await ingestBundle("recordings/"));
// or, from bytes already in hand (a browser drop):
// await checkIngest(await ingestEntries([{ path: "proof.json", open: () => bytes }]));
process.stdout.write(renderCheckText(report));License
MIT. Verification and evaluation are permissionless by design.
