diff --git a/AGENTS.md b/AGENTS.md index ccd1ff5..b9596b9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -213,6 +213,28 @@ cargo clippy --target wasm32-unknown-unknown -- -D warnings cargo semver-checks check-release # before a release ``` +## Stub Guard (release-blocking) + +Four registered-but-always-failing stubs shipped across nine published +versions (reviews 010/011). This class is now release-blocking in both +directions: + +1. **Test guard**: `cargo test --test stub_guard` (part of `cargo + test`) fails if the string `not_implemented` appears anywhere in + `src/`. A "loud failure" stub is still a stub. +2. **Registration-time invariants** (ADR-047 amendment 4 §4.8): a + marked (`channel_open`) spec refuses the generic registration path + (`OperationRegistry::register`, `CallConnection::register_imported`, + `op/register`) and refuses `publish_schema`; the sanctioned + registrations are the channel wrapper family + (`register_openable*`, `register_pub_openable`, + `register_relay_openable`). +3. **The ruling**: implement-or-stop-advertising before any publish. + The only permitted deferral statement is "not this session" — never + "next version", "not v1", or any bucket that parks a decision on an + unknown future release. Every handler that registers on a + discoverable surface must be capable of succeeding. + ## Architecture Context - `docs/architecture/` — the authoritative spec. Read it before diff --git a/docs/architecture/README.md b/docs/architecture/README.md index fb2a86f..bb01103 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -13,7 +13,7 @@ This crate unifies `alknet-call` and `alknet-channels` from the alknet mono-repo, plus the vendored core types formerly in `alknet-core`. The source architecture docs were ported from `/workspace/@alkdev/alknet/docs/architecture/` and renumbered as alkcall -ADRs (ADR-001..051). The ALPN strings (`alk/call`, `alk/channels`) +ADRs (ADR-001..053). The ALPN strings (`alk/call`, `alk/channels`) are wire-stable and unchanged — see ADR-004. ## Documents @@ -104,6 +104,8 @@ are wire-stable and unchanged — see ADR-004. | [049](decisions/049-channel-open-establishment-phase.md) | Channel-Open Establishment Phase | `OpenEstablisher` + `register_openable_with_establisher` (awaited, bounded); typed `channel:open_failed` with `details.reason`; `Establishment.plan` (`ChannelPlan`) threaded to the `OpenHandler` (amendment 2); the `JoinHandle` data-plane lifetime contract | | [050](decisions/050-pump-bidi-two-pump-helper.md) | `pump_bidi` Two-Pump Helper | `channels::pump_bidi` — shutdown-on-completion two-pump data plane pinned in one place (alknet ADR-078) | | [051](decisions/051-channel-relay-and-hub-leg.md) | In-Tree Channel Relay (`ChannelRelay`) and the Hub-Leg Assembly | ADR-042 amended — the relay implementation is an alkcall export; translate hop = ADR-049 establisher (`register_relay_openable`), byte-forward hop = `pump_bidi`; implicit per-channel id mapping; two-phase registration (discover/stash → fork-register); hub-leg install template; ACL layering note | +| [052](decisions/052-channel-control-surface.md) | `channel/control` Control-Handle Surface | Establisher-contributed `ChannelControl` (`Establishment::with_control`), wrapper-installed/teardown-removed in the manager; `channel:no_control_handle` capability answer; resolves OQ-39 | +| [053](decisions/053-resource-enumerators.md) | Live Resource Enumerators | `ResourceEnumerators` (peer-shared registry); snapshot-then-changes enumerator contract; scan-once aggregation; empty seed; resolves OQ-40 | ## Relevant Open Questions diff --git a/docs/architecture/channel-operations.md b/docs/architecture/channel-operations.md index f1c2119..4070993 100644 --- a/docs/architecture/channel-operations.md +++ b/docs/architecture/channel-operations.md @@ -1,6 +1,6 @@ --- -status: draft -last_updated: 2026-08-12 +status: current +last_updated: 2026-10-13 --- # channel-operations.md — Channel Lifecycle on the Call Protocol @@ -60,15 +60,18 @@ Response (`call.responded`): |-------|------|---------| | `channel_id` | u32 | Allocated by the connection owner (ADR-047 §5). In the `Sub` case, the responder (producer). Both sides route chunks with this ID to the new channel. | -**`channel_id` allocation (ADR-047 §5): connection-owner allocates.** +**`channel_id` allocation (ADR-047 §5, as amended for the put path by +ADR-047 amendment 4): connection-owner allocates.** The side that holds the `ChannelManager` for that connection allocates -the `channel_id` via a monotonic `AtomicU32` (`next_id.fetch_add(1, -Relaxed)`) and returns it in the response. In the `Sub` case the -responder owns the connection and allocates; in the `Pub` case the -initiator owns and allocates. One round-trip before data flows — the -same round-trip the call protocol makes for every operation. All -current channel types (TTY, tunnel, SSH) already require a negotiation -round-trip, so the open round-trip is not additive latency. +the `channel_id` via a monotonic `AtomicU32` (odd/even split — connect +side starts at 1, accept at 2) and returns it in the response. In the +`Sub` case the responder owns the connection and allocates — one +round-trip before data flows (the same round-trip every call makes). +In the `Pub` case (the put contract above) the initiator owns, +allocates, and streams bytes **before** the open resolves — the +early-arrival parking covers the adopt race. All current channel +types (TTY, tunnel, SSH) already require a negotiation round-trip, so +the open round-trip is not additive latency. **Error codes** (`CallError.code` strings): @@ -110,19 +113,80 @@ the existing teardown machinery, unchanged. Head-of-line safety: the serving loop spawns Once invocations as independent tasks, so a slow establisher on one open op does not block other calls on channel 0. -### `channels//pub` — publish a binary stream +### `channels//pub` — publish a binary stream (the put contract, ADR-047 amendment 4) -`OperationType::Pub` (ADR-046). The initiator publishes a stream of -`call.published` events to the responder; the responder's `SinkHandler` -consumes them and returns a single `call.responded`. The -`channel_open` marker on the op spec tells the channels layer the -stream is binary — the `call.published` chunks carry binary payloads -on a data channel (not JSON on channel 0). +A marked `Pub`-typed open op is the **put** shape: the data plane is +the adopted channel's `BiStream` — never the call plane. The contract, +pinned end-to-end (ADR-047 amendment 4; review 011's corrections are +the authoritative shape — a serving-loop `PublishStream` sketch was +considered and rejected, it is ADR-046's *unmarked* pub semantics): + +1. **The initiator allocates and writes immediately.** The putter + allocates the channel id on its own side (connect side → odd, per + the ADR-047 §5 odd/even split), inserts the **reserved input key + `"channel_id"`** into the open op's input, and streams bytes on the + channel before the open resolves — the early-arrival parking + (ADR-051's tested pre-adopt buffer) covers the responder-adopts + race. Zero `call.published` on marked ops: chunks on the call plane + are rejected loudly with `channel:publish_chunks_rejected` (the + first chunk writes the rejection in the initiator's direction; the + handler is never fed). An unmarked `Pub` op is an ordinary ADR-046 + sink (JSON chunks, `publish_schema`); it does not ride the channel + machinery. +2. **The responder adopts.** `ChannelCore::register_pub_openable` is + the only registration seam: the wrapper strips the reserved key + pre-validation (dispatch layer, before `input_schema`) and adopts + the given id (`adopt_channel` — the remote parity range enforced; + failure replies `channel:open_failed` with reason `adopt_failed`). +3. **The wrapper is the responder of record:** `check_open` → adopt → + bounded establisher (offer validation + ack fields + + `Establishment::with_control`, ADR-049 amendment 3's mechanism) → + the `PubOpenHandler` runs on the adopted channel's `Connection`, + **awaited inline** → the standard completion teardown → **one + response**, merge order pinned: + + ``` + { "channel_id": } → ack fields → handler result + ``` + + (the handler result wins on key collision — its computed result + postdates the ack; a handler `Err` IS the response, and the channel + tears down). +4. **The ack-timing sentence (protocol semantics):** a sink op gets + exactly one `call.responded`, produced after the handler completes + — a putter stream-waits nothing; the establishment ack is + informative-in-result, never a gating ack. The fetch/put asymmetry + is direction-inherent: fetch (`Sub`) acks at establishment (the + `{channel_id}` response precedes any byte); put (`Pub`) acks in the + result. A putter that waits for the ack deadlocks — this sentence + is the contract, not per-consumer folklore. +5. **The putter-facing client shape:** + `ChannelClient::open_pub_channel(operation_id, input, alpn)` + allocates the odd id, sends the open op in the background, and + returns `(id, send, recv, reply)` immediately; `reply.resolve()` + is the awaited merged response. + +**Error codes** (the pub path): + +| code | meaning | retryable | +|------|---------|-----------| +| `channel:open_failed` | establishment failure (ADR-049 §3 vocabulary) or adoption failure — new reason `adopt_failed` (collisions, cap, out-of-range reserved ids; ADR-047 amendment 4 §4.4) | false | +| `channel:publish_chunks_rejected` | `call.published` chunks on a marked op (the data plane is the channel, ADR-047 amendment 4 §4.1) | false | +| `INVALID_INPUT` | absent/non-u32 reserved `channel_id` key on a marked pub op (ADR-047 amendment 4 §4.3) | false | The hub-as-proxy pattern (ADR-042, ADR-047 §1) composes on top: a -worker publishes to the hub, the hub owns the resource, and consumers -subscribe from the hub. The broker (topic registry, `Pub`↔`Sub` -matching) is a hub concern, not an alkcall concern (ADR-047 §1, Gap B). +worker publishes to the hub, the hub owns the resource, consumers +subscribe from the hub. Marked pub openables relay through the put +translation (ADR-047 amendment 4 §4.7): the hub allocates its own +connect-side id, opens the spoke op in the background (put +acks-in-result — an establisher awaiting the spoke's response would +deadlock the leg), pumps consumer bytes → spoke leg, and mirrors the +spoke's single response with its reserved key stripped. + +Registration invariants (ADR-047 amendment 4 §4.8 — the stub-class +guard): a marked spec refuses the generic registration path and +refuses `publish_schema`; `register_openable*` refuse `Pub`-typed +specs; `register_pub_openable` refuses unmarked/non-`Pub` specs. ## The generic ops (keyed by `channel_id`) @@ -159,7 +223,7 @@ TTY's 5-byte payload format); for tunnels it is the last data byte before close. This invariant crosses two channels (the data channel and channel 0), so the channels layer owns the ordering guarantee. -### `channel/control` — out-of-band control on channel 0 +### `channel/control` — out-of-band control on channel 0 (implemented, ADR-052) For control that doesn't need ordering relative to data (resize, signal, keepalive): @@ -174,11 +238,23 @@ keepalive): } ``` -The channels layer routes `message` to the handler's control handle for -`channel_id`. The `message` JSON is ALPN-specific; the channels layer -does not interpret it. +The channels layer routes `message` to the channel's control callback +(ADR-052): the ALPN crate contributes it through its open-op +establisher (`Establishment::with_control`); the wrapper installs it +under the allocated/adopted id and every teardown path removes it. The +`message` JSON is ALPN-specific; the channels layer does not interpret +it. The reply is `{ "control": }`, or: -### `channel/resources/subscribe` — live resource discovery +| code | meaning | +|------|---------| +| `channel:unknown_channel` | no such channel (pre-existing class) | +| `channel:no_control_handle` | the channel's ALPN registered no control surface — a capability answer, not a failure (ADR-052 §3); data-ordered control rides the data channel (DP-4 below) | +| (callback's own code) | the callback's `CallError` verbatim | + +Control that MUST be ordered relative to data rides the data channel's +`BiStream` (the control-message division below is unchanged). + +### `channel/resources/subscribe` — live resource discovery (implemented, ADR-053) **This is a `Sub` operation (ADR-021), not a polled Query.** The call protocol has `StreamingHandler` / `invoke_streaming` (implemented and @@ -193,8 +269,21 @@ start/stop. } ``` -The responder registers a `StreamingHandler` that emits a -`ResponseEnvelope` whenever the resource set changes. Each event: +Each openable ALPN crate provides a **resource enumerator** +(ADR-053 §1 — snapshot-then-changes: the first item is the current +payload, subsequent items are full replacements; the producing crate +owns its change detection) at assembly/install time: +`ResourceEnumerators::register(alpn, enumerator)`, one shared registry +per accepting peer. The handler aggregates the enumerators registered +at subscription start (the **scan-once** pin — a later registration is +invisible to an in-flight subscription; re-subscribe): + +- **non-empty registry:** each enumerator item becomes one event, + `{ "output": { "resources": [ { "alpn": …, …payload } ] } }` — the + first events across the ALPNs are the initial snapshot; +- **empty registry:** exactly one `{ "resources": [] }` event, then + the stream stays open (subscribe, read the first event, cancel — + the poll use case). ```json { @@ -471,6 +560,8 @@ All design decisions are documented as ADRs in [decisions/](decisions/). | [021](decisions/021-streaming-handler-for-subscriptions.md) | StreamingHandler | The machinery `channel/resources/subscribe` uses | | [046](decisions/046-publish-operation-type-and-handler-kind-sink.md) | Pub Operation Type | The `Pub`/`Sub` primitives the per-ALPN open ops build on | | [049](decisions/049-channel-open-establishment-phase.md) | Channel-Open Establishment Phase | The establisher hook; typed `channel:open_failed`; `Establishment.plan` (amendment 2); the `JoinHandle` lifetime contract | +| [052](decisions/052-channel-control-surface.md) | `channel/control` Control-Handle Surface | The establisher-contributed `ChannelControl`; wrapper-install/teardown-removal; `channel:no_control_handle` | +| [053](decisions/053-resource-enumerators.md) | Live Resource Enumerators | The snapshot-then-changes enumerator contract; scan-once aggregation; the empty seed | | [050](decisions/050-pump-bidi-two-pump-helper.md) | `pump_bidi` Two-Pump Helper | The data-plane helper openable-ALPN handlers await inline | | [026](decisions/026-forwarded-for-identity.md) | Forwarded-For Identity | The auth chain for hub-relayed opens (and why the cap is per direct-caller, not per `forwarded_for`) | | [011](decisions/011-dynamic-resource-ownership-for-runtime-spawned-resources.md) | Dynamic Resource Ownership | The parallel — a channel slot is a resource, the cap is a quota check; `resource_id_path` works again under per-ALPN ops | diff --git a/docs/architecture/decisions/047-openable-alpns-are-operations.md b/docs/architecture/decisions/047-openable-alpns-are-operations.md index c261963..592cbbd 100644 --- a/docs/architecture/decisions/047-openable-alpns-are-operations.md +++ b/docs/architecture/decisions/047-openable-alpns-are-operations.md @@ -366,7 +366,9 @@ half load-bearing for the first time and asked for a decision on the static half. Decision: **both halves of the "static per-op" split gain what is cheap today; the dynamic half stays deferred** (OQ-40, now with a "load-bearing for alktunnels discovery UI" note in -`open-questions.md`; alktunnels v1 uses config-known op names): +`open-questions.md`; alktunnels v1 uses config-known op names). +*[Amendment 4 era, 2026-10-13: the dynamic half is now implemented — +ADR-053; this 2026-09-06 text records the state at its writing.]* - `OperationSpec` gains `description: Option` — a human- readable op description, set via `with_description` at registration. diff --git a/docs/architecture/decisions/051-channel-relay-and-hub-leg.md b/docs/architecture/decisions/051-channel-relay-and-hub-leg.md index 8d2960f..81453c5 100644 --- a/docs/architecture/decisions/051-channel-relay-and-hub-leg.md +++ b/docs/architecture/decisions/051-channel-relay-and-hub-leg.md @@ -205,11 +205,13 @@ same pin. ### 6. Bounds: rejected shapes and residual notes -- **Pub-typed open ops** — rejected loudly (`channel:pub_open_not_implemented`, - the C-08 blocker). The relay registers only `Query`/`Mutation`-typed - ops (plain forwarding) and `Sub`-typed marked ops (relay openables); - a discovered `Pub`-typed marked spec is an assembly error, not a - silent stub. +- **Pub-typed open ops** — ~~rejected loudly~~ **translated** (ADR-047 + amendment 4 §4.7 — the put-forwarding translation: the hub allocates + its connect-side id, opens the spoke op in the background, + pumps the consumer bytes → spoke, and mirrors the spoke's single + response with its reserved key stripped; `PubTypedOpen` is removed). + The rejection remains for the genuinely-unsupported shapes: unmarked + specs (`UnmarkedSpec`) only. - **Compounding establishment bounds** — each hop's establisher is bounded independently (ADR-049 §2); an N-hop chain adds N bounds. The consumer's dispatch deadline (`min(deadline, 10s)` per hop) @@ -263,7 +265,6 @@ same pin. `ChannelRelay` instead of re-deriving the hub shape. - U-1's gate 2 (the review's silent-failure scenario — flavor-form ops relaying as plain forwarding stubs) becomes a permanent e2e gate. - **Negative:** - New pub API (`ChannelRelay`, the registration entry point, the diff --git a/docs/architecture/open-questions.md b/docs/architecture/open-questions.md index 99d4852..766aad8 100644 --- a/docs/architecture/open-questions.md +++ b/docs/architecture/open-questions.md @@ -1,6 +1,6 @@ --- -status: draft -last_updated: 2026-08-12 +status: current +last_updated: 2026-10-13 --- # Open Questions @@ -82,8 +82,8 @@ is the load-bearing piece the broker composes on. | OQ-36 | `channel_id` allocation in Pub case | resolved | medium | ADR-047 §5 — "connection owner allocates" (the side that holds the `ChannelManager`); amends "responder allocates" | | OQ-37 | `from_call` relay wrapper for marked ops | open | medium | ADR-047 §1 names it as a consumer (hub) concern; alkcall's `from_call` reconstructs the marker (Gap F resolved) so the consumer can branch on it | | OQ-38 | ALPN→path-segment mapping | resolved | low | ADR-047 §"Negative" — strip the `alk/` prefix; ALPNs without that prefix use the full ALPN string (rare, two-way-door) | -| OQ-39 | `channel/control` control-handle surface | open | medium | The `channel/control` handler currently returns `channel:control_not_implemented`. The control-handle surface (per-channel control callbacks registered by ALPN crates, routing `message` to the handler's control handle for `channel_id`) is real design work — each ALPN crate needs a way to register a control callback, and the channels layer needs a control-handle registry keyed by `channel_id`. Deferred until an ALPN crate (TTY, tunnel) needs out-of-band control. | -| OQ-40 | `channel/resources/subscribe` live subscription | open | medium | The `channel/resources/subscribe` handler currently returns `channel:resources_not_implemented`. The live subscription aggregated from ALPN-crate resource enumerators (ADR-047 §6) requires each ALPN crate to provide a resource enumerator, and the channels layer to aggregate them into a live `Stream` that emits on any change. The current stub is a one-shot error; the real implementation is deferred until a consumer (hub, dashboard) needs live resource discovery. **Load-bearing for alktunnels' discovery UI** (review 006 E-02): a consumer cannot distinguish produced tunnel resources by op name alone. The static half is covered (0.5.0: `OperationSpec.description` on `services/list` — describes the op, not the live resource set); the dynamic half stays deferred — alktunnels v1 uses config-known op names. | +| OQ-39 | `channel/control` control-handle surface | resolved | medium | ADR-052 — the control-handle surface is implemented: per-channel control callbacks contributed by the ALPN establisher (`Establishment::with_control`), wrapper-installed and teardown-removed in the manager; the op routes the ALPN-specific `message` and answers `channel:no_control_handle` when the channel's ALPN registered none. The stub (`channel:control_not_implemented`) was removed with the single-publish fix (reviews 010 S-02 / 011). | +| OQ-40 | `channel/resources/subscribe` live subscription | resolved | medium | ADR-053 — the live subscription is implemented: one `ResourceEnumerator` per openable ALPN (provided by the producing crate, snapshot-then-changes), aggregated by the handler over the scan-once snapshot; an empty registry emits one `{ "resources": [] }` seed and stays open. The one-shot error stub was removed with the single-publish fix (reviews 010 S-03 / 011). The alktunnels discovery-UI note (review 006 E-02) is served by the enumerator surface. | | OQ-41 | QUIC-native multi-stream substrate | open | medium | Only the in-line substrate mode is implemented (single bidi stream, header-demuxed N channels). The QUIC-native multi-stream substrate (accept remaining bidi streams, read headers off each — ADR-034 §substrate modes) is deferred to the downstream alknet crate. The wire format and demux loop are correct for both substrates; only the outer `accept_bi()` loop is missing. The alknet crate owns the QUIC dial/accept loop and is the natural place for the multi-stream accept loop. This crate stays transport-agnostic (no QUIC dependency, WASM-compatible). | ## Core Types diff --git a/docs/reviews/001-pub-and-channels-integration-review.md b/docs/reviews/001-pub-and-channels-integration-review.md index 819794f..3dc7c74 100644 --- a/docs/reviews/001-pub-and-channels-integration-review.md +++ b/docs/reviews/001-pub-and-channels-integration-review.md @@ -962,7 +962,11 @@ promises. vice versa) as the smaller change; record the decision in ADR-047 §5 or a follow-on ADR. Then implement the adoption path: the non-allocating side registers the remotely-assigned id in its - manager/mux. + manager/mux. **[RESOLVED 2026-10-06/13: the implemented odd/even + split (connect odd, accept even) + `ChannelManager::adopt_channel`; + the marked-pub contract rides it end-to-end — ADR-047 amendment 4; + the C-08-blocker stub class this decision previously gated on is + closed with reviews 010/011's single-publish fixes.]** - C-12: reject `channel_id: 0` in the `channel/close` handler. - C-13: `channel/close` must drain (signal EOF to the handler, await pump completion) before `abort`/decrement; implement REQ-CH-06 diff --git a/docs/reviews/010-stub-sweep.md b/docs/reviews/010-stub-sweep.md index 290f632..8927785 100644 --- a/docs/reviews/010-stub-sweep.md +++ b/docs/reviews/010-stub-sweep.md @@ -7,6 +7,12 @@ ops-put task hits `channel:pub_open_not_implemented` when its Sink op carrying the `channel_open` marker is invoked, exactly as the code comments predict. The downstream project is paused on this. +**All four findings resolved 2026-10-13** — implemented in alkcall +main ahead of the single 0.9.0 publish (see the resolution note under +S-01's remediation options; the pinned stub tests are deleted and the +`not_implemented` source-tree guard is part of the test suite, +`tests/stub_guard.rs`, wired into AGENTS.md's "Stub Guard" section). + Policy context: shipping stubs to crates.io is now a hard failure per the repo owner's ruling. Any handler that registers successfully and returns an error of the form `*:not_implemented` on every invocation @@ -94,13 +100,37 @@ least fails loudly. **Remediation options (pick one before the next release):** +> **CORRECTED 2026-10-13 (reviews 011 + ADR-047 amendment 4):** the +> implemented fix is option 1, but option 1's sketch below was the +> wrong shape for marked ops and is superseded — the correct shape is +> review 011's (also pinned as ADR-047 amendment 4): the initiator +> allocates and streams bytes on the channel before the open +> resolves; the responder adopts (`register_pub_openable`); the +> wrapper is the responder of record, awaiting the handler and +> merging ack fields + handler result into ONE response; zero +> `call.published` on marked ops (chunks rejected loudly); +> `RelayError::PubTypedOpen` is replaced by the relay put +> translation. Handing the `OpenHandler` the serving loop's +> `PublishStream` (the sketch below) is ADR-046's *unmarked* pub +> semantics and would have re-validated MB-scale binary payloads as +> JSON call chunks. The downstream agent caught this in review 011 +> before the amendment was implemented. + +> **RESOLVED 2026-10-13** — implemented in alkcall main (the single +> 0.9.0 publish, review 011 §6): S-01 via the marked-pub contract +> above, S-02/S-03 implemented (ADR-052/ADR-053), S-04 +> forward-through (ADR-022, 2026-10-13 amendment), the +> `not_implemented` source-tree guard wired into AGENTS.md's +> "Stub Guard" section, and the pinned stub tests deleted. + 1. **Implement the wrapper** — allocate the channel id, run the establishment phase (reply fields carry the offer ack, per ADR-047 §1 and ops-put's acceptance criteria), hand the `OpenHandler` the `PublishStream` already produced by the serving loop along with the channel's `Connection`. All plumbing exists; this is wrapper glue, plus deciding the OpenHandler-reads-channel-vs-reads-PublishStream - question in an amended ADR-047. + question in an amended ADR-047. *[Superseded sketch — see the + correction above; review 011's shape is the pinned one.]* 2. **Reject at registration** — return an error from `register_openable_with_establisher` for `OperationType::Pub`, mirroring `RelayError::PubTypedOpen` (src/channels/relay.rs:147-156), diff --git a/docs/reviews/011-pub-open-remediation-input-from-alkblobs.md b/docs/reviews/011-pub-open-remediation-input-from-alkblobs.md index 59736af..0953ca8 100644 --- a/docs/reviews/011-pub-open-remediation-input-from-alkblobs.md +++ b/docs/reviews/011-pub-open-remediation-input-from-alkblobs.md @@ -2,13 +2,22 @@ ## Status -Open — filed 2026-10-13. From alkblobs (the downstream crate paused on +Closed — filed 2026-10-13. From alkblobs (the downstream crate paused on review 010 S-01; its ops-put task gates on this surface). Purpose: correct review 010's S-01 remediation sketch before the amendment is drafted, enumerate what the amendment must pin so one publish (0.9.0) fixes all four stubs right, and record the downstream requirements and non-requirements from the consumer seat. +**Closed 2026-10-13:** all seven §2 pins are implemented and pinned in +ADR-047 amendment 4 / ADR-052 / ADR-053 / ADR-022's 2026-10-13 +amendment, with the §3 verification gates landed as tests +(`channels::operations::pub_open_tests`, +`channels::hub_leg::gate2_tests::gate_pub_relay_put_end_to_end`, the +registration-invariant tests in `registry::registration` and +`registry::op_register`), the guard is `tests/stub_guard.rs`, and the +docs rewrites below are done. The §6 sequencing concluded. + Input basis: alkblobs ADR-014 (`/workspace/@alkdev/alkblobs/docs/architecture/decisions/ 014-marked-pub-put-contract-and-alkcall-gate.md`, committed 7ac0099) diff --git a/src/channels/relay.rs b/src/channels/relay.rs index f3a37c2..05acd9b 100644 --- a/src/channels/relay.rs +++ b/src/channels/relay.rs @@ -132,7 +132,8 @@ impl ChannelRelay { /// - `spec` must carry the `channel_open` marker (reconstructed /// WITH the marker by discovery, ADR-047 amendment 3). A /// `Pub`-typed marked spec composes through the put-forwarding - /// translation (see [`Self::register_relay_pub_openable`]); an + /// translation (`register_relay_pub_openable`; ADR-047 amendment 4 + /// §4.7); an /// unmarked spec is a /// [`RelayError::UnmarkedSpec`] error — branch on the /// marker before calling.