Bellbook 0.10.0 is out. Nothing changed on the wire. What changed is that a receipt can now be judged under a tier where every claim that shapes a line of work is bound to a key, and the promise 1.0 will make is written down.

What Clean does not say

A Clean receipt says the record is intact, every verdict re-derives, and the rules were followed. Under bellbook-core-v1 it also says the rules had the shape two parties agreed to compare under. Under delivery-receipt-v1 it says a specific delivery claim holds: requirement met, evidence on the record, evaluator distinct from producer.

None of that says who wrote the records. Bellbook has carried Ed25519 signatures since 0.2, and the rules could always require them, but nothing published said what “enough signing” looks like, so a receipt from a party you do not trust could be Clean and unsigned at the same time. The threat model has been explicit about this from the start: the log’s owner can rewrite it wholesale from genesis, and the only thing that anchors against that is a signature under a key the other party pinned.

bellbook-core-signed-v1 is the tier that says what enough looks like.

Four clauses, all fail-closed

  • S0 the baseline bellbook-core-v1 is met.
  • S1 the rules require a signature on every evolution kind: Candidate, Evaluation, Selection, Retraction, Requirement.
  • S2 every author of an accepted record of those kinds is pinned to a public key in the rules.
  • S3 every evaluation an accepted, selected Selection uses is bellbook.evaluation.attested.v1.

Fail-closed means the same thing it meant for the delivery receipt. An Invalid receipt fails every clause. A record without its signature by a pinned author is rejected on replay with SignatureMissing; one signed with a key the rules do not pin for its author is SignatureInvalid; the signature is bound into the record id, so a stripped signature is not a missing field but a broken id, and the receipt is Invalid. The attested evaluation schema is admitted only under a pinned signature, which is what makes S3 mean something: a selection that rests on it rests on a judgment someone put their key to.

Tainted history may still conform. A signed retraction is exactly what the tier is for, and the record that shows one is a better record, not a worse one.

No payload changes shape

The tier is not a new record model. A baseline-conformant receipt reaches it by adding signatures and switching evaluation schema ids. A delivery receipt judged under the signed tier is the same delivery receipt, with the same eight clauses, plus four more. The vectors include that case explicitly: a delivery claim under the signed tier, every clause of both profiles met.

Signing without touching Rust

Key generation stays with the host; Bellbook has no random number generator and does not want one. Any 32 bytes are a valid secret.

openssl rand -hex 32 > human.hex
bellbook key public --secret human.hex
bellbook rules init --author human:user --author agent:provider --author evaluator:provider \
    --signed --author-key human:PUBKEY_HEX --author-key agent:PUBKEY_HEX \
    --author-key evaluator:PUBKEY_HEX --out rules.json
bellbook candidate add --log ./log --rules rules.json --author agent --git-tree TREE --sign-key agent.hex
bellbook eval add --log ./log --rules rules.json --author evaluator --candidate CAND \
    --criterion unit-tests --passed --evaluator harness --basis recomputed --attested --sign-key evaluator.hex
bellbook export --log ./log --rules rules.json \
    --profile bellbook-core-v1 delivery-receipt-v1 bellbook-core-signed-v1 --out receipt.json
bellbook validate receipt.json

Every recording command takes --sign-key FILE. The CLI never prints a secret. validate evaluates every declared profile unasked and exits 0 only if every one is met; a skeptic who received a receipt that did not declare the tier runs --require-profile bellbook-core-signed-v1 and gets the same answer.

In Python: default_rules(..., signed=True, author_keys={actor: [pubkey_hex]}), Writer(log_dir, rules, signers={actor: secret}), and evaluate(..., attested=True). Report.profiles carries the four clauses.

Two implementations, one answer

The profile ships with its own vector set: the tier met in five honest shapes, one rejecting case per clause, a stale declaration, and a receipt with a signature stripped. Every record a pinned author wrote carries a real Ed25519 signature under a deterministic test key. The independent Python validator implements every clause from scratch, verifies the signatures itself with a different library, and agrees on every vector, in CI on every commit. The one-receipt entry point for a skeptic, validate_receipt.py, is standard library only and refuses to run with the bellbook package imported.

What 1.0 will promise

1.0 is a freeze, not a feature release. docs/STABILITY.md says what it will cover: spec epoch 0.4 frozen and receipts forward-valid; the three profiles immutable by construction; the crate’s public items, the CLI grammar and JSON output, and the Python surface under semantic versioning; the MSRV policy. It says what it will not cover: capture completeness, confidentiality, anything a profile does not check, performance, internals, message text. And it says how anything promised can change, with a deprecation period per surface.

The 0.10.x line already behaves this way. The public API audit that accompanies it found every public item documented, nothing deprecated, and no breaking change against 0.9.0 under cargo semver-checks.

What did not change

Nothing on the wire. Every record, canonical form, signing form, schema, and verdict rule is that of 0.9.0. Core conformance and the two earlier profiles’ vectors are byte-unchanged. A 0.9.0 receipt validates identically under 0.10.0.

What happens next

0.10.0 is the line the 1.0 soak runs on. 1.0.0 is tagged after a committed adopter runs on it in production for 30 days with no breaking change needed and the external security review is complete. The sequence is in RFC-0003 section 9. Until then, nothing here is a settled standard, and the documents say so.

cargo add bellbook or pip install bellbook==0.10.0. The profile document is docs/profiles/bellbook-core-signed-v1.md; the specification is SPEC.md section 12.2.