Skip to content

chore: release 0.6.0 — affinidi-did-common 0.4 + pre-release audit fixes - #50

Merged
stormer78 merged 2 commits into
mainfrom
did-common-0.4
Jul 19, 2026
Merged

chore: release 0.6.0 — affinidi-did-common 0.4 + pre-release audit fixes#50
stormer78 merged 2 commits into
mainfrom
did-common-0.4

Conversation

@stormer78

@stormer78 stormer78 commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator

What

Bumps affinidi-did-common from "0.3" to "0.4" and affinidi-data-integrity
from "0.7" to "0.7.7", plus a pre-release audit pass over dependencies,
documentation, and code quality.

Releases as 0.6.0, not 0.5.8 — see Why the version moved.

Both upstream dependencies are now published, so CI should be green and
Cargo.lock has been regenerated (this PR originally left it untouched
deliberately, because it could not be regenerated honestly until they landed).

Why the version moved

The audit fixes include #[non_exhaustive] on public enums that shipped through
0.5.7. That breaks downstream code matching them exhaustively, so shipping it in
a patch release would violate semver. Doing it now — rather than deferring —
means future variants of DIDWebVHError no longer force a major bump each time.

This also triggers removal of the affinidi_secrets_resolver re-export, which
was deprecated in 0.5.0 with "removed in 0.6.0" in both its doc comment and
its #[deprecated] note.

Breaking changes

  • DIDWebVHError, URLType and LogEntryValidationStatus are now
    #[non_exhaustive]. Downstream match expressions need a _ => arm.
  • The didwebvh_rs::affinidi_secrets_resolver re-export is removed. Replace
    with didwebvh_rs::prelude::Secret, or depend on affinidi-secrets-resolver
    directly. README carries a migration note.

Fixed

  • update_did() silently discarded portable on the migrate path.
    do_migrate carried its own copy-pasted parameter-overlay block that omitted
    the portable arm, so .migrate_to(..).disable_portability() built and ran
    successfully while dropping the portability change. Both paths now share a
    single apply_param_overrides helper. The regression test was verified to
    fail without the fix.
  • u32 overflow in the successor-version check. versionId is parsed from
    the untrusted log and verify_log_entry is public, so a bare id + 1 was
    reachable with attacker-controlled input: a panic in debug builds, and a
    silent wrap to 0 in release builds that would let a chain restart its
    numbering.
  • Two panics in the interactive CLI update flow. active_update_keys[0]
    was indexed unconditionally; a deactivated DID (empty update_keys) or
    deselecting every key in the prompt produced an index-out-of-bounds panic
    instead of an error.

Conformance: witness-update is now asserted to fail

The witness-update interop test was #[ignore]d with the reason "witness
proof signature on entry 2 fails verification"
. That diagnosis was wrong — the
signature verifies fine. The actual failure is a witness threshold mismatch,
and it is correct behaviour.

Entry 2 of that vector lowers its own witness config from
{threshold: 2, witnesses: [A, B]} to {threshold: 1, witnesses: [A]} and
supplies one proof. The committed resolutionResult.json expects it to resolve,
i.e. the generator evaluated the entry against the new config it declares.

didwebvh 1.0 says otherwise:

the resolver MUST confirm that the did-witness.json file contains verified
witness Data Integrity proofs from a threshold of the then active witnesses

rotating the keys authorized to update a DID or changing the witnesses for a
DID take effect only after the entry in which they are defined has been
published

The then-active threshold for entry 2 is 2; one proof is present; it is
rejected. Accepting the fixture's reading would make witnessing bypassable
an attacker holding a compromised update key could publish
{threshold: 1, witnesses: [attacker]}, sign the single required proof
themselves, and have it accepted. Bounding exactly that compromise is the point
of the witness mechanism.

So rather than weakening the verifier to match the fixture, the test is
un-ignored and inverted: witness_update_rejects_self_lowered_threshold pins
the rejection. If it ever starts passing by resolving, that is a security
regression, not fixture drift. The discrepancy should be raised against
didwebvh-test-suite.

Interop suite is now 13/13 with nothing ignored.

Documentation

  • Fixed README examples that could not compile: create_did() shown without
    .await; CreateDIDResult shown accessing did / log_entry /
    witness_proofs as fields when they are pub(crate); update_keys built
    from a bare String instead of a Multibase.
  • Fixed the MSRV badge (1.94.0 → 1.95.0), a dead LICENSE-APACHE link, stale
    versions in four install snippets, two rustdoc intra-doc links that render
    broken in plain Markdown, and an incomplete prelude listing.
  • Converted all 22 ```ignore doctests to ```no_run. Doctests went
    from 2 passing / 22 ignored to 24 passing / 0 ignored. This is what let the
    README bugs above survive; the conversion immediately surfaced a documented
    builder method (.authorization_secrets() / .witness_secrets()) that does
    not exist — the real API is a single .secrets().
  • Added a README "Conformance" section documenting the witness-update
    divergence for anyone comparing implementations.

Tests

  • Fixed an intermittent failure in tests/revoked.rs. LogEntry::save_to_file
    appends, the tests wrote to fixed paths under the git-tracked
    tests/test_vectors/, and cleanup ran only after the assertion — so any
    failed or interrupted run left a file the next run appended to, producing a
    corrupt chain. Self-perpetuating once triggered. Each test now gets its own
    TempDir, removed on drop including on panic.
  • Added regression coverage for the portable-on-migrate fix, the other overlay
    fields on the migrate path, and the u32::MAX boundary.

Dependencies & CI

  • Direct deps refreshed: async-trait 0.1.91, serde 1.0.229, thiserror
    2.0.19, tokio 1.53.0, plus anyhow/clap dev-side. tempfile added as a
    dev-dependency.
  • Dropped four stale --ignore flags from the CI audit job
    (RUSTSEC-2026-0098/0099/0104, RUSTSEC-2025-0134). Those were rooted in the
    reqwest 0.11 chain, which disappeared when this crate moved to reqwest 0.13; a
    current audit does not report them. Stale ignores silently mask an advisory if
    the dependency ever returns.
  • Gated the nightly-only bench behind a nightly feature, so
    cargo check/clippy --all-targets now works on stable instead of failing with
    E0554.

Publish ordering

  1. affinidi-did-common 0.4.0 ✅ published
  2. affinidi-data-integrity 0.7.7 ✅ published
  3. thisdidwebvh-rs 0.6.0
  4. affinidi-tdk-rs #629 then resolves and merges

Verification

  • cargo fmt --check clean
  • cargo clippy --all-features --tests --examples -- -D warnings clean
  • 528 tests + 24 doctests passing, 3 ignored (all network-gated)
  • cargo audit clean under the trimmed ignore list
  • MSRV 1.95.0 builds; --no-default-features builds; --all-targets builds on stable
  • Packages at 297 KiB

Not done, deliberately

ResolveOptions was not made #[non_exhaustive]. It is a config struct with
public fields, and non_exhaustive forbids struct-literal construction
downstream entirely — even with ..Default::default(). examples/resolve.rs
uses exactly that pattern, so it is the documented public idiom. The right
version of that change is non_exhaustive plus a builder, which is a larger
API addition than belongs in this release.

affinidi-did-common 0.4.0 promotes alsoKnownAs to a typed
Document::also_known_as field. The change is additive and this crate
needed no code changes: it neither constructs Document by struct literal
nor re-exports it from its public API.

Also pins affinidi-data-integrity to 0.7.7. It is a transitive consumer
of affinidi-did-common and, left at "0.7", resolves to the published
0.7.6 which still requires "0.3" -- putting a second copy of
affinidi-did-common in the dependency graph.

This release must reach crates.io before affinidi-did-common 0.4.0
propagates to consumers. Per affinidi-tdk-rs ADR 0003 section 3, a minor
bump of affinidi-did-common invalidates the [patch.crates-io] redirect
held by any external consumer still requiring "0.3"; leaving this crate
on "0.3" puts two copies of affinidi-did-common in the graph and breaks
downstream builds with duplicate-type errors.

Cargo.lock is intentionally left untouched: it cannot be regenerated
until affinidi-did-common 0.4.0 and affinidi-data-integrity 0.7.7 are
published.

Signed-off-by: Glenn Gore <glenn.gore@gmail.com>
@stormer78 stormer78 changed the title chore: bump affinidi-did-common to 0.4 (release 0.5.8) chore: release 0.6.0 — affinidi-did-common 0.4 + pre-release audit fixes Jul 19, 2026
Promotes the release from 0.5.8 to 0.6.0: the fixes below add
`#[non_exhaustive]` to already-published public enums, which is breaking
for downstream code matching them exhaustively.

Breaking:
- `DIDWebVHError`, `URLType`, `LogEntryValidationStatus` are now
  `#[non_exhaustive]`, so future variants no longer require a major bump.
- Removed the `affinidi_secrets_resolver` whole-crate re-export,
  deprecated since 0.5.0 and documented for removal in 0.6.0.

Fixed:
- `update_did()` no longer silently drops `portable` on the migrate path.
  `do_migrate` had a copy-pasted parameter overlay missing the `portable`
  arm, so `.migrate_to(..).disable_portability()` discarded the change.
  Both paths now share `apply_param_overrides`.
- The successor-version check no longer overflows on a `versionId` of
  `u32::MAX` — a debug panic, and a release wrap to 0 that would let a
  chain restart its numbering. Extracted as `expected_next_version_id`.
- The interactive CLI update flow no longer panics on an empty
  `active_update_keys` (deactivated DID, or all keys deselected).

Changed:
- `WebVHURL` derives `Debug`/`PartialEq`/`Eq`; `URLType` derives `Eq`.
- Regenerated `Cargo.lock` now that affinidi-did-common 0.4.0 and
  affinidi-data-integrity 0.7.7 are published; refreshed direct deps.
- Dropped four stale `--ignore` flags from the CI audit job (the
  reqwest 0.11 chain they covered is gone).
- Gated the nightly bench behind a `nightly` feature so
  `--all-targets` works on stable.

Docs:
- Fixed README examples that could not compile (missing `.await`,
  `pub(crate)` field access, a `Multibase` type error), the MSRV badge,
  a dead LICENSE link, stale versions, and the prelude listing.
- Converted all 22 `ignore` doctests to `no_run`: 24/24 now compile.
  This caught a documented builder method that does not exist.

Tests:
- `tests/revoked.rs` now uses `TempDir`. `save_to_file` appends, and the
  fixed paths were only cleaned up after the assertion, so a failed run
  poisoned every subsequent run — the source of the intermittent failure.
- `witness-update` interop vector un-ignored and inverted to assert
  rejection. The vector expects an entry that lowers its own witness
  threshold to be accepted; didwebvh 1.0 requires proofs from the "then
  active" witnesses, and accepting it would let a compromised update key
  self-approve. Interop suite is now 13/13 with nothing ignored.

Signed-off-by: Glenn Gore <glenn@affinidi.com>
@stormer78
stormer78 merged commit d3cc500 into main Jul 19, 2026
6 checks passed
@stormer78
stormer78 deleted the did-common-0.4 branch July 19, 2026 13:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant