Bellbook 0.7.0 is out. It ships one thing, and the reason for it is worth stating plainly.

The problem with “Clean”

A Bellbook receipt validates to one of three words: Clean, Tainted, or Invalid. Clean means the captured history replays consistently under the rules embedded in the receipt. That sentence has a quiet clause in it: under the rules embedded in the receipt. Every receipt carries its own rule set, and a validator judges the history against those rules and no others.

That is the right design for a single organization. It is a problem the moment two organizations want to compare receipts. My Clean and your Clean may rest on different rule shapes: different registered authors, different evidence thresholds, different context bounds. Both receipts are honest. Neither says anything about the other. The rules_hash a receipt reports lets you check that the rules match a set you already trust, but it cannot tell you what a stranger’s rules require.

Since 0.3, the honesty note in the spec has said exactly this: Clean is “internally consistent,” not “meets a shared security baseline,” and a baseline was planned. 0.7 ships it.

bellbook-core-v1

bellbook-core-v1 is a profile: a separately versioned document with a stable id, a hash over its normative clauses, and a predicate over the receipt’s rules, records, and validation report. It is deliberately small. Six clauses, and two of them can never fail:

  • B1: the receipt is not Invalid.
  • B2: author roles are registered, so every accepted record’s author was known to the rules.
  • B3: evidence thresholds are declared for the evolution records (Candidate, Evaluation, Selection) and are no weaker than each schema’s base class. This is the clause that does the work: it rules out a rule set that would admit assumption-class evolution records.
  • B4: a bounded context size is declared.
  • B5: who could retract or reaffirm is reported from the rules.
  • B6: the source binding mode of every accepted Candidate is reported.

B5 and B6 are reporting clauses. They exist so that a consumer reading a profile result sees the facts a comparison needs, not so a receipt can fail them.

The clause table is content-addressed: a SHA-256 over its canonical form, printed with every result. If a clause statement changes, that is a new version with a new hash. Nobody can quietly move the bar.

The baseline requires no signatures. Not every adopter signs from day one, and a baseline nobody can meet compares nothing. The signed tier is a separate profile, scheduled for 1.0.

A report, not a verdict

This is the design decision I care most about. Profile conformance is reported alongside the verdict and never changes it. A receipt can be Clean and NonConformant at the same time, and that is not a contradiction: the history is consistent under its own rules, and its rules do not have the baseline shape. The validator says both things and lets the consumer decide. Conformance is never a verdict reason code, and Tainted receipts can conform: the baseline is about the shape of the rules a history was committed under, not about whether anything was ever retracted.

bellbook validate receipt.json --require-profile bellbook-core-v1
# exit 0 clean and conformant
# exit 2 tainted and conformant
# exit 3 validates but does not conform
# exit 1 invalid
report = bellbook.validate(data, require_profile="bellbook-core-v1")
report.profiles[0]["status"]    # "Conformant" | "NonConformant" | "Unknown"
report.profiles[0]["clauses"]   # [{"id": "B3", "passed": False, "detail": "Candidate=missing, ..."}, ...]

An id the validator does not know is reported as Unknown, never raised.

Checked, not promised

The profile ships with vectors: receipts paired with the exact result each must yield, including a rejecting vector for every clause that can fail. The independent Python validator, which shares no code with the Rust reference, implements every clause from scratch, recomputes the profile hash from the published clause table, and agrees with the reference on every vector. That is the same discipline the core conformance corpus follows, kept in a separate directory so that a profile can never destabilize core conformance.

Generated rules now conform out of the box: bellbook rules init and the Python default_rules(...) emit the three baseline thresholds. Rules written earlier stay valid and keep their hash. They are simply NonConformant until the thresholds are added, which is the correct answer.

Where this goes

0.7 is the first release of a sequence set out in RFC-0003, accepted this week. The next spec epoch adds a Requirement record, first-class artifact identity, and a structured evaluator decision. On top of those, a delivery-receipt profile will define what it takes for a receipt to carry a checkable claim that a piece of work was done against stated requirements. The baseline profile is the floor all of that stands on.

Nothing on the wire changed in 0.7. The spec epoch stays 0.3 and the 0.3 conformance corpus is byte-for-byte what it was. Receipts from 0.3 through 0.6 validate identically.

Bellbook is on crates.io and PyPI. The profile document, the clause table, and the vectors are in the repository.