release: prepare content-addressable 0.1.1 (identity/classification layer) - #86
Merged
Conversation
…ayer) #85 is on `main` (`dbb94c3`) and its full matrix re-verified green there. This cuts the bugfix release and closes two documentation gaps found while re-verifying it. **No code behavior changes.** WHAT WAS VERIFIED ON MERGED `main` FIRST #85's green matrix was measured against `534d96d`; `main` then took serde 1.0.229, serde_json 1.0.151, pyo3 0.29.2 and thiserror 2.0.20 underneath it and the PR merged without textual conflict — which is not the same as a validated tree. Re-ran everything on `dbb94c3`: `just check`, MSRV 1.85, `pytest` 105, each `unstable-*` feature ALONE (test + clippy) plus no-features and all-features, and CI on the merge commit. All green; the dep bumps are inert here. The invariants also hold as enforced code, not just prose: `ForeignCid`'s every constructor rejects a recognized profile and `ClassifiedCid::from_cid` is total, so the three variants are pairwise disjoint and jointly total; `Deserialize` re-derives the variant from wire bytes; both mintable profiles validate version + codec + hash code + digest length; identity is the whole `Cid` newtype, never the digest; the Python surface exports neither legacy nor migration. GAP 1 — a default-off surface with no stability statement `docs/STABILITY.md` is the crate's answer to "may I persist this?", which is why it has a **What is NOT frozen** section. That section covered `unstable-merkle` and `unstable-store` but NOT the two features #85 added. `IdentityMigration` appeared nowhere in the document at all; its non-frozen status lived only in a `Cargo.toml` comment and a module doc. That inference is expensive in the wrong direction. The record is itself content-addressed, so its field names are load-bearing for its own id — a consumer who read the silence as "settled" would persist identity claims that a later rename invalidates. Both features now have subsections stating what is unfrozen (API for legacy; API *and* wire bytes for migration) and, for migration, the condition that would freeze it: golden migration vectors making its content-addressed representation explicit and reproducible, the same bar `vectors.json` / `raw_vectors.json` already meet. Regression guard, not just prose: `tests/stability_doc.rs` fails if any declared `unstable-*` feature is unmentioned under that heading. Verified non-vacuous — against `main`'s shipped text it names exactly `["unstable-legacy", "unstable-migration"]`. It deliberately does not judge what the prose *says*; a test cannot, and wording stays a review concern. Its parser is covered by a second test (comments, other tables, non-list values) so the guard cannot pass by parsing nothing. GAP 2 — one survivor of the `VerifiedCid` → `ClassifiedCid` rename `Cargo.toml`'s `unstable-legacy` comment still said `VerifiedCid`. Small, but it reintroduces exactly the name ambiguity the rename removed — this crate already has a `VerifiedStore` that *does* check content against its id, and two meanings of "verified" in one surface is what the rename existed to prevent. RELEASE Three version strings bumped in lockstep (0.1.0 → 0.1.1); `just verify-release` confirms Rust '0.1.1' == PEP 440 '0.1.1'. CHANGELOG entry describes the release as additive: the frozen `0.1.0` core is untouched, every golden vector is byte-identical, and identifiers minted under `0.1.0` remain valid. Tagging is deliberately NOT done here — pushing `v0.1.1` triggers the publish workflow to crates.io and PyPI, which is irreversible, so it stays a human act per RELEASING.md. Co-authored-by: Claude Opus 5 (1M context) (Claude Code) <309460085+newt-agent@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this PR does
#85 is already on
main(dbb94c3). This cuts the 0.1.1 bugfix release andcloses two documentation gaps found while re-verifying it. No code behavior
changes.
Why re-verify at all
#85's green matrix was measured against
534d96d.mainthen took serde1.0.229, serde_json 1.0.151, pyo3 0.29.2 and thiserror 2.0.20 underneath it, and
#85 merged without a textual conflict — which is not the same thing as a
validated tree. So the first thing I did was re-run everything on
dbb94c3.Result: green, and the dep bumps are inert here. Details in the test plan.
The invariants hold as enforced code, not just prose:
WellFormedCid ≅ ContentId ⊎ RawContentId ⊎ ForeignCidForeignCid's every constructor (new,TryFrom<Cid>,FromStr,from_bytes,Deserialize) rejects a recognized profile;ClassifiedCid::from_cidis total. Pairwise disjoint, jointly total.Deserializere-derives the variant from wire bytes rather than trusting a tagClassifiedCidproves profile membership; content verification staysverify/ensure_content_id/VerifiedStoreFromStrnever learns a dialectCidwith derivedEq/Hash/Ord— no cross-typePartialEq, noFromeither wayGap 1 — a default-off surface with no stability statement
docs/STABILITY.mdis the crate's answer to "may I persist this?" — the wholereason it has a What is NOT frozen section. That section covered
unstable-merkleandunstable-storebut not the two features #85 added.IdentityMigrationappeared nowhere in the document at all; its non-frozenstatus lived only in a
Cargo.tomlcomment and a module doc.That silence is expensive in the wrong direction. The record is itself
content-addressed, so its field names are load-bearing for its own id — a
consumer who read the silence as "settled" would persist identity claims that a
later rename invalidates.
Both features now have subsections stating what is unfrozen (API for legacy;
API and wire bytes for migration) and, for migration, the condition that
would freeze it: golden migration vectors making its content-addressed
representation explicit and reproducible — the same bar
vectors.json/raw_vectors.jsonalready meet.Guarded, not just written:
tests/stability_doc.rsfails if any declaredunstable-*feature is unmentioned under that heading. Againstmain's shippedtext it names exactly
["unstable-legacy", "unstable-migration"]. Itdeliberately does not judge what the prose says — a test cannot; wording
stays a review concern. Its parser has its own test (comments, other tables,
non-list values) so the guard cannot pass by parsing nothing.
Gap 2 — one survivor of the
VerifiedCid→ClassifiedCidrenameCargo.toml'sunstable-legacycomment still saidVerifiedCid. Small, but itreintroduces exactly the ambiguity the rename removed: this crate already has a
VerifiedStorethat does check content against its id.Test plan
Run on merged
main(dbb94c3) and again on this branch:just check— fmt · clippy--all-features -D warnings·cargo test+cargo test --all-features· doc-D warnings· leaf guard ✅just msrv) ✅maturin develop+pytest -q: 105 passed ✅--all-features): each ofunstable-merkle/unstable-store/unstable-legacy/unstable-migrationalone, plus no-features —testand
clippy -D warningsfor every one ✅foreign_cannot_alias_a_recognized_profile_through_any_door(both serde flavors), crafted/self
IdentityMigrationrecords refused in JSONand dag-cbor,
cidv0_is_foreign, adversarial store backends ✅dbb94c330— success ✅just verify-release— Rust0.1.1== PEP 4400.1.1✅ContentId,RawContentId,content_id,to/from_canonical_dagcbor— no legacy, no migration leak ✅Out of scope
v0.1.1triggers publication to crates.io and PyPI,which is irreversible — it stays a human act per
RELEASING.md.ContentId's frozen profile, the profile law, the classifieralgebra, the legacy posture, or
IdentityMigration's validation. The code isfeat(identity): RawContentId + ClassifiedCid, the profile law, legacy adapters, IdentityMigration #85's as merged.
tracks them today, so item 6's hold has no recorded exit condition outside
this document — flagged rather than filed unilaterally.