- 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.
239 lines
12 KiB
Markdown
239 lines
12 KiB
Markdown
# Review 011 — The marked-pub open contract: downstream input on review 010's S-01, and what the single-publish fix must pin
|
|
|
|
## Status
|
|
|
|
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)
|
|
— the reviewed downstream ruling this review summarizes the
|
|
requirements of.
|
|
|
|
---
|
|
|
|
## 1. S-01: the remediation is "implement", but the review's option-1 sketch is the wrong shape — amend the review text
|
|
|
|
Review 010's S-01 poses two remedies. Option 2 (reject at
|
|
registration) is honest but strands the downstream: alkblobs' put is
|
|
the real consumer of the marked-pub shape, and its settlement was
|
|
"implement, in the adoption shape". The one publish should take
|
|
option 1. But **option 1's sketch is wrong for marked ops** and
|
|
would, if implemented as sketched, ship a second incoherence this
|
|
ADR cycle exists to end:
|
|
|
|
> "hand the `OpenHandler` the `PublishStream` already produced by the
|
|
> serving loop along with the channel's `Connection`"
|
|
|
|
That reads ADR-046's **unmarked** Pub semantics (`call.published`
|
|
JSON chunks through `publish_schema` validation) into the **marked**
|
|
case. ADR-047 §2's marker sentence says the opposite: *"the
|
|
`channel_open` marker that tells the channels layer 'this op's
|
|
stream is binary, not JSON' — allocate a channel for it."* A marked
|
|
Pub op whose bulk bytes rode `call.published` would be the worst of
|
|
both stacks: bulk binary re-validated per-chunk as JSON against
|
|
`publish_schema`, MB-scale payloads through the JSON event grammar,
|
|
and no bounded-buffer backpressure (ADR-040's bounded buffer is
|
|
precisely the mechanism that exists for this). Section 010's
|
|
"tension" paragraph (stub comment vs ADR-047 §1's `call.published`
|
|
prose) resolves the same way: §1/AAD-046's `call.published` prose
|
|
describes *unmarked* Pub ops; the *marked* case's stream is the
|
|
allocated channel's BiStream.
|
|
|
|
The settled shape (agreed with the downstream's architecture
|
|
session; recorded as alkblobs ADR-014 §1/§2/§3):
|
|
|
|
1. **The initiator allocates and writes immediately.**
|
|
Connection-owner allocates the `channel_id` (ADR-047 §5; connect
|
|
side → odd per the implemented odd/even split,
|
|
`src/channels/manager.rs` `next_channel_id`), opens the write
|
|
half on it, and streams bytes before the open op resolves. The
|
|
existing early-arrival parking (`drain_early_arrivals`,
|
|
`src/channels/manager.rs`) is the established mechanism covering
|
|
the responder-adopts race — implemented, tested, no new
|
|
machinery.
|
|
2. **The responder adopts.** The channel ID crosses the open
|
|
invocation as a wrapper-reserved field. The reserved-field
|
|
placement question — reserved input key vs envelope field — is
|
|
decided by schema-validation ordering: validation runs in
|
|
`invoke_*` before any handler, so a reserved *input* key must be
|
|
stripped in the dispatch sink branch (the layer that reads the
|
|
`channel_open` marker) before `input_schema` validation; an
|
|
envelope field would extend the call event grammar for one op
|
|
family. Recommendation: **reserved input key + dispatch-layer
|
|
strip**, symmetric with amendment 3's reply-side
|
|
`RESERVED_REPLY_KEY` reservation — the input side of the same
|
|
pattern. Either way, the exact key name and the reject-not-strip
|
|
posture for a *marked* op are wire-visible from the first
|
|
consumer (alkblobs' put; not yet shipped — it pends behind this
|
|
amendment, so this is the cheapest moment to pin).
|
|
3. **The wrapper is the responder of record.** Flow:
|
|
`policy.check_open` → adopt → bounded establisher (offer
|
|
validation, establishment reply fields — ADR-049 amendment 3's
|
|
mechanism, unchanged) → spawn the `OpenHandler` on the channel's
|
|
`Connection` → **await the handler's `JoinHandle`** → respond
|
|
once: the `{channel_id}` output extended with the establisher's
|
|
reply fields and the handler-completion result merged (R-02's
|
|
lifetime contract is load-bearing here — the handle must track
|
|
the data plane). A failed establishment tears the channel down
|
|
and replies `channel:open_failed` (the only response); initiator
|
|
bytes racing the torn-down channel hit EOF and are discarded.
|
|
4. **Zero `call.published` data on the call plane for marked pub
|
|
ops** — a nonempty publish-chunk stream on a marked op is
|
|
rejected loudly, never consumed. Two competing data paths is the
|
|
incoherence that must never exist.
|
|
5. **Relay lifting:** `PubTypedOpen` rejection
|
|
(`src/channels/relay.rs`) is replaced with real relay
|
|
translation for pub openables (the hub's put-forwarding shape).
|
|
|
|
The corollary this implies downstream (also pinned in alkblobs
|
|
ADR-014 §2): **the put's establishment ack is informative-in-result,
|
|
not a gating ack.** A sink op gets exactly one `call.responded`
|
|
(ADR-046 §5), produced after the handler completes — so a putter
|
|
cannot wait for the ack before streaming (a wait deadlocks: the
|
|
response cannot exist until the stream completes). The fetch/put
|
|
asymmetry is direction-inherent: fetch (`Sub`) acks at
|
|
establishment before any byte; put (`Pub`) acks in the result. The
|
|
amendment should state this sentence, not leave it for each
|
|
consumer to derive — it is exactly the kind of half-stated
|
|
implication that review 010 flagged as drift fuel.
|
|
|
|
## 2. What amendment 4 must pin (the publish checklist)
|
|
|
|
All of these are wire-visible or API-visible from the first
|
|
consumer; none is safely deferrable to a later amendment while
|
|
put-shaped consumers exist:
|
|
|
|
1. **Marker semantics** — the marked Pub op's stream is the adopted
|
|
channel's `BiStream` (ADR-050 pump patterns, one direction);
|
|
zero `call.published` (§1.4 above; rejection is the wire
|
|
behavior).
|
|
2. **`publish_schema` on a marked op is refused at registration** —
|
|
publish chunks never exist, so the field is dead machinery on a
|
|
marked spec; the same class review 010 outlaws (registered but
|
|
incapable of mattering). Registration-time loudness, per the
|
|
house pattern.
|
|
3. **The reserved-field wire form** for the adopted channel id
|
|
(input-key + dispatch-strip recommended, §1.2) — key name and
|
|
strip-before-validation ordering pinned in the ADR; one-way from
|
|
the first consumer.
|
|
4. **The wrapper-as-responder-of-record flow** (§1.3) — response
|
|
merge order (`channel_id` reserved → establisher reply fields →
|
|
handler result), single-response semantics, teardown-on-fail.
|
|
5. **The ack-timing sentence** (§1 corollary) — put acks in-result;
|
|
consumers stream without waiting.
|
|
6. **Relay translation for pub openables** (§1.5) — no
|
|
`PubTypedOpen` remains.
|
|
7. **The client-side initiator-allocates put-open capability.**
|
|
Nothing in `ChannelClient` supports stream-then-open today:
|
|
`open_channel`/`open_channel_with_reply` await the open
|
|
*reply* before returning the halves — unusable when bytes flow
|
|
before the reply. The amendment supplies the putter-facing
|
|
shape (allocate odd id locally → write half streaming
|
|
immediately → send the open op carrying the reserved id → await
|
|
the single merged response). Without this the server-side
|
|
wrapper is untestable end-to-end in-crate.
|
|
|
|
## 3. Verification gates for the S-01 implementation
|
|
|
|
To be landed as tests in the same publish (deleting the pinned stub
|
|
tests per review 010's action 3 —
|
|
`make_open_handler_sink_returns_not_implemented`,
|
|
`relay_tests.rs:113`, and the C-08/C-09 blocker-comment assertions
|
|
they assert):
|
|
|
|
- known-length put e2e: offer + adopt + bytes → one response,
|
|
`{channel_id, ack fields, handler result}` merge order verified;
|
|
- unknown-length put e2e: the handler's digest rides the merged
|
|
response (the downstream putter's wire contract, ADR-012 §2's
|
|
`{token, digest}`);
|
|
- publish-chunk rejection: a marked pub op carrying nonempty
|
|
publish chunks → loud rejection, no handler consumption;
|
|
- establishment failure: `channel:open_failed`, channel torn down,
|
|
ledger un-incremented, racing bytes EOF-discarded;
|
|
- `publish_schema` registration refusal on a marked op;
|
|
- relay translation: a hub translating a pub openable end-to-end
|
|
(the ADR-051-§6 gap closed).
|
|
|
|
## 4. S-02 / S-03 / S-04 from the downstream seat
|
|
|
|
No alkblobs requirement rides any of the three — the ops module
|
|
rides neither `channel/control` nor `channel/resources/subscribe`
|
|
nor `op/register`-announced streaming. Each remedy is downstream-safe:
|
|
|
|
- **S-02/S-03**: implement-or-stop-advertising per the ruling;
|
|
implementing both is preferred (the publish is one 0.x bump
|
|
either way, and re-advertising later would be a second bump).
|
|
What the downstream needs: nothing. Do not couple the S-01 shape
|
|
to these designs (the OQ-39 control-handle and OQ-40 enumerator
|
|
designs are independent machinery).
|
|
- **S-04**: forwarding-through
|
|
(`make_streaming_forwarding_handler` /
|
|
`make_sink_forwarding_handler` already exist) over rejection —
|
|
the rejection leaves a discoverable-but-unusable service
|
|
announcement, which is the same consumer-visible failure mode
|
|
the ruling targets, one layer over.
|
|
|
|
## 5. The four published crates' impact (the same-publish update)
|
|
|
|
No published consumer registers a Pub-typed openable (alktunnels'
|
|
four openables are Sub-type; alkhttp has no openables in its
|
|
default surface; alkty rides Sub + control-JSON; alksocks rides
|
|
Sub) — **the S-01 implementation is capability-additive in
|
|
practice**. The semver-relevant risk concentrates in what the
|
|
publish *removes*:
|
|
|
|
- S-02/S-03 implemented → additive, zero removals, the four crates
|
|
re-verify and bump their floors only.
|
|
- S-02/S-03 stop-advertised → two discoverable ops disappear;
|
|
acceptable per the ruling, but a 0.x removal worth a changelog
|
|
line for every consumer.
|
|
- S-01/§2's wire pins (reserved key, reject-chunks) carry no
|
|
shipped consumer; the first consumer (alkblobs' put) pends
|
|
behind the amendment — ADR-014 §3's gate — and floors up to the
|
|
amendment release at task start.
|
|
|
|
## 6. Sequencing for the one publish
|
|
|
|
1. The four amendment documents (ADR-047 amendment 4; the
|
|
OQ-39/OQ-40 promotions; the S-04 pin) — drafted for review
|
|
before code, per the working agreement.
|
|
2. Implementation + the §3 gates + the review-010 action-3 guard
|
|
(`not_implemented` source-tree grep wired into AGENTS.md's
|
|
release checklist; the four pinned stub tests deleted).
|
|
3. Docs: OQ-39/40 resolved, C-08 closed, channel-operations.md's
|
|
hedge-era sentences rewritten, review 010's S-01 sketch
|
|
corrected to this review's shape (so the published record stops
|
|
carrying the wrong-graph reading).
|
|
4. One publish, all downstream floors bump in it.
|
|
|
|
## References
|
|
|
|
- review 010 (the stub sweep this review amends the S-01 path of)
|
|
- alkblobs ADR-014 (`014-marked-pub-put-contract-and-alkcall-gate.md`)
|
|
— the downstream ruling; its §2 pins the ack-timing asymmetry
|
|
- alkblobs tasks/ops-put.md — the paused task; its Notes record the
|
|
gate and the two timing clarifications
|
|
- ADR-040 (bounded buffer — the backpressure the data plane rides),
|
|
ADR-046 (the pub lifecycle's single response), ADR-047 §2/§5
|
|
(the marker; connection-owner allocates), ADR-049 + amendments
|
|
2/3 (establishment, plan, reply fields; the RESERVED_REPLY_KEY
|
|
pattern the input-side reservation mirrors), ADR-050 (pumps)
|
|
- ADR-051 §6 (the relay-side documentation of the same gap)
|
|
- `src/channels/manager.rs` (`next_channel_id` odd/even;
|
|
`drain_early_arrivals`), `src/channels/operations.rs` (the stub,
|
|
the wrapper), `src/channels/relay.rs` (`PubTypedOpen`) |