@mikeargento/exam-cli
v0.3.0
Published
exam: bind the bank, derive the paper from its nonce, ask a model, bind the answers, grade, export, verify.
Readme
exam
A command-line tool that binds a request naming the public item bank as a BitGraph (A), derives a set of exam questions from A's nonce, asks a model through its own API, binds the model's answers as a second BitGraph (B) with a Base ceiling, grades by re-running the bank's checkers, and writes a folder anyone can verify offline.
Proves: every question is derived from BitGraph A's nonce, which the enclave drew and bound in
one atomic action and nobody could see before A existed, so the exact questions did not exist
before A, and no training set frozen before A was bound contains them; A's request named the bank,
so the bank was fixed before that nonce; BitGraph B bound the answer sheet, which names the paper
and A, at a later count in the same epoch; both proofs carry an AWS Nitro attestation of a
published BitGraph enclave image; where answers/base-ceiling/ is in the folder, the answers
existed by that Base block.
Does not prove: that the template families are unfamiliar to the model; that the model worked alone, without tools, or quickly; that the answers came from the model the sheet names.
Every report leads with A: "These exact questions could not have existed before BitGraph #102 was bound." Each BitGraph then reads as its proof page does: its two counts (the nonce at N, the digest at N+1, one atomic action); the instant it was bound, by the AWS Nitro attestation's signed clock; and, for B, its ceiling, "existed by Base block M (header time T)", once that is written.
Run
Node 22.13 or later (the grader uses node --permission).
exam run --provider anthropic --model claude-haiku-4-5 --name demo/claude-haiku-4-5The Haiku 4.5 sitting in the sealed-exam repository printed, as it went:
Bound the request as BitGraph #102 (nonce at count 101), 19 Oct 2026 05:09:47Z by the AWS Nitro attestation's signed clock. Enclave v13.
Wrote 20 questions from its nonce and the bank it names.
Asked claude-haiku-4-5. 20 answers back.
Bound the answers as BitGraph #104 (nonce at count 103), 19 Oct 2026 05:10:10Z by the AWS Nitro attestation's signed clock.and exam verify demo/claude-haiku-4-5.exam on that folder prints, before the line-by-line checks:
ACCEPT
Score 12/20
These exact questions could not have existed before BitGraph #102 was bound.
BitGraph #102 was bound at 19 Oct 2026 05:09:47Z, by the AWS Nitro attestation's signed clock.
A (the request; its nonce made the paper): BitGraph #102 https://bitgraph.ing/proof/G_pjZcvmt1vFvpnryeLOz1oU-oh4YbtidaJZetxDaqk
bound nonce at count 101, digest at 102, one atomic action, epoch EvN3PaxsZnaW…
recorded 19 Oct 2026 05:09:47Z (the AWS Nitro attestation's time)
B (the answers): BitGraph #104 https://bitgraph.ing/proof/DMtRKRoPudd_-dUuNwlLichD7_rTjN2ydXsGsjAthpI
bound nonce at count 103, digest at 104, one atomic action, epoch EvN3PaxsZnaW…
recorded 19 Oct 2026 05:10:10Z (the AWS Nitro attestation's time)
ceiling existed by Base block 52798633 (header time 19 Oct 2026 05:10:13Z)
Model: anthropic claude-haiku-4-5 (reported claude-haiku-4-5-20251001)run ends the same way. A ceiling is written after its BitGraph (seconds to minutes), so a
folder exported at once may say "not in this folder" for it; exam export <name> again adds it. run
also opens report.html. --json prints the verdict object instead. The API key comes from the
environment (ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_API_KEY, OPENROUTER_API_KEY); it is
never written anywhere. Each step is also its own command: paper (bind A, derive the paper), ask (ask, bind B), grade,
export, verify, selftest. exam verify <folder> runs entirely offline and answers
ACCEPT, REJECT, or NO-EVIDENCE (exit 0, 1, 2; 3 on error), writing verdict.json and
report.html beside the files.
The folder
<name>.exam/ holds four directories in the shape the bitgraph.ing drop zone reads (drop the
whole folder there and both BitGraphs show):
request/ request.json (A's bytes: names the bank), proof.json (A)
bank/ bank.json, generator.tar
paper/ paper.json derived from A's nonce; not bound itself
answers/ answers.json (B's bytes), proof.json (B), base-ceiling/
raw/ q01.json … every request and reply, verbatim
README.txt the claim boundaryanswers/base-ceiling/ceiling.json is B's Base ceiling (bitgraph-ceiling/1, the file its
proof page downloads), kept only when it checks against the proof. Folders made before 2026-10-18
carry the paper's own proof under paper/ and a floor block's evidence beside each proof
(base-floor/ or ethereum-anchors/); they verify as they always have.
What a verifier does
- A, the bind of
request/request.json, with bitgraph-verify'sverifyAttested: signature, nonce record and its binding, the atomic form (a proof that is not a bind is refused), the AWS Nitro attestation chaining to the AWS root at its own instant with user_data equal to the signed body, and a PCR0 among BitGraph's published images; the request names the bank in the folder, whose digest recomputes from the shipped bank and generator archive. - The seed from A's nonce and the bank digest; all K questions re-instantiated and compared to
paper/paper.jsonbyte for byte. - B, the bind of
answers/answers.json, checked as A; same epoch and chain, B's nonce after A's bind (a different epoch is undetermined, not a failure); itspaperDigestB64is the paper's digest and itsa.proofHashis A's; each raw reply inraw/hashes to the digest the sheet names. Whereanswers/base-ceiling/ceiling.jsonis present, B's Base ceiling: a transaction from BitGraph's published writer, included in a Base block by the inclusion proof and header the file carries, whose Merkle root covers B's proof hash. A ceiling file that does not check is a contradiction; an absent one is not held against the run. - The grade, by re-deriving the canonical answers and running each family's checker. A
grade.jsonin the folder is never read.
Missing proof or bank: NO-EVIDENCE. Anything that recomputes and disagrees: REJECT. Everything
checks: ACCEPT. The derivation is written down with test vectors in
@mikeargento/exam-core/SPEC.md.
Fixtures
fixtures/ holds nine folders made offline from one real sitting, each with a FIXTURE.txt
naming the expected verdict. exam selftest verifies each (and, in a clone of the repository,
the archived fixtures too) and prints the count.
