docs(review 011): downstream input on review 010's S-01 — the marked-pub open contract and the single-publish amendment requirements
From alkblobs (paused on S-01; its ADR-014 is the reviewed downstream ruling): - S-01's option-1 sketch (hand the OpenHandler the PublishStream) is the wrong shape for marked ops — it imports ADR-046's unmarked call.published semantics into the ADR-047 §2 marker case. The settled shape: initiator allocates (odd) and writes immediately; responder adopts (channel id crosses the open invocation, reserved input key + dispatch-layer strip recommended); wrapper as responder of record (await the handler's JoinHandle, merge establishment reply fields + handler result into the single response); zero call.published on marked ops (nonempty publish chunks rejected loudly); relay PubTypedOpen lifted. - Seven items the amendment-4 publish must pin (incl. publish_schema refused at registration for marked ops; the put acks-in-result sentence; the client-side stream-then-open capability absent from ChannelClient today). - Verification gates list; S-02/S-03/S-04 downstream non-requirements and remedy preferences; per-crate impact for the four published consumers (none register Pub-typed openables — S-01 is capability-additive; semver risk concentrates in any stop-advertising removals). Docs-only change.
This commit is contained in:
1 parent
1793265b04
commit
549893cbbf
1 file changed
+230
@@ -0,0 +1,230 @@
|
|||||||
|
# Review 011 — The marked-pub open contract: downstream input on review 010's S-01, and what the single-publish fix must pin
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Open — 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.
|
||||||
|
|
||||||
|
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`)
|
||||||
Reference in new issue
Block a user