fix(review-008 audit): adopted-entry drop guard; explicit-ALPN guards; review 009

Post-landing audit of the 0.7.1 -> 0.8.0 remediation diff: two
hardening guards, one rejection-posture fix, two log/message
corrections, and the deferred coverage debt filed as review 009.

- RelayPlan owns the producer-leg ChannelManager and reclaims the
  adopted spoke channel_id via a Drop guard (replaces the pump
  handler's post-pump_bidi explicit reclaim). Closes the leak
  windows the pump's normal path cannot reach: the wrapper's
  establishment bound expiring after the adopt, and the pump
  handler's early-return arms (plan absent, downcast failure,
  try_unwrap failure, accept_bi failure). The send-half drop still
  EOFs the spoke leg via the mux pump's implicit-EOF sentinel, so
  the spoke-side cascade is unchanged. ADR-051 §6 documents the
  closed post-adopt window (the pre-adopt §6 window and the
  inside-adopt_channel cancellation point stay as documented).
- rebuild_spec_for trims and rejects empty/whitespace
  channel_open_alpn strings — an empty explicit string previously
  overrode a sane name-derived ALPN.
- op_name_is_standard_channel_open_shape applies the same
  empty-segment guard as the derivation: channels//sub no longer
  serializes boolean-only and then reconstructs unmarked (silent
  stub for a marked op); the explicit string rides instead.
- reserved_reply_key_call_error interpolates RESERVED_REPLY_KEY;
  the establisher-bug log fires at warn! (programming error).
- Regression tests: the plan drop guard, the empty-ALPN fallback,
  the empty-segment shape check (672 tests, 3 new).
- CHANGELOG [Unreleased] entry for the audit fixes.
- docs/reviews/009 — the audit's deferred test-coverage gaps
  (template failure arms, filtered/only, batch reserved key,
  wire failure path, golden pins, derivation edge shapes, builder
  overwrite semantics), each with the test to add and gates.

Verification: cargo test 672 passed; clippy --all-targets -D
warnings clean; fmt --check clean; doc --no-deps clean; wasm32
check clean.
This commit is contained in:
2026-09-18 06:19:56 +00:00
parent 50182d7298
commit 54c2a3f941
8 changed files with 416 additions and 28 deletions
+40
View File
@@ -4,6 +4,46 @@ All notable changes to this crate are documented here. The format is
based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
this crate adheres to [Semantic Versioning](https://semver.org/).
## [Unreleased]
Post-landing audit of review 008's remediation (0.7.1 → 0.8.0) — two
hardening guards, two log/error-message corrections, one
rejection-posture fix, and the ADR-051 §6 residual update. No wire
changes; all behavior deltas are failure-path.
### Fixed
- **Adopted-entry teardown on every relay plan path** (ADR-051 §6) —
the relay's `RelayPlan` now owns the producer-leg
`ChannelManager` and reclaims the adopted spoke `channel_id` via a
`Drop` guard, instead of the pump handler's post-`pump_bidi`
explicit reclaim. This closes the leak windows the pump's normal
path cannot reach: the open wrapper's establishment bound expiring
after the adopt (the plan dropped before the pump ever spawns) and
the pump handler's early-return arms (plan absent, downcast
failure, `try_unwrap` failure, `accept_bi` failure). Dropping the
plan's send half still EOFs the spoke side via the mux pump's
implicit-EOF sentinel, so the spoke handler reclaims through the
same cascade as before. Regression test:
`relay_plan_drop_reclaims_the_adopted_producer_leg_entry`.
- **Empty/whitespace `channel_open_alpn` rejected at rebuild** — an
explicit string of `""` (or whitespace-only) previously overrode a
sane name-derived ALPN, poisoning the marker for every consumer
from one misconfigured producer; `rebuild_spec_for` now trims and
rejects empties, falling back to the derivation.
- **Empty-segment standard-shape names** — `channels//sub` was
serialized boolean-only (the standard-shape check saw flavor
`sub`) but reconstructed UNMARKED (the derivation's empty-segment
guard returned `None`) — a silent stub for a marked op. The
standard-shape check now applies the same empty-segment guard, so
the explicit `channel_open_alpn` string rides the wire and the
marker reconstructs.
- **Reserved-reply-key error text and log level** — the
`channel:open_failed` message interpolates `RESERVED_REPLY_KEY`
instead of hardcoding `"channel_id"` (the two could drift), and the
establisher-bug log fires at `warn!` (a programming error), not
`debug!`.
## [0.8.0] - 2026-09-18
Review 008's remediation lands in full — the graduation upstream asks
@@ -214,6 +214,23 @@ relays. The end consumer never authenticates to the spoke directly.
the window deterministically by severing the spoke leg and asserts
the spoke-side reclaim + ledger decrement. A channel never outlives
its leg lifetimes; never a leak past the connection's lifetime.
- **Adopted-entry teardown on every plan path** — the hub's
producer-leg entry (the `adopt_channel` state) is owned by the
relay's `RelayPlan` via a `Drop` guard, so it reclaims whenever the
plan dies: the normal pump completion, the pump handler's
early-return arms (plan absent, downcast failure, `try_unwrap`
failure, `accept_bi` failure), and the wrapper's establishment
bound expiring after the adopt (the plan dropped before the pump
ever spawns). Dropping the plan's send half also EOFs the spoke
side through the mux pump's implicit-EOF sentinel (REQ-CH-01), so
the spoke handler reclaims through the same cascade the normal
path uses. This closes the adopt-then-timeout window the landing
review surfaced (the §6 window above is the *pre-adopt* window;
this is the *post-adopt* one, now closed by construction). The
one remaining cancellation point *inside* `adopt_channel`'s own
await is the same pre-existing client-side window
(`ChannelClient::open_channel`'s adopt has it too) — bounded by
the leg's transport-EOF `clear_all`, not by the plan.
- **Multi-hop relays** — a chain of relays composes (each hop is a
hub leg pair), with the compounding-bound note above. No special
machinery.
@@ -0,0 +1,208 @@
# Review 009 — Post-landing audit of the review-008 remediation (0.7.1 → 0.8.0)
## Status
Open — filed 2026-09-18 from the post-landing audit of the six commits
`82ddddf..50182d7` (review 008's three units). Scope: correctness
review of the full diff, test-coverage mapping, and the classic
implementation-issue sweep. Verified against HEAD at filing (0.8.0 +
the audit's inline hardening commit): 672 tests pass, clippy/fmt/doc
clean, wasm check clean.
The audit's code findings were fixed inline at filing time (see the
CHANGELOG [Unreleased] entry): the adopted-entry leak windows
(audit F-1/F-2 — `RelayPlan` drop guard), the empty explicit
`channel_open_alpn` (audit F-3), the `channels//sub` silent-stub
shape (audit N-4), and the reserved-key message/log-level nits
(audit N-1/N-2). ADR-051 §6 documents the closed post-adopt window.
What remains from the audit is the **test-coverage debt** below —
deliberately deferred to this review so the fix commit stayed small.
Findings continue the review numbering with prefix `C` (coverage).
## Scope
Test-coverage gaps in the 0.8.0 surface: the Unit 3 modules
(`src/channels/relay.rs`, `src/channels/hub_leg.rs`) and the Unit 1/2
diff surfaces (`src/channels/operations.rs`, `src/channels/client.rs`,
`src/client/from_call.rs`, `src/registry/discovery.rs`). No production
code changes are requested by this review unless a gap's write-up
says otherwise; each finding names the test(s) to add and what they
pin. None block a consumer — the e2e gates the review-008 plan pinned
are all landed and passing; these gaps are the paths the gates never
walk.
## C-1: `HubLegTemplate::install_hook` failure arms are loud-only-in-code
**Finding.** `src/channels/hub_leg.rs:232-275` — four install arms end
the leg with only a `tracing::warn!`: generic channel ops registration
failure, plain-bundle registration failure, relay-openable
registration failure, bootstrap-discovery install failure. ADR-051 §6
pins the posture as "loud, never a silent stub" and only the
relay-openable arm is test-pinned
(`hub_leg_tests.rs` `template_pub_typed_marked_spec_is_loud_at_install`).
The other three are near-unreachable (generic ops and bootstrap
discovery cannot realistically fail; plain bundles were already
registered on the producing side) — but the ADR's posture claim rests
on code that no test walks.
**Requested change.** Unit-test the three unpinned arms. The plain
routes: (a) a plain bundle that fails registration — e.g. a duplicate
name between a plain bundle and the generic channel ops — asserts the
install task ends before `run_loop_single_stream`; (b) the same shape
for a relay-openable failure is already pinned; (c) the
generic-ops/bootstrap-discovery arms need an injectable failure seam
if they are to be tested honestly — if that is disproportionate,
instead demote the claim: document in ADR-051 §5 that only the
relay-openable arm is test-pinned and the others are
best-effort-loud, so the ADR and the code say the same thing.
**Verification gates:**
1. The template's install task provably ends (channel 0 never
dispatches) on each exercised failure arm.
2. The ADR-051 §5/§6 text matches what is actually pinned.
## C-2: `HubLegImports::filtered` / `only` have no test
**Finding.** `src/channels/hub_leg.rs:91-111` — the per-consumer
op-subset filter, the mechanism behind ADR-051 §4's "per-consumer ACL
differentiation is a composition consequence" note, has no unit test.
Trivial code, but it is exported pub API (`HubLegImports` is a crate
export) and the filter story is load-bearing for the hub/spoke family.
**Requested change.** Unit tests: `from_bundles` splits by marker;
`filtered`/`only` partition both halves; a `#[must_use]` misuse
compiles with a warning (already enforced by the attribute — just
exercise the two builders).
**Verification gates:**
1. `only(&["channels/tunnel/direct"])` keeps the marked direct spec,
drops everything else, plain bundle untouched by the marked list.
2. An empty `only` stash installs a leg that serves only the generic
ops + discovery (no re-exposed ops in `services/list`).
## C-3: Batch-form reserved reply key
**Finding.** `src/channels/operations.rs` — the reserved-key teardown
test (`with_reply_field("channel_id", …)`, `operations.rs:~2606`)
covers only the builder form. The wrapper's check inspects the merged
map, so `with_reply_fields(map)` smuggling `channel_id` is covered by
construction — but the batch path is the shape a hub's
`Establishment::with_reply_fields(relay_map)` will actually use, and
it is unasserted.
**Requested change.** One-line extension of the existing test (or a
sibling): the batch form fails with reason `handler_error`, channel
torn down, ledger decremented.
**Verification gates:**
1. `Establishment::default().with_reply_fields(json_map_containing_channel_id)`
`channel:open_failed` / `handler_error`; `count_for == 0` after.
## C-4: `open_channel_with_reply` wire failure path
**Finding.** `src/channels/client.rs:1654` — the new client API's e2e
test covers the success path only. The failure path (a
`channel:open_failed` resolving through `open_channel_with_reply`) is
covered only indirectly (wrapper-level test + the generic
`establisher_failure_resolves_typed_open_failed_on_the_client`).
Low risk — the parse path is shared with `open_channel` — but the new
pub API's error path has no direct pin.
**Requested change.** One e2e test against the existing
establisher-always-fails harness: `open_channel_with_reply` resolves
`Err(ChannelOpenError::CallFailed)` with the `channel:open_failed`
code and details intact.
**Verification gates:**
1. The full reply shape (`reason`, `message` in `details`) is
assertable by the caller through the new API.
## C-5: Byte-identical claims are shape-pinned, not golden-pinned
**Finding.** Two "byte-identical" claims are pinned structurally
(key-set equality), not by literal:
- `run_open_wrapper_without_reply_fields_is_byte_identical_to_pre_amendment`
(`operations.rs:2553`) asserts `v == json!({"channel_id":
v["channel_id"]})` — pins the exact key set but builds the expected
from the actual (a wrong-typed `channel_id` value would pass).
- The Unit-2 standard-shape wire payload
(`spec_standard_shape_channel_open_stays_boolean_only`) pins the
key *absence* of `channel_open_alpn` (the real pin, solid) but not
the payload's full key set.
**Requested change.** Golden-pin both: assert serialized bytes or a
literal `json!` against the actual for the no-fields reply, and a
full-object comparison for the standard-shape payload. Cheap, and it
converts "structurally equal" into "these exact bytes" for the two
claims the ADRs advertise as wire-stable.
**Verification gates:**
1. The no-fields open reply equals `json!({"channel_id": <exact
literal>})` — not a self-referential compare.
2. The standard-shape `services/schema` payload compares equal to a
literal object including key order (serde_json default map).
## C-6: `derive_alpn_from_op_name` edge shapes unpinned
**Finding.** `src/client/from_call.rs:326-337` — the empty-segment
guard (`segment.is_empty()`) and the bare-no-slash name have no unit
test, and the 4-segment name behavior *changed* (pre-amendment:
`None`; now: `Some("x/sub")`) without any test noticing. The audit
closed the `channels//sub` serialization half (N-4); the derivation
function's own edge-shape unit tests remain unlanded.
**Requested change.** Unit tests over the derivation directly:
`"channels//sub"` → `None`; `"channels//direct"` → `None`;
`"channels"` → `None`; `"channels/x/sub/extra"` → `Some("alk/x/sub")`
(the deliberate strict-superset behavior, worth pinning as intended);
`"channels/alk/tty/sub"` → `Some("alk/tty")` (the verbatim case).
**Verification gates:**
1. All five shapes assert as above; the 4-segment case is annotated
as a behavior change vs the pre-amendment derivation.
## C-7: Builder overwrite semantics unpinned
**Finding.** `src/channels/operations.rs:420-434` — two
`with_reply_field("same-key", …)` calls silently last-win; the batch
form extends (also last-win). Establisher-own concern, nit-level, but
it is pub-API behavior a consumer will rely on or be confused by.
**Requested change.** Either pin last-win with a test (and one doc
sentence on `with_reply_field`), or reject duplicates loudly at
build time. Prefer pinning: rejection adds a failure mode with no
consumer ask behind it.
**Verification gates:**
1. `.with_reply_field("k", v1).with_reply_field("k", v2)` →
`reply_fields()["k"] == v2`.
## Non-goals (recorded to bound the review)
- **No wire-format or API changes** — everything above is tests plus
at most doc text. The audit's code findings are already landed;
they are not re-opened here.
- **No new ADRs** — C-1's documentation route (if chosen) edits
ADR-051 §5/§6 text only.
- **alktunnels-side work** (the bind-first establisher, the listen-op
spec) sequences after this review as planned; nothing here blocks
it.
## References
- ADR-051 §6 (the post-adopt teardown bullet the drop guard landed;
C-1 pins its assembly-arm siblings)
- ADR-047 amendment 3 (C-6's derivation shapes), ADR-049 amendment 3
(C-3/C-7's reply projection), ADR-047 (C-5's wire-stability claims)
- CHANGELOG [Unreleased] — the audit's inline fixes this review
complements
- Review 008 — the remediation whose surface this review audits
+3 -3
View File
@@ -789,12 +789,12 @@ fn establishment_timeout_call_error() -> CallError {
fn reserved_reply_key_call_error() -> CallError {
CallError::new(
CHANNEL_OPEN_FAILED,
"establisher supplied the reserved reply key `channel_id`",
format!("establisher supplied the reserved reply key `{RESERVED_REPLY_KEY}`"),
false,
)
.with_details(json!({
"reason": "handler_error",
"message": "establisher supplied the reserved reply key `channel_id`",
"message": format!("establisher supplied the reserved reply key `{RESERVED_REPLY_KEY}`"),
}))
}
@@ -929,7 +929,7 @@ async fn run_open_wrapper(
.as_ref()
.is_some_and(|m| m.contains_key(RESERVED_REPLY_KEY))
{
tracing::debug!(
tracing::warn!(
channel_id = id,
"open wrapper: establisher supplied the reserved reply key; tearing down channel"
);
+39 -21
View File
@@ -66,10 +66,27 @@ pub struct ProducerLeg {
/// establisher and the handler agree on this concrete type (the
/// `ChannelPlan` downcast contract, ADR-049 amendment 2); alkcall
/// never inspects it.
///
/// **Adopted-entry ownership (ADR-051 §6):** the plan holds the
/// producer-leg `ChannelManager` and reclaims the adopted
/// `spoke_id` entry on drop — including the teardown windows the
/// pump handler never reaches (the wrapper's establishment bound
/// expiring after the adopt, the plan dropped before the pump
/// spawns). Dropping the plan's `spoke_send` also EOFs the spoke
/// side via the mux pump's implicit-EOF path (REQ-CH-01), so the
/// spoke handler reclaims through the same cascade the normal path
/// uses.
struct RelayPlan {
spoke_id: u32,
spoke_send: MpscSendStream,
spoke_recv: MpscRecvStream,
producer_manager: ChannelManager,
spoke_send: Option<MpscSendStream>,
spoke_recv: Option<MpscRecvStream>,
}
impl Drop for RelayPlan {
fn drop(&mut self) {
let _ = self.producer_manager.teardown_channel(self.spoke_id);
}
}
/// The relay component (ADR-051). Holds the producer-leg surface;
@@ -141,7 +158,6 @@ impl ChannelRelay {
let producer_call = Arc::clone(&self.producer_leg.call);
let producer_manager = self.producer_leg.manager.clone();
let op_name = spec.name.clone();
let handler_manager = producer_manager.clone();
let establisher: OpenEstablisher = Arc::new(move |input, per_call_auth| {
let producer_call = Arc::clone(&producer_call);
let producer_manager = producer_manager.clone();
@@ -159,7 +175,6 @@ impl ChannelRelay {
});
let open_handler: OpenHandler = Arc::new(move |_input, plan, channel_conn, _auth| {
let producer_manager = handler_manager.clone();
tokio::spawn(async move {
let Some(plan) = plan else {
return;
@@ -168,34 +183,36 @@ impl ChannelRelay {
// wrapper → handler, moved each hop), so `try_unwrap`
// succeeds; on the phantom-holder case the wrapper's
// no-accept telemetry fires and the spoke leg EOFs
// when the plan finally drops.
// when the plan finally drops (its Drop guard reclaims
// the adopted entry whenever that drop happens).
let Ok(relay_plan) = plan.downcast::<RelayPlan>() else {
tracing::debug!("relay: establisher plan of unexpected type");
return;
};
let Ok(relay_plan) = Arc::try_unwrap(relay_plan) else {
let Ok(mut relay_plan) = Arc::try_unwrap(relay_plan) else {
tracing::debug!("relay: establisher plan held elsewhere; skipping pump");
return;
};
let Ok(bidi) = channel_conn.accept_bi().await else {
return;
};
// Take the streams out of the plan; the plan itself
// stays alive as the adopted-entry drop guard (see
// `RelayPlan`) until this task's end.
let Some(spoke_recv) = relay_plan.spoke_recv.take() else {
return;
};
let Some(spoke_send) = relay_plan.spoke_send.take() else {
return;
};
// The byte-forward hop (ADR-042 layer 2, ADR-050):
// two pumps joined inline (R-02). On completion the
// pump's drops EOF the spoke leg; the wrapper's
// handler-exit teardown covers the consumer leg. The
// adopted spoke channel entry is reclaimed here so the
// producer-leg manager holds no stale routing state
// after the relayed channel is done (the consumer-leg
// wrapper's teardown cannot see it — it is on the
// producer leg's manager).
let RelayPlan {
spoke_id,
spoke_send,
spoke_recv,
} = relay_plan;
// pump's send-half drops EOF the spoke leg (the mux
// pump's implicit-EOF sentinel) and the plan's Drop
// guard reclaims the adopted producer-leg entry; the
// wrapper's handler-exit teardown covers the consumer
// leg.
let _ = pump_bidi(bidi, spoke_recv, spoke_send).await;
let _ = producer_manager.teardown_channel(spoke_id);
})
});
@@ -268,8 +285,9 @@ async fn open_on_producer_leg(
let establishment = Establishment::new(Arc::new(RelayPlan {
spoke_id,
spoke_send,
spoke_recv,
producer_manager: producer_manager.clone(),
spoke_send: Some(spoke_send),
spoke_recv: Some(spoke_recv),
}) as super::operations::ChannelPlan);
Ok(if reply_fields.is_empty() {
establishment
+42
View File
@@ -203,6 +203,48 @@ fn relay_payload_omits_forwarded_for_when_identity_none() {
// --- helpers --------------------------------------------------------------
/// The `RelayPlan` drop guard (ADR-051 §6, the adopted-entry teardown
/// bullet): dropping the plan — via whatever path — reclaims the
/// adopted producer-leg entry. Covers the windows the pump handler's
/// normal-path reclaim cannot reach: the wrapper's establishment bound
/// expiring after the adopt (the plan dropped before the pump spawns),
/// and the pump handler's early-return arms (plan held elsewhere —
/// `try_unwrap` failure — where the pump never runs).
#[tokio::test]
async fn relay_plan_drop_reclaims_the_adopted_producer_leg_entry() {
let producer_manager = manager_for_test();
// Adopt a channel the way the relay establisher does.
let (spoke_send, spoke_recv) = producer_manager
.adopt_channel(4, "alk/relay", None)
.await
.expect("adopt the spoke channel");
assert_eq!(data_channels_len(&producer_manager), 1);
// Wrap it in a plan and drop the plan without any pump ever
// running (the establishment-timeout-after-adopt shape).
let plan = RelayPlan {
spoke_id: 4,
producer_manager: producer_manager.clone(),
spoke_send: Some(spoke_send),
spoke_recv: Some(spoke_recv),
};
drop(plan);
assert!(
producer_manager.channel_ids().into_iter().all(|id| id == 0),
"the adopted producer-leg entry must reclaim on the plan's drop"
);
}
fn data_channels_len(manager: &ChannelManager) -> usize {
manager
.channel_ids()
.into_iter()
.filter(|id| *id != 0)
.count()
}
fn overlay_connection() -> Arc<CallConnection> {
Arc::new(CallConnection::new_overlay_only(identity("spoke", &[])))
}
+60 -1
View File
@@ -271,7 +271,9 @@ pub(crate) fn rebuild_spec_for(
// can see it without re-fetching `services/schema`.
let explicit_alpn = schema_json
.get("channel_open_alpn")
.and_then(|v| v.as_str());
.and_then(|v| v.as_str())
.map(str::trim)
.filter(|s| !s.is_empty());
if schema_json
.get("channel_open")
.and_then(|v| v.as_bool())
@@ -816,6 +818,63 @@ mod tests {
);
}
/// An empty/whitespace explicit `channel_open_alpn` must not
/// override a sane name derivation (review-008 post-landing audit
/// F-3): a single misconfigured producer would otherwise poison the
/// marker for all consumers with an empty ALPN.
#[test]
fn rebuild_spec_empty_explicit_alpn_falls_back_to_derivation() {
for bad in ["", " "] {
let mut schema = sample_schema_json("channels/tunnel/direct", "sub");
schema["channel_open"] = json!(true);
schema["channel_open_alpn"] = json!(bad);
let spec = rebuild_spec_for(&schema, "channels/tunnel/direct", &None).expect("rebuild");
let marker = spec
.channel_open
.expect("marker reconstructed via the derivation fallback");
assert_eq!(
marker.alpn, "alk/tunnel",
"empty explicit ALPN `{bad}` must not override the derivation"
);
}
}
/// A marked op named `channels//sub` (empty segment) must NOT
/// serialize boolean-only and then reconstruct unmarked — the
/// standard-shape check applies the same empty-segment guard the
/// derivation does, so the explicit string rides the wire
/// (review-008 post-landing audit N-4).
#[test]
fn spec_empty_segment_name_is_not_treated_as_standard_shape() {
use crate::registry::discovery::{
op_name_is_standard_channel_open_shape, spec_to_json_pub,
};
use crate::registry::spec::ChannelOpenSpec;
assert!(!op_name_is_standard_channel_open_shape("channels//sub"));
let spec = OperationSpec::new(
"channels//sub",
OperationType::Sub,
Visibility::External,
json!({}),
json!({}),
vec![],
crate::registry::spec::AccessControl::default(),
None,
)
.with_channel_open(ChannelOpenSpec::new("alk/tty"));
let wire = spec_to_json_pub(&spec);
assert_eq!(
wire["channel_open_alpn"],
json!("alk/tty"),
"the empty-segment name is non-derivable — the explicit string must ride"
);
let rebuilt = rebuild_spec_for(&wire, "channels//sub", &None).expect("rebuild");
let marker = rebuilt.channel_open.expect("marker reconstructs");
assert_eq!(marker.alpn, "alk/tty");
}
/// Review 008 U-1 gate 3: standard-shape ops round-trip unchanged —
/// boolean only, no `channel_open_alpn` key, byte-stable.
#[test]
+7 -3
View File
@@ -235,14 +235,18 @@ pub(crate) fn spec_to_json(spec: &OperationSpec) -> Value {
/// (the flavor form — `channels/tunnel/direct`) is non-derivable and
/// serializes the explicit `channel_open_alpn` string alongside the
/// boolean (ADR-047 amendment — review 008 U-1).
fn op_name_is_standard_channel_open_shape(name: &str) -> bool {
pub(crate) fn op_name_is_standard_channel_open_shape(name: &str) -> bool {
let Some(rest) = name.strip_prefix("channels/") else {
return false;
};
let Some((_segment, flavor)) = rest.rsplit_once('/') else {
let Some((segment, flavor)) = rest.rsplit_once('/') else {
return false;
};
flavor == "sub" || flavor == "pub"
// The standard shape's segment must be non-empty — the same guard
// `derive_alpn_from_op_name` applies, so a marked op named
// `channels//sub` (segment empty) does not serialize boolean-only
// and then reconstruct unmarked: it rides the explicit string.
!segment.is_empty() && (flavor == "sub" || flavor == "pub")
}
/// Public serialization of an `OperationSpec` into the `services/schema`