Skip to content

fix: clear the review residuals on main before the 0.28.0 cut - #484

Merged
bahdotsh merged 12 commits into
mainfrom
fix/pre-release-residuals-0-28-0
Sep 30, 2026
Merged

bahdotsh merged 12 commits into
mainfrom
fix/pre-release-residuals-0-28-0

Conversation

@bahdotsh

Copy link
Copy Markdown
Member

Summary

Clears every residual that a review of a PR merged since v0.27.0 left on main, before the 0.28.0 cut. Each residual was a claim someone verified by hand once and nothing in CI verified afterwards: two release-workflow steps no run has exercised, sentences that described a different order or parser than the code, and branches whose tests passed with the branch deleted.

Twelve commits, one per residual family, each independent of the others:

Commit Came from What changes
ci(release): tell a failing npm view from a staging publish #464 The provenance verify poll keeps npm's stderr. Nothing, a warning or an E404 is staging. Any other npm error is printed, and the timeout names it instead of blaming the registry.
ci: key the iOS Rust caches on the podspec #475 cargo does not rebuild Rust crates when IPHONEOS_DEPLOYMENT_TARGET changes. Both iOS caches put the podspec's hash in shared-key. It cannot go in key, which rust-cache ignores when shared-key is set.
fix(bindings): check the deployment target inside package_xcframework #474 The gate moved into the pod's packaging function, which now takes the target. The Swift package's packager still relies on the CI job's own gate step, and the docs say so. The test drives the function with stand-ins for otool, lipo and xcodebuild, replacing line-order greps that missed 4 of 6 breaking edits.
fix(bindings): say where the Kotlin timestamp parser parts from Instant #475 Four sentences claimed parity with Instant. The parser matches Instant for every timestamp a relay sends, and refuses hour 24, an empty fraction, lowercase t/z and a leap second, which Instant accepted. \d became [0-9], pinned by a Rust guard.
test(bindings): pin a mesh wake whose service does not start #475 Robolectric's startService never returns null, so the null branch goes through an internal seam and is now tested.
ci(bindings): lint the Android library for calls newer than minSdk #475 Lint's NewApi check runs alone on the library and fails the build. Generated bindings are excluded: their one newer call sits behind a Class.forName.
test(leaf): pin the pointer guard's tokenizer #476 Four tokenizer behaviours get pins. The module doc names the three routes a one-file scan cannot follow.
fix(protocol): tell an unreadable sealed record from one that does not open #468 A read error was reported as "does not open under this store key" and counted towards WrongStoreKey. The probe skips an unreadable record, so a readable one can still prove the key, and reports the read failure, naming the record, only when nothing opens. The listing warns with the real cause and still skips the record. The warn-once memo forgets a path when it is written or removed.
test(protocol): open the file stores over a relative root #468 A new test binary pins flush_target at its call site. Before this, bypassing it passed every test.
fix(protocol): a sealed record that opens outranks a pairing-id mismatch #470 A mismatch now asks the records, and only one that opens under this MLS store's record key admits the state root. Two tests pin branches that survived mutation. Three sentences said the MLS store is written first; the state store is.
fix(protocol,mls): check key-type isolation before the state suite deletes #466 The FFI-exposed protocol-state suite deleted what it listed before any check ran, so a merging backend lost real records to it. It now checks isolation first and stops on a failure. Both suites gain a probe type related by suffix. Check count and names are unchanged.
fix(bindings): two orderings in the peer-stream managers #465 iOS stop() clears the session before ending links. Android resets the redial delay on a new connection after a disconnect. Both are pinned by a source guard.

Already closed on main, so not in this PR: the second podspec reader and the gate's error text from #474 (fixed by #475), the rustix comment from #468, and the React Native guide's appId gap (#462). The Android library's missing INTERNET permission is by design and documented in bindings/kotlin/README.md.

Verification

  • Every behavioural fix was mutation-checked: each mutant of the fix fails a test. 31 mutants across the gate, the Kotlin parser, the wake seam, the tokenizer, the sealed store, the pairing and both suites; all caught.
  • npm verify step: extracted from the YAML and run under a fake npm for four scenarios (staging then live, persistent DNS failure, a transient failure then staging, never visible). Each ends with the right annotation.
  • Android lint: green on the library. A planted java.time call fails it, and without lint.xml the only hit is the generated Cleaner.
  • Android: 551 unit tests pass in a clean copy. The other 10 are the vector-file lookup that works only inside the repository.
  • iOS: the CI bridge typecheck step, run verbatim, is clean, and a planted type error fails it.
  • A fresh-context reviewer read the whole diff before the push. It found two blocking issues, both fixed: the cache key first went in key, which rust-cache ignores, and the docs claimed the gate covered the Swift package's packager. Its five non-blocking findings were fixed too.
  • Workspace: 3,033 unit tests pass, plus the checklist below.

Not in this PR

The release cut itself, an rc tag, and the two-phone smoke test. The public API of the native packages. The ARM32 UniFFI checksum defect. Dependabot.

Type of change

  • fix: bug fix
  • test / ci

Checklist

  • Commits follow Conventional Commits
  • cargo fmt --all -- --check
  • cargo clippy --workspace --locked -- -D warnings
  • cargo test --workspace --lib
  • RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps
  • Leaf no_std clippy on all four embedded targets
  • CHANGELOG updated under [Unreleased]

The provenance verify step read every empty answer as staging, so a DNS
failure or a broken .npmrc polled for twenty minutes and then blamed the
registry. Each poll now keeps npm's stderr. Nothing, a warning or an
E404 is staging; any other npm error is printed, and the timeout names
the last one.
cargo does not rebuild a Rust crate when IPHONEOS_DEPLOYMENT_TARGET
changes, so a lowered target restored objects stamped for the old one
and the minimum-OS gate failed on them. The Swift Package job and the
release's iOS job now add the podspec's hash to their cache key.
The minimum-OS gate was three calls in the build script, pinned by
greps on the order of its lines, and four of six edits that disabled it
kept the order. package_xcframework now takes the deployment target and
checks every archive before it packages anything, and the gate test
drives the function with stand-ins for otool, lipo and xcodebuild: a
refused archive packages nothing, and each slice is held to its own
ceiling. Six mutants of the function and the call are each caught.
Four sentences said the parser answers what java.time.Instant answered.
It does for every timestamp a relay sends, and not at the edges: Instant
accepted hour 24, an empty fraction, lowercase t and z, and a leap
second, and this refuses all four. The header, the Swift test comment
and the changelog now say so. The pattern matches ASCII digits: on
Android \d is ICU's and matches any Unicode digit, which a JVM test
cannot see, so a Rust guard pins it. Two tests pin every field's bound,
which mutation showed no test did.
dispatchWake treats a null from startService as a failed wake, and no
test could reach that branch: Robolectric's startService never returns
null. The start now goes through an internal seam, and a test makes it
answer null and checks the keep-alive stops at once. Removing the branch
fails it.
The java.time guard reads source text for one package, and java.time on
Android 7 was one instance of a class: any API above minSdk compiles,
passes every JVM test, and throws NoClassDefFoundError on an older
phone. The library build now runs lint's NewApi check alone, failing the
build, and the Android Library job runs it. The generated bindings are
left out: their one newer call, Cleaner, sits behind a Class.forName. A
planted java.time call fails the step.
…t see

Four tokenizer behaviours had no pin and survived mutation: string
escapes, nested block comments, raw identifiers and raw byte strings.
Each now has a case that fails when it is removed. The module doc names
the three routes a one-file token scan cannot follow, a module that
re-exports alloc, a glob over one, and a path a macro assembles, where
it had said the guard refuses any path.
…t open

open_record folded a read error into 'does not authenticate', so a
permission or disk error was logged as a record sealed under another
key, and the lost-check probe counted it towards WrongStoreKey: the
right key over one unreadable record was refused as a wrong one. The
probe now stops with an I/O error naming the record, and the listing
warns with the real cause. The listing's warn-once memo also forgets a
path when the file there is written or removed, so a record damaged
again is reported again. Five mutants are each caught.
…binary

A one-component relative root once failed its first open, because the
parent of 'keys' is the empty path and flushing it is 'not found'.
flush_target fixes that, and its unit test pins the function, but
bypassing it at its call site passed every test: each store test opens
an absolute temporary root. This opens a pair over 'keys' and 'state'
under a temporary working directory, in its own test binary because it
changes the process's current directory.
…smatch

A pairing-id mismatch refused the state root outright, even when its
sealed records opened under this MLS store's record key, which only
this identity's records do. Lose the MLS pairing file, rebind beside a
fresh state root, put the first one back, and the same identity was
refused. A mismatch now asks the records, and only one that opens
admits the root. Two tests pin branches that survived mutation: a state
store with no record beside another bound MLS store, and a state root
made again beside a bound one. Three sentences said the MLS store takes
the pairing id first; the state store does, which is the crash-safe
order, and the docs now say so.
…letes

The protocol-state conformance suite, the one exposed over the FFI and
run against real providers, cleaned up by listing a probe key type and
deleting what it listed before any check ran. On a backend that merges
key types that listing names real records, and the cleanup deleted
them; the UDL called the suite safe against a live store. It now checks
isolation first, with point writes and deletes, and stops there on a
failure, as the MLS suite already did. Both suites also write a probe
type that ends like another: the SDK's key types are related by suffix
and the probes only by prefix, so a backend listing by suffix was green
on both. The count and names of the checks are unchanged.
…group

iOS stop() ended every link while the session was still set, so a
.connected already queued passed the session check and created a link
in the emptied table. start() never clears it, and a remote that kept
its MCPeerID met a stale refused link after a restart: no preamble, a
refusal at its deadline, one wasted round. The session is now cleared
first. Android never reset the redial delay on a new group, so a new
group's first failed dial waited out the old group's backoff; a new
connection after a disconnect now resets it. Both are pinned by a
source guard, since neither manager runs in CI.
@bahdotsh
bahdotsh merged commit d5ffb42 into main Sep 30, 2026
42 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 30, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant