docs: step-3 rewrites — OQ-39/40 resolved, C-08 closed, channel-operations.md put/control/resources contracts, review 010/011 closed with the S-01 sketch corrected

- open-questions.md: OQ-39 → ADR-052, OQ-40 → ADR-053 (resolutions replace
  the deferred-until-needed posture)
- channel-operations.md: the put contract (initiator allocates + streams,
  reserved input key, one response, acks-in-result sentence), the control
  routing (ADR-052), the enumerator aggregation (ADR-053), the
  connection-owner paragraph's pub-era correction, the ADR table rows
- ADR-051 §6: PubTypedOpen → the put translation; ADR-047's 2026-09-06
  amendment records its era honestly (the dynamic half is now ADR-053)
- review 010: all four findings resolved (with the S-01 sketch correction
  so the published record stops carrying the wrong-graph reading)
- review 011: closed (all seven pins implemented + gates landed)
- review 001 C-08: resolved note (odd/even + adopt, implemented)
- AGENTS.md: the 'Stub Guard' section — the not_implemented grep is part
  of cargo test; the registration invariants and the implement-or-stop
  ruling are release-blocking

Verified: cargo test (696+guard), clippy -D warnings, fmt, doc clean.
This commit is contained in:
glm-5.3-flash committed 2026-10-13 09:04:13 +00:00
1 parent 944c300c17
commit 3317ea0963
10 files changed
+205 -43

No files matched your search

+22
View File
@@ -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
+3 -1
View File
@@ -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
+118 -27
View File
@@ -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/<alpn>/pub` — publish a binary stream
### `channels/<alpn>/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": <adopted> } → 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": <callback output> }`, 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 |
@@ -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<String>` — a human-
readable op description, set via `with_description` at registration.
@@ -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
+4 -4
View File
@@ -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
@@ -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
+31 -1
View File
@@ -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),
@@ -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)
+2 -1
View File
@@ -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.