Releases: gaussia-labs/pyboltzmann
Release list
v0.9.0
v0.9.0 (2026-08-31)
This release is published under the MIT License.
Bug Fixes
- sandbox: Match the index interface that changed in v0.2.0 (
c07a45f)
Index.build gained a ContentReader in 838fb72 and the sandbox's two engines never followed, so every planner test has errored since -- eighteen of them, long enough that the breakage read as background noise rather than a signal.
Both engines accept the reader and neither uses it, which is the honest shape for a bag-of-words example: term matching reads the symbolic fields, and fetching a datum per block to index text already in the payload would make every commit pay for nothing. The parameter is documented as accepted-and-unused so the next reader does not go looking for the call.
Unrelated to the attribution work, and separate from it for that reason. It is here because the sandbox has to run before anything about the sandbox can be verified.
Documentation
- attribution: A guide for who did the work (
7078f33)
There was no provenance guide at all, and the records were documented in a table that said six when there have been seven since validation landed -- so the one member every record carries, the actor, went unmentioned in the place a reader would look for it.
The new guide is organised around the distinction that took the longest to settle: the actor, the assisting parties, and the key subject answer three different questions, and only two of them are ever checked against a signature. It states plainly that the protocol assigns no responsibility, which is a decision worth being able to point at rather than infer.
Also: every page that used curator now defines it. Eight of them referenced a variable out of nowhere, which is fine when the value is obvious and stopped being fine when the identifier acquired a form.
Every example in the guide was executed before it was written down.
Features
- sandbox: Record the agent that did the work (
561b6aa)
The sandbox is the one place an agent actually calls in, and every write here claimed ActorKind.HUMAN -- including the MCP server paths, where the caller is by construction an agent. The record said a person did work a model did, which is the one thing an audit trail must not do quietly.
BOLTZMANN_AGENT names the runtime writing on the actor's behalf and BOLTZMANN_AGENT_MODEL the model it ran; both land in assisted_by beside the actor, never instead of it. The actor is whose account the work runs under and the agent is what did it, and neither substitutes for the other.
Empty is the ordinary case for a person working directly, and it matters that it stays empty: naming an agent unconditionally would take every brain the sandbox writes out of schema version 1 for nothing.
boltzmann-doctor prints who is assisting, so the answer to "what will this record" is visible before the first write rather than after it.
Detailed Changes: v0.9.0-b.4...v0.9.0
v0.9.0-b.4
v0.9.0-b.4 (2026-08-31)
This release is published under the MIT License.
Features
- authenticity: Report which claimed actors a signature vouches for (
a02488f)
The reader half of the trust root's subject. Until a key stands behind a name, the actor in a provenance record is a declared identifier -- whoever can write to a brain can write any name into its audit trail -- and this is where the names and the signatures are finally put side by side.
Reported, never enforced, and the restraint is the point. A snapshot legitimately names actors that never signed it: every merge does, because reconciliation brings another party's records into a history the local key signs. Refusing an unvouched actor would refuse reconciliation itself. What must not happen is passing unremarked, since an unvouched actor is exactly what every actor was before subjects existed.
Only the records a snapshot introduces are compared -- judging inherited ones would re-report every ancestor's contributors at every position -- and only against keys whose signature actually verified, so a retired or under-scoped key identifies its holder without vouching for anyone.
asserted and legacy are kept apart because the remedies differ: one is a governance act, the other a rewrite nobody can perform on published bytes. Assisting parties are never compared; nothing expects a model to hold a key.
check_attribution follows removals.py, including its resolvable/undecidable split: a composition that cannot be read is reported rather than passed, or a withheld one would turn the comparison off.
Detailed Changes: v0.9.0-b.3...v0.9.0-b.4
v0.9.0-b.3
v0.9.0-b.3 (2026-08-31)
This release is published under the MIT License.
Features
- authenticity: Name who a trusted key belongs to (
365104a)
The paper makes this connection load-bearing -- Section 5 says a signature is what turns a declared actor into an authenticated identity, and Section 8.3 rests the Ed25519 strictness on it -- and no mechanism existed. A trust-root entry carried five members and none was an identity, the SSH comment is deliberately stripped, and Brain.sign never reads the actor. So provenance named a person, the signature named a fingerprint, and nothing asserted they were the same.
TrustedKey gains an optional subject: an actor identifier, inside the signed bytes, changed only by a revision and therefore only by a quorum. The claim is narrow on purpose and stays narrow: it is what this brain's governance asserts, not a certificate, and the grounds for believing a key is someone's remain outside the protocol. What it adds is that once those grounds exist the conclusion is written where a verifier reads it.
An absent subject is omitted rather than serialized as null, so a trust root that names none keeps exactly the digest it had and every pin still holds.
SignatureVerdict and Authorship report it, so a quorum's holders are all readable rather than just the first. An attributable key reports none, which is the whole state: nobody here has said whose it is.
AgentSigner stops discarding the agent comment and offers it as a suggested subject when it already is an identifier. Offered, never adopted -- the comment is a label the key's own holder typed.
Detailed Changes: v0.9.0-b.2...v0.9.0-b.3
v0.9.0-b.2
v0.9.0-b.2 (2026-08-31)
This release is published under the MIT License.
Features
- provenance: Record everyone who took part (
6b74f6f)
Most brains are hydrated through an agent, so the record of who actually did the work was the missing part. A record has always named an actor; what it could not say is that a model wrote the interpretation, which harness it ran inside, or that a second person was in the session. producer answered part of that, for derivations alone, in a shape that made a version string load-bearing.
Schema version 2 adds assisted_by: people and agents in one list, each agent naming the model it ran, so the pair stays intact when several agents write into one snapshot. The same model under a different harness is a different collaborator. No version strings -- a version is the member most likely to be invented by whoever fills the record in, and it buys less than the identity beside it.
A derivation's two versions are disjoint rather than nested, since version 2 replaces producer instead of adding to it. DerivationRecordV2 is therefore a sibling, as SemanticBlockV3 already is, and requires assisted_by: version 1 obliged a writer to say what produced a derived block and version 2 must not relax that. A record naming nobody keeps the bytes, and the block_id, it had before version 2 existed.
A removal never leaves version 1. It is the one record a verifier must decode to decide a blocking question, and _reachable_removals skips what it cannot decode -- so an older client would read a valid brain, miss the record, and reject the snapshot for violating an invariant it satisfies. Not being able to read something must never be reported as that thing being wrong.
Ledger.made_by resolves one query across both shapes, because a brain holds records of both at once and a batch invalidation that read one would silently miss blocks. A person is never matched as a model: a human collaborator carries none.
Corpus 1.1 vendored, which is what registers provenance 2.
Testing
- Measure the merkle scaling bound with the fastest run, not one run (
5351583)
The assertion is narrow on purpose: doubling the leaves costs twice as much when verify is linear and four times when it is quadratic, so the bar sits between at three. That leaves it about 50% of headroom over the real ratio, and a shared CI runner spends more than that preempting the process -- twice on 3.13 the same code that measures 2.05 locally measured 3.5 and failed, while passing on 3.11 and 3.12 in the same run.
Timing noise can only make a run slower, never faster, so the minimum of several runs is the closest reading to the work actually performed. Five repetitions bring the measured ratio to 2.17 with a variance of 0.01, and cost a few milliseconds.
A fresh tree per repetition, because MerkleTree memoizes its internal nodes: verifying one instance twice would time a warm cache and report a speed nothing in production sees. The leaves are built once, outside the timer, since building them is not what is being measured.
Widening the bar was the alternative and is worse. At four it stops distinguishing linear from quadratic, which retires the assertion instead of stabilising it. Checked against a deliberately quadratic stand-in, which still measures 4.07 and still fails.
Detailed Changes: v0.9.0-b.1...v0.9.0-b.2
v0.9.0-b.1
v0.9.0-b.1 (2026-08-31)
This release is published under the MIT License.
Features
- identity: Give an actor an identifier two implementations resolve (
a22853f)
Actor.id was an unconstrained string, and the repository spelled it five ways: role nouns, a first name, $USER. A provenance record is a block, so that string is hashed into block_id -- two spellings of one person are two names for one fact, and neither party fails. It is the divergence canonical serialization exists to prevent, arriving through a field nobody had canonicalized.
An identifier now takes one of two forms: an address, or a namespaced name. Lowercase ASCII, refused rather than normalized, because lowering one would mint a block_id the caller neither asked for nor can predict.
The check is deliberately asymmetric. Actor itself stays permissive, since every record ever written decodes through it and a validator on the type would strand every brain that predates the rule. Enforcement attaches where an identifier is chosen -- Brain.init and the request models -- where a caller can still be told what to choose instead.
The sandbox derived its actor from $USER, a name that resolves on one machine and nowhere else; the fallback is now namespaced under sandbox/ to say so.
Detailed Changes: v0.8.0...v0.9.0-b.1
v0.8.0
v0.8.0 (2026-08-31)
This release is published under the MIT License.
Detailed Changes: v0.8.0-b.13...v0.8.0
v0.8.0-b.9
v0.8.0-b.9 (2026-08-30)
This release is published under the MIT License.
Features
- authenticity: Warn when the govern quorum leaves no margin (
d421559)
A trust root whose quorum equals its number of govern holders is legal, and it is also a one-key fuse. Lose that key -- stolen, or simply lost -- and governance is over: neither the remaining holders nor an attacker can assemble the signatures to record a compromise or admit a replacement, while a stolen key keeps signing within its scopes. The protocol has no recovery path, because re-anchoring would be exactly the self-assertion the quorum rule exists to forbid.
So it is said out loud, twice and for different readers. init and rotate warn at the moment the margin is chosen, which is the only moment anything can still be done about it, and the report carries a non-blocking QUORUM_MARGIN finding so a consumer meeting the brain later sees the condition too.
A warning rather than a refusal: no rule forbids the configuration, and a deployment with exactly one owner has no other option available to it.
Detailed Changes: v0.8.0-b.8...v0.8.0-b.9
v0.8.0-b.8
v0.8.0-b.8 (2026-08-30)
This release is published under the MIT License.
Features
- authenticity: Distinguish an attributable proposal from an unauthorized head (
1c52fae)
A signature by a key the trust root does not list had exactly one reading here, and the protocol requires two. Offered for review, such a snapshot is how an open project hears from someone it has never admitted: the author is identified and no authority attaches. Served as the brain's current state, the identical bytes are an impersonation attempt. Collapsing them meant either refusing every stranger's contribution or reporting an imposture as an ordinary proposal.
The distinction is positional, so the position is now an input. authenticate() takes a stance, defaulting to HEAD because a caller who does not say is asking about a brain's current state and must get the safe answer. Under OFFERED an unlisted key yields ATTRIBUTABLE_KEY and the report resolves to the new ATTRIBUTABLE state; the policy bar for a published head is not applied, since judging a proposal against it would refuse every contribution ever made.
Attributable is not a weaker authorized: require_authorized still raises. What it adds is the author's fingerprint, on the report and in the Authorship an evidence bundle carries, because a state whose whole content is "who wrote this" that did not say who would have gained nothing over an anonymous one.
plan_reconcile sets the stance for the contribution path and reports the result, which is where a maintainer reads it.
Detailed Changes: v0.8.0-b.7...v0.8.0-b.8
v0.8.0-b.7
v0.8.0-b.7 (2026-08-30)
This release is published under the MIT License.
Features
- ingest: Record the verdict that admitted each block (
aaaede6)
The provenance ledger carried six record types where the protocol names seven. The missing one is validation, and its absence made "it was validated" a claim a consumer had to take from whoever committed: the verdict lived only on the write path, and nothing in the signed composition could confirm a gate had run at all.
Every committed block now gets a validation record beside its derivation edge, naming the verdict, the checks that produced it, and the task. The check set is part of the claim rather than decoration -- the same VALIDATED under two different check sets says two different things -- so the gate now carries the codes that ran on its report, and the record refuses to be written without them.
ValidationStatus moves to the provenance module, because a verdict that travels in a record is wire schema rather than write-path bookkeeping. It is re-exported from the gate, so every existing import keeps working.
Brain.audit_validation reads the ledger back and reports what cannot show its verdict. It reports rather than refuses: a brain written before the record existed did nothing wrong, and refusing it would trade availability for an auditability that snapshot cannot retroactively supply. The removal invariant is the one that rejects, because there a missing record is the attack itself.
Detailed Changes: v0.8.0-b.6...v0.8.0-b.7
v0.8.0-b.6
v0.8.0-b.6 (2026-08-30)
This release is published under the MIT License.
Bug Fixes
- distribution: Serialize a projection's references as its source does (
088cffd)
A projection's canonical bytes came from a plain model dump while a snapshot's exclude None, so a retained reference was spelled with explicit nulls where the source snapshot omitted the keys entirely. The two documents then disagreed about a reference both claim is the same one, and a consumer comparing the retained entry against the resolved source byte for byte would have been right to refuse.
Adding tombstones to ModuleRef widens that gap by one more optional field, so it is fixed here rather than left to grow.
Features
- retention: Make removals verifier-checkable (
67a67c9)
Detailed Changes: v0.8.0-b.5...v0.8.0-b.6