Found by the API audit on the integration head (#479), which is the first tree that held every
lane of the 2026-09 programme at once.
1. The #399 freeze is incomplete — ten sites, ~110 types at two public paths
#399 converted most of capsule-core to pub(crate) mod + pub use, which gives one canonical
path per type. Ten sites still pair a pub mod with a re-export of the same items from the
parent, so every listed item resolves at two paths:
| # |
Site |
Duplicated |
| 1 |
capsule-core/src/backup/mod.rs:15,17-25 |
artifact::* — 10 items |
| 2 |
capsule-core/src/crypto/mod.rs:22,26,33 |
hash, primitives, verify_asset — and only these 3 of 14, so the crate is inconsistent about which crypto types get a short alias |
| 3 |
capsule-core/src/crypto/encryption/mod.rs:14-28 |
blob, keywrap, rekey, stream — 21 items |
| 4 |
capsule-core/src/crypto/provenance/mod.rs:13-22 |
action, manifest, record — 13 items |
| 5 |
capsule-core/src/crypto/authority/mod.rs:28,42 |
reference::ReferenceAuthority (its sibling openmls_authority is already private — half-converted) |
| 6 |
capsule-core/src/exif/mod.rs:1-5 |
extract, timezone |
| 7 |
capsule-core/src/ml/mod.rs:45-63 |
orchestrator, regen, registry, runner — ~37 items, the largest site |
| 8 |
capsule-core/src/validation/mod.rs:17-24 |
protocol, structural — notable because validation is part of the ungated sealing surface |
| 9 |
capsule-core/src/metadata/crdt/mod.rs:13-19 |
counter, lww, or_set (parent metadata/mod.rs is correctly converted, so the freeze stopped one level short) |
| 10 |
capsule-core/src/lib.rs:27 + media/mod.rs:75 |
DerivativeFormat at capsule_core::derivative_format:: and capsule_core::media::. Deliberate and documented, but done with a pub mod rather than a private module or #[doc(hidden)], so the second path leaks |
Both paths are live in the tree, not merely reachable: capsule-wasm/src/lib.rs:65 imports the
long form (capsule_core::crypto::encryption::stream::…) while capsule-cli/src/repair.rs:54
imports the short form (capsule_core::exif::{extract_exif, resolve_timezone}).
The converted sites are the template. capsule-core-ffi, capsule-wasm and capsule-i18n are
clean — this is a capsule-core issue only.
2. Two capsule-i18n functions with no caller outside the crate
format_message (capsule-i18n/src/format.rs:80) and format_message_in (:90), both
re-exported at lib.rs:31. Inside the crate format_message is called only by its own tests and
format_message_in only by format_message and Bundle::format; every external consumer goes
through Bundle::format.
Flagged rather than asserted dead: format_message_in is the natural entry point for formatting
a template you already hold rather than a catalog key, which is a plausible intentional API. The
decision is whether that is a supported surface — if yes it wants a caller or a doc example, if
no it wants pub(crate).
3. A ROADMAP wording correction, already applied in #479
The invocation that ordered this audit described cbor, crypto, drop, sharing,
validation, lqip and client_build as cargo features. They are not; they are the ungated
pub mod declarations in capsule-core/src/lib.rs, and what keeps them in the
--no-default-features build is the absence of a #[cfg(feature = …)], not a flag.
cargo check -p capsule-core --no-default-features --features cbor,… fails with
"does not contain these features". The surface itself is intact — plain
--no-default-features compiles, and the set is one larger than the seven
(derivative_format is also ungated, deliberately). ROADMAP.md now records it as modules.
Not in scope here
Whether capsule_core::{culling, federation, ml, media, utils, constants} should narrow to
pub(crate): each is used by capsule-core's own internals and has no external consumer, but
design/module-map.md:53-58 names culling, federation and ml as contract-owning modules,
so that is a design call rather than a mechanical one.
One concrete item that is mechanical: capsule-server/src/upload/policy.rs:43-48 restates
DERIVATIVE_CONTENT_TYPES with a comment saying it should be
capsule_core::derivative_format::DerivativeFormat::STILL_DELIVERY_ORDER mapped through mime()
but that the module "is not reachable from this crate yet: it exists only on the branch of #436".
It is reachable now — #436 is merged and the module is ungated, which is why the server can link
it with default-features = false. The stated blocker has cleared.
Found by the API audit on the integration head (#479), which is the first tree that held every
lane of the 2026-09 programme at once.
1. The #399 freeze is incomplete — ten sites, ~110 types at two public paths
#399 converted most of
capsule-coretopub(crate) mod+pub use, which gives one canonicalpath per type. Ten sites still pair a
pub modwith a re-export of the same items from theparent, so every listed item resolves at two paths:
capsule-core/src/backup/mod.rs:15,17-25artifact::*— 10 itemscapsule-core/src/crypto/mod.rs:22,26,33hash,primitives,verify_asset— and only these 3 of 14, so the crate is inconsistent about which crypto types get a short aliascapsule-core/src/crypto/encryption/mod.rs:14-28blob,keywrap,rekey,stream— 21 itemscapsule-core/src/crypto/provenance/mod.rs:13-22action,manifest,record— 13 itemscapsule-core/src/crypto/authority/mod.rs:28,42reference::ReferenceAuthority(its siblingopenmls_authorityis already private — half-converted)capsule-core/src/exif/mod.rs:1-5extract,timezonecapsule-core/src/ml/mod.rs:45-63orchestrator,regen,registry,runner— ~37 items, the largest sitecapsule-core/src/validation/mod.rs:17-24protocol,structural— notable becausevalidationis part of the ungated sealing surfacecapsule-core/src/metadata/crdt/mod.rs:13-19counter,lww,or_set(parentmetadata/mod.rsis correctly converted, so the freeze stopped one level short)capsule-core/src/lib.rs:27+media/mod.rs:75DerivativeFormatatcapsule_core::derivative_format::andcapsule_core::media::. Deliberate and documented, but done with apub modrather than a private module or#[doc(hidden)], so the second path leaksBoth paths are live in the tree, not merely reachable:
capsule-wasm/src/lib.rs:65imports thelong form (
capsule_core::crypto::encryption::stream::…) whilecapsule-cli/src/repair.rs:54imports the short form (
capsule_core::exif::{extract_exif, resolve_timezone}).The converted sites are the template.
capsule-core-ffi,capsule-wasmandcapsule-i18nareclean — this is a
capsule-coreissue only.2. Two
capsule-i18nfunctions with no caller outside the crateformat_message(capsule-i18n/src/format.rs:80) andformat_message_in(:90), bothre-exported at
lib.rs:31. Inside the crateformat_messageis called only by its own tests andformat_message_inonly byformat_messageandBundle::format; every external consumer goesthrough
Bundle::format.Flagged rather than asserted dead:
format_message_inis the natural entry point for formattinga template you already hold rather than a catalog key, which is a plausible intentional API. The
decision is whether that is a supported surface — if yes it wants a caller or a doc example, if
no it wants
pub(crate).3. A ROADMAP wording correction, already applied in #479
The invocation that ordered this audit described
cbor,crypto,drop,sharing,validation,lqipandclient_buildas cargo features. They are not; they are the ungatedpub moddeclarations incapsule-core/src/lib.rs, and what keeps them in the--no-default-featuresbuild is the absence of a#[cfg(feature = …)], not a flag.cargo check -p capsule-core --no-default-features --features cbor,…fails with"does not contain these features". The surface itself is intact — plain
--no-default-featurescompiles, and the set is one larger than the seven(
derivative_formatis also ungated, deliberately).ROADMAP.mdnow records it as modules.Not in scope here
Whether
capsule_core::{culling, federation, ml, media, utils, constants}should narrow topub(crate): each is used bycapsule-core's own internals and has no external consumer, butdesign/module-map.md:53-58namesculling,federationandmlas contract-owning modules,so that is a design call rather than a mechanical one.
One concrete item that is mechanical:
capsule-server/src/upload/policy.rs:43-48restatesDERIVATIVE_CONTENT_TYPESwith a comment saying it should becapsule_core::derivative_format::DerivativeFormat::STILL_DELIVERY_ORDERmapped throughmime()but that the module "is not reachable from this crate yet: it exists only on the branch of #436".
It is reachable now — #436 is merged and the module is ungated, which is why the server can link
it with
default-features = false. The stated blocker has cleared.