feat(ops): the ops foundation — the token layer, the custody split, the namespace binding, and the fanout registry (wave 7)
The ops module's state substrate under feature `ops`, no op handlers yet (they land with ops-json-ops/ops-fetch/ops-put): - `OpsContext` — the context type the handler pairs capture: the `Arc<Store>`, the module config, the token registry, the per- namespace converter bindings, the fanout registry (re-exported from lib.rs) - The pin-token layer (ADR-010 §4 / ADR-012 §2): CSPRNG `PinToken` (32 bytes, `getrandom 0.4` — the decided minting form; counter and derivable forms rejected as guessable capabilities), the token↔(key, owner) mapping with process lifetime (retirement at conversion-confirmed or process end; no expiry machinery), the validity domain = the minting node (peers refuse unknown tokens with present: false, never an error) - The guard-custody split: `mint_fleet` (mapping only; the put handler's guard drops at response completion — its own act; the fleet-declaration and owner-instance checks ride the mint) vs `mint_custodial` (the registry holds the guard; nothing expires; refused under a fleet declaration) - The namespace→liveness-source binding: per-namespace `PinConverter` registration; the conversion trigger decided as the embedder-called confirm verb (probe-triggered conversion rejected: a read-gated op never mutates protection), riding `RegistryCore::convert`'s ordered step under the arbitration lock - The crate-internal store-core renewal access: `Store::ops_pin_renew`/`Store::ops_pin_check` (pub(crate), cfg all (ops, postgres)) routed through the fleet host — the W6-3 named consumer; the probe composition decided as verify-the-mapping-then-renew; no public-API widening, no backend side-door - The fanout registry (POC #3 finding A3): per-digest broadcasts, bounded subscriber channels, the late-joiner degrade signal (subscribe → None, channel end → verified get), and the Stall/Drop slow-subscriber policies - Dependency posture: serde/serde_json/getrandom enter as ops-gated optional deps; the default build's dependency tree verified unchanged; gc.rs's allow-scope notes updated to the landed state Verification: cargo test (default 73/19/3, module compiled out), cargo test --features ops (84/19/3, 11 new ops unit tests), cargo test --all-features (237/125/4); clippy --all-targets -D warnings across the default/ops/all-features/pg-pure variants; cargo fmt; cargo doc --no-deps zero warnings on the default and gated-ops builds (the all-features count at the pre-existing baseline).
This commit is contained in:
1 parent
c9829836b1
commit
6feaa2ac72
6 files changed
+1501
-20
No files matched your search
+4
-1
@@ -23,7 +23,7 @@ sqlite = ["dep:rusqlite"]
|
|||||||
postgres = ["dep:tokio-postgres", "dep:deadpool-postgres"]
|
postgres = ["dep:tokio-postgres", "dep:deadpool-postgres"]
|
||||||
mem = []
|
mem = []
|
||||||
pg-lo = ["dep:tokio-postgres", "dep:deadpool-postgres"]
|
pg-lo = ["dep:tokio-postgres", "dep:deadpool-postgres"]
|
||||||
ops = ["dep:alkcall"]
|
ops = ["dep:alkcall", "dep:serde", "dep:serde_json", "dep:getrandom"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tokio = { version = "1", default-features = false, features = ["rt", "sync", "io-util", "macros", "time"] }
|
tokio = { version = "1", default-features = false, features = ["rt", "sync", "io-util", "macros", "time"] }
|
||||||
@@ -32,8 +32,11 @@ parking_lot = "0.12"
|
|||||||
|
|
||||||
alkcall = { version = "0.8.1", optional = true }
|
alkcall = { version = "0.8.1", optional = true }
|
||||||
deadpool-postgres = { version = "0.14", optional = true }
|
deadpool-postgres = { version = "0.14", optional = true }
|
||||||
|
getrandom = { version = "0.4", optional = true }
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
rusqlite = { version = "0.40", features = ["bundled"], optional = true }
|
rusqlite = { version = "0.40", features = ["bundled"], optional = true }
|
||||||
|
serde = { version = "1", features = ["derive"], optional = true }
|
||||||
|
serde_json = { version = "1", optional = true }
|
||||||
sha1 = "0.10"
|
sha1 = "0.10"
|
||||||
sha2 = "0.10"
|
sha2 = "0.10"
|
||||||
tokio-postgres = { version = "0.7", optional = true }
|
tokio-postgres = { version = "0.7", optional = true }
|
||||||
|
|||||||
@@ -81,12 +81,18 @@
|
|||||||
//! fleet trait and its registration bookkeeping in non-postgres
|
//! fleet trait and its registration bookkeeping in non-postgres
|
||||||
//! builds (no implementer or consumer compiles without feature
|
//! builds (no implementer or consumer compiles without feature
|
||||||
//! `postgres`), the seam's `pin_check` clause — the pins-only
|
//! `postgres`), the seam's `pin_check` clause — the pins-only
|
||||||
//! classifier, deliberately unconsumed (its remaining consumer is
|
//! classifier, whose consumer has landed (wave 7's
|
||||||
//! wave 7's ops renewal probe), and the ADR-005 conversion-ordering
|
//! ops-foundation: the ops module's renewal probe reaches it through
|
||||||
//! primitive (`GcHome::convert`/`RegistryCore::convert`) plus the
|
//! the store core's crate-internal `ops_pin_check` crossing, the
|
||||||
//! `GcHome::pin_count` observation aid — test-consumed today, with
|
//! have handler's renewal-verification step), and the ADR-005
|
||||||
//! the conversion's in-sweep consumer being wave 7's liveness edit
|
//! conversion-ordering primitive (`GcHome::convert`/
|
||||||
//! flow.
|
//! `RegistryCore::convert`) plus the
|
||||||
|
//! `GcHome::pin_count` observation aid — the conversion's
|
||||||
|
//! guard-custody consumer has also landed (the ops module's confirm
|
||||||
|
//! verb converts held guards at the embedder's confirmed
|
||||||
|
//! registration), leaving `convert`/`pin_count` test-consumed
|
||||||
|
//! today, with the conversion's in-sweep consumer being wave 7's
|
||||||
|
//! liveness edit flow.
|
||||||
|
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,12 @@ pub mod pg_lo;
|
|||||||
#[cfg(feature = "ops")]
|
#[cfg(feature = "ops")]
|
||||||
pub mod ops;
|
pub mod ops;
|
||||||
|
|
||||||
|
#[cfg(feature = "ops")]
|
||||||
|
pub use ops::{
|
||||||
|
FanoutChunk, FanoutPublisher, FanoutReceiver, FanoutRegistry, OpsConfig, OpsContext,
|
||||||
|
PinConverter, PinToken, RenewalOutcome, SlowSubscriberPolicy, TokenRegistry,
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(any(feature = "postgres", feature = "pg-lo"))]
|
#[cfg(any(feature = "postgres", feature = "pg-lo"))]
|
||||||
mod pg_common;
|
mod pg_common;
|
||||||
|
|
||||||
|
|||||||
+1296
-2
File diff suppressed because it is too large.
Load diff
@@ -1834,6 +1834,55 @@ impl Store {
|
|||||||
pub(crate) fn gc(&self) -> &GcHome {
|
pub(crate) fn gc(&self) -> &GcHome {
|
||||||
&self.gc
|
&self.gc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// **The ops module's crate-internal renewal access — `pin_renew`**
|
||||||
|
/// (ADR-010 §4: "the ops module is in-crate and crosses the seam by
|
||||||
|
/// construction, the same way the put handler holds the `Pin`
|
||||||
|
/// guard"): the have handler's renewal step's store-core crossing,
|
||||||
|
/// routed through the fleet host. This verb is the crossing's named
|
||||||
|
/// shape — the store core owns the seam's reachability, so a
|
||||||
|
/// *consumer* never reaches the `EngineFleetState` clause, and no
|
||||||
|
/// public API widens for this (ADR-001 §4's facade-consumer rule:
|
||||||
|
/// this and [`Store::gc`] are the ops module's only store-core
|
||||||
|
/// access crate-internally — the facade plus exactly this seam).
|
||||||
|
///
|
||||||
|
/// Non-fleet stores answer `Err`: the pin row's renewal is the
|
||||||
|
/// fleet form's act (under `fleet: false` the token is the
|
||||||
|
/// in-process guard's custody — nothing expires — and the renewal
|
||||||
|
/// probe answers from custody, never through this verb; the
|
||||||
|
/// ops-layer custody branch is upstream of this call).
|
||||||
|
#[cfg(all(feature = "ops", feature = "postgres"))]
|
||||||
|
pub(crate) async fn ops_pin_renew(&self, key: &[u8], owner: &[u8]) -> Result<bool, Error> {
|
||||||
|
match self.fleet_sql_host() {
|
||||||
|
Some(host) => EngineFleetState::pin_renew(&**host, key, owner).await,
|
||||||
|
None => Err(Error::Io(
|
||||||
|
"pin renewal outside a declared fleet store: under fleet: false the token \
|
||||||
|
is the in-process guard's custody (nothing expires) — the renewal probe \
|
||||||
|
answers from the ops layer's custody, never through this verb"
|
||||||
|
.to_owned(),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// **The ops module's crate-internal renewal access — `pin_check`**
|
||||||
|
/// (ADR-010 §2's engine-state seam; the ADR-008 row classifier):
|
||||||
|
/// the have handler's renewal-verification step's store-core
|
||||||
|
/// crossing (the clause's named consumer — the wave-6 review's
|
||||||
|
/// W6-3 note), routed through the fleet host under the same
|
||||||
|
/// ownership rule as [`Store::ops_pin_renew`]: store-core-owned,
|
||||||
|
/// never public API, never a backend side-door.
|
||||||
|
#[cfg(all(feature = "ops", feature = "postgres"))]
|
||||||
|
pub(crate) async fn ops_pin_check(&self, key: &[u8]) -> Result<crate::facade::PinState, Error> {
|
||||||
|
match self.fleet_sql_host() {
|
||||||
|
Some(host) => EngineFleetState::pin_check(&**host, key).await,
|
||||||
|
None => Err(Error::Io(
|
||||||
|
"pin check outside a declared fleet store: the pin-row classification \
|
||||||
|
is the fleet form's act (ADR-010 §2) — under fleet: false the in-process \
|
||||||
|
guard's custody is the classification, and the ops layer holds it"
|
||||||
|
.to_owned(),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The read path (store-api.md §Read path): `get`, `stat`,
|
/// The read path (store-api.md §Read path): `get`, `stat`,
|
||||||
|
|||||||
+134
-11
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
id: ops-foundation
|
id: ops-foundation
|
||||||
name: the ops module's substrate — the token layer, the namespace binding, and the fanout registry
|
name: the ops module's substrate — the token layer, the namespace binding, and the fanout registry
|
||||||
status: pending
|
status: completed
|
||||||
depends_on: []
|
depends_on: []
|
||||||
scope: moderate
|
scope: moderate
|
||||||
risk: medium
|
risk: medium
|
||||||
@@ -98,30 +98,30 @@ below — no backend side-door, no engine types.
|
|||||||
|
|
||||||
## Acceptance Criteria
|
## Acceptance Criteria
|
||||||
|
|
||||||
- [ ] `src/ops.rs` carries the module structure under feature `ops`:
|
- [x] `src/ops.rs` carries the module structure under feature `ops`:
|
||||||
the ops context, the token layer, the namespace binding, the
|
the ops context, the token layer, the namespace binding, the
|
||||||
fanout registry — no op handlers yet (they land in
|
fanout registry — no op handlers yet (they land in
|
||||||
ops-json-ops/ops-fetch/ops-put)
|
ops-json-ops/ops-fetch/ops-put)
|
||||||
- [ ] The token layer: opaque, unguessable minting (the CSPRNG design
|
- [x] The token layer: opaque, unguessable minting (the CSPRNG design
|
||||||
point decided and recorded); the token↔(key, owner) mapping with
|
point decided and recorded); the token↔(key, owner) mapping with
|
||||||
process lifetime; validity domain = the minting node (a
|
process lifetime; validity domain = the minting node (a
|
||||||
per-process registry; no cross-node form exists)
|
per-process registry; no cross-node form exists)
|
||||||
- [ ] The guard-custody design decided and recorded: fleet — the
|
- [x] The guard-custody design decided and recorded: fleet — the
|
||||||
guard drops at response completion (the row's expiry is the
|
guard drops at response completion (the row's expiry is the
|
||||||
liveness signal); non-fleet — the registry holds the guard
|
liveness signal); non-fleet — the registry holds the guard
|
||||||
(nothing expires) until conversion
|
(nothing expires) until conversion
|
||||||
- [ ] The namespace→liveness-source binding: per-namespace converter
|
- [x] The namespace→liveness-source binding: per-namespace converter
|
||||||
registration riding `RegistryCore::convert`; the conversion
|
registration riding `RegistryCore::convert`; the conversion
|
||||||
trigger design decided and recorded
|
trigger design decided and recorded
|
||||||
- [ ] The crate-internal renewal access: the ops module reaches
|
- [x] The crate-internal renewal access: the ops module reaches
|
||||||
`pin_renew`/`pin_check` through the fleet host (in-crate seam
|
`pin_renew`/`pin_check` through the fleet host (in-crate seam
|
||||||
crossing; no public-API widening; no backend side-door)
|
crossing; no public-API widening; no backend side-door)
|
||||||
- [ ] The fanout registry: per-digest broadcast, bounded subscriber
|
- [x] The fanout registry: per-digest broadcast, bounded subscriber
|
||||||
channels, the late-joiner degrade signal, the
|
channels, the late-joiner degrade signal, the
|
||||||
drop-and-late-join policy point recorded
|
drop-and-late-join policy point recorded
|
||||||
- [ ] New dependencies (`serde`, `serde_json`, the CSPRNG) are
|
- [x] New dependencies (`serde`, `serde_json`, the CSPRNG) are
|
||||||
`ops`-gated; the default build's dependency tree is unchanged
|
`ops`-gated; the default build's dependency tree is unchanged
|
||||||
- [ ] Unit tests green; `cargo test` (default) green with the module
|
- [x] Unit tests green; `cargo test` (default) green with the module
|
||||||
compiled out; `cargo test --features ops` green;
|
compiled out; `cargo test --features ops` green;
|
||||||
`cargo clippy --all-targets -- -D warnings` and
|
`cargo clippy --all-targets -- -D warnings` and
|
||||||
`cargo fmt --check` green on both shapes; `cargo doc --no-deps`
|
`cargo fmt --check` green on both shapes; `cargo doc --no-deps`
|
||||||
@@ -153,8 +153,131 @@ below — no backend side-door, no engine types.
|
|||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
> To be filled by implementation agent
|
> Landed as `src/ops.rs` under feature `ops` (the default build
|
||||||
|
> compiles the module out; `cargo tree` default-vs-HEAD verified
|
||||||
|
> byte-unchanged). The store access is exactly the facade plus one
|
||||||
|
> crate-internal seam: `Store::ops_pin_renew`/`Store::ops_pin_check`
|
||||||
|
> (store.rs, `pub(crate)`, cfg all(ops, postgres)) routing through the
|
||||||
|
> existing `fleet_sql_host` — the store core owns the crossing; no
|
||||||
|
> engine type or `Backend` name reaches the ops module; no public API
|
||||||
|
> widened. The fanout registry (`FanoutRegistry`) and the token
|
||||||
|
> registry (`TokenRegistry`) are constructible standalone; the
|
||||||
|
> custody-validated mint verbs live on the context.
|
||||||
|
>
|
||||||
|
> **Decided design points (recorded on the module):**
|
||||||
|
>
|
||||||
|
> - **Token minting form: a CSPRNG id** — `PinToken` = 32 bytes from
|
||||||
|
> `getrandom` (v0.4, the same version alkcall 0.8 pulls — no
|
||||||
|
> duplication). Rejected: a counter (a token is a capability; a
|
||||||
|
> guessable capability is broken) and derivable forms (key/owner-
|
||||||
|
> derived is equally guessable and no longer opaque). Wire form hex
|
||||||
|
> (lowercase canonical, decode case-insensitive); `Debug` masks the
|
||||||
|
> bytes — the token never lands in logs/panics/errors. Malformed hex
|
||||||
|
> ingress returns `None` (the have handler maps it to the
|
||||||
|
> unknown-token answer, present: false — never an error; no new
|
||||||
|
> error-family member invented).
|
||||||
|
> - **Mapping retention**: conversion-confirmed (the confirm verb's
|
||||||
|
> success removes the entry) or process end; no expiry machinery on
|
||||||
|
> the mapping — the pin row's expiry is the fleet liveness signal,
|
||||||
|
> nothing expires non-fleet.
|
||||||
|
> - **Custody split**: `OpsContext::mint_fleet` (mapping only; owner
|
||||||
|
> validated against the bound store's instance id; refused over a
|
||||||
|
> non-fleet-declared store) vs `OpsContext::mint_custodial` (the
|
||||||
|
> registry takes the `Pin` guard; refused on a declared fleet
|
||||||
|
> store). The fleet put handler's guard-drop-at-response-completion
|
||||||
|
> stays the handler's act (the mint deliberately takes no guard);
|
||||||
|
> enforcement of the mirror refusal is structural at the mint verbs.
|
||||||
|
> - **Conversion trigger: an embedder-called confirm verb**
|
||||||
|
> (`OpsContext::confirm(namespace, token)`). Probe-triggered
|
||||||
|
> conversion rejected: have is a read-gated op whose caller is the
|
||||||
|
> remote putter — a protection-lifecycle mutation keyed off it would
|
||||||
|
> give a read op write semantics, while the hand-over contract's
|
||||||
|
> duty text puts the observation (have returns present after
|
||||||
|
> registration) and the act (confirm) on the embedder. Refusals
|
||||||
|
> (unknown/mismatched-namespace/unbound-converter) keep the mapping
|
||||||
|
> intact; the guarded conversion rides `GcHome::convert`'s ordered
|
||||||
|
> step (release + registration under the one arbitration-lock
|
||||||
|
> section; the converter's act is documented as quick, non-blocking
|
||||||
|
> — the `LivenessSource::contains` discipline), the fleet conversion
|
||||||
|
> invokes the converter act with no pin to release.
|
||||||
|
> - **Renewal probe composition: verify-the-mapping-then-renew** —
|
||||||
|
> (a) token lookup (validity domain; unknown → `UnknownToken` →
|
||||||
|
> present: false, the peers' refusal form), (b) the one-token-one-
|
||||||
|
> row digest check (`DigestMismatch`), (c) the store core's
|
||||||
|
> `ops_pin_check` (the W6-3 named consumer) — a row not live
|
||||||
|
> answers `Expired` with no renewal write, (d) `ops_pin_renew` —
|
||||||
|
> its never-resurrect bool is the final answer (a check/renew race
|
||||||
|
> lands on the same Expired). Renew-answers-the-verification
|
||||||
|
> rejected: the verify steps are where the validity domain and the
|
||||||
|
> row bind are enforced. Non-fleet answers from custody (nothing
|
||||||
|
> expires; no engine call). All engine failures fold to
|
||||||
|
> `present: false` — the probes stay probes (ADR-008's discipline).
|
||||||
|
> - **Fanout mechanics**: per-digest (`Key`) map of live broadcasts;
|
||||||
|
> `begin` → exclusive `FanoutPublisher` (`None` when already live —
|
||||||
|
> the producer continues store-arm-only, CAS guarantees identical
|
||||||
|
> bytes); `subscribe` → bounded-channel arm or `None` (the
|
||||||
|
> late-joiner/absent/bound-exhausted degrade signal; taps receive
|
||||||
|
> from the tap moment onward — the A3 tap-time-set shape, the whole
|
||||||
|
> blob's authority stays the verified get); per-broadcast
|
||||||
|
> `SlowSubscriberPolicy::{Stall, Drop}` — Stall is the put's form
|
||||||
|
> (bounded backpressure, the put must land), Drop is the
|
||||||
|
> drop-and-late-join escape (a full arm is dropped, its channel
|
||||||
|
> closes, the consumer degrades); publisher finish-or-drop removes
|
||||||
|
> the entry, existing arms see channel end, late joiners see
|
||||||
|
> `subscribe` → `None` — either way the degrade is the verified
|
||||||
|
> get. Arms are cloned out under the lock and driven outside it
|
||||||
|
> (nothing async under the lock); closed/full arms are reaped at
|
||||||
|
> broadcast time (channels never grow unbounded).
|
||||||
|
> - **The renewal-cadence boundary**: documented on the module (mechanism
|
||||||
|
> here; the TTL/3 cadence is embedder composition, ADR-013 §1 — no
|
||||||
|
> in-crate worker, none may exist).
|
||||||
|
>
|
||||||
|
> **Scope notes**: gc.rs's allow(dead_code) module doc updated —
|
||||||
|
> `pin_check`'s W6-3 "deliberately unconsumed" note now reads its
|
||||||
|
> landed state (the ops module's renewal probe, through the store
|
||||||
|
> core's `ops_pin_check`), and `convert`'s note names the confirm
|
||||||
|
> verb as the guard-custody consumer. `serde`/`serde_json`
|
||||||
|
> (features derive / default) entered as ops-gated optional deps now
|
||||||
|
> (the payload-handling posture's shape) — first consumed by
|
||||||
|
> ops-json-ops/ops-put; the default build's dependency tree is
|
||||||
|
> verified unchanged (`cargo tree` diff against HEAD).
|
||||||
|
>
|
||||||
|
> The fleet forms against a *live* fleet store (a real `pin_check` →
|
||||||
|
> `pin_renew` row cycle; the `mint_custodial`-refused-under-fleet
|
||||||
|
> arm) need the harness server (wave 6's fleet column pattern) and
|
||||||
|
> land with the handler tasks — the foundation's unit tests cover the
|
||||||
|
> custody branching (registry-level fleet entry: no guard held,
|
||||||
|
> renewal degrades closed, conversion is the converter act alone;
|
||||||
|
> custody-mint validation: the fleet mint refused over a non-fleet
|
||||||
|
> store; the custodial path end to end) on the store-free and
|
||||||
|
> mem/sqlite shapes.
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
> To be filled on completion
|
> Completed 2026-10-12 (wave 7, first task of the decomposition). The
|
||||||
|
> ops module's substrate landed: `OpsContext` (the `Arc<Store>` +
|
||||||
|
> config + token registry + converters + fanout binding the handler
|
||||||
|
> pairs capture), the pin-token layer (CSPRNG `PinToken`, the
|
||||||
|
> process-lifetime token↔(key, owner) mapping, validity domain = the
|
||||||
|
> minting node, retention = conversion-confirmed-or-process-end), the
|
||||||
|
> guard-custody split (`mint_fleet`: mapping-only, the guard's drop
|
||||||
|
> at response completion is the put handler's act; `mint_custodial`:
|
||||||
|
> the registry holds the guard, nothing expires), the
|
||||||
|
> namespace→liveness-source binding (`PinConverter` per-namespace
|
||||||
|
> registration; the conversion trigger is the embedder-called
|
||||||
|
> confirm verb riding the ordered-step conversion), the crate-internal
|
||||||
|
> store-core renewal access (`Store::ops_pin_renew`/`ops_pin_check` —
|
||||||
|
> the W6-3 consumer; ADR-012 §2's verify-the-mapping-then-renew probe
|
||||||
|
> composition), and the fanout registry (per-digest broadcast,
|
||||||
|
> bounded subscriber channels, the late-joiner degrade signal, the
|
||||||
|
> Stall/Drop slow-subscriber policies) — no op handlers, per the task
|
||||||
|
> text. Five unit tests' worth of mechanics green (11 test fns);
|
||||||
|
> gates green on both shapes: `cargo test` (default, 73/19/3 with the
|
||||||
|
> module compiled out), `cargo test --features ops` (84/19/3),
|
||||||
|
> `cargo test --all-features` (237/125/4), clippy
|
||||||
|
> `--all-targets -D warnings` across the default/ops/all-features/
|
||||||
|
> pg-pure variants, `cargo fmt`, `cargo doc --no-deps` zero warnings
|
||||||
|
> on the default and gated-ops builds (the all-features build's
|
||||||
|
> count is the pre-existing private-link baseline, nothing added).
|
||||||
|
> Dependencies: `serde` (derive), `serde_json`, `getrandom 0.4` —
|
||||||
|
> all `ops`-gated; the default dependency tree verified unchanged.
|
||||||
Reference in new issue
Block a user