Files
alktunnels/tasks/tunnels/corrmiss-forwarded-gate.md
T
glm-5.3-flash 0ceecf582b docs: remove em dashes from repo prose and forbid them going forward
- Rewrote the remaining em dashes outside docs/reviews/ (preserved as
  historical record, matching the alktty precedent): tests/ (313),
  fuzz/ (34 plus one shell comment), tasks/ (671 plus 12 kept in
  fences), docs/research + sdd_process + plans (525 plus 25 in
  fences), docs/architecture/overview.md fence labels (13), README,
  CHANGELOG, AGENTS.md, fuzz/README.md (176), and the
  .opencode/agents prompts (110)
- Rewrote fenced content too: ASCII tree and module-map label
  separators, Rust snippet comments, and template placeholders; wire
  data, JSON/BAST examples, and transcripts survive verbatim
- Rewrote 6 assert/expect message literals in tests/ and 2 format
  strings in fuzz/shared/src; no test data changed
- Added Project Convention 18: no em dashes in prose (doc comments,
  markdown including docs/ and tasks/, agent prompts, commit
  messages); fence content rule, en-dash clause rule, and the
  no-spaced-hyphen prohibition follow alktty's convention 15

Follows 6b20924 (src doc comments) and cd0afe0 (docs/architecture).

Verification: cargo test (141 pass); cargo clippy --all-targets -D
warnings; cargo fmt --check; cargo doc --no-deps; wasm check and
clippy for wasm32-unknown-unknown with -D warnings; em dash grep over
the repo returns matches only in docs/reviews/
2026-11-03 14:12:46 +00:00

9.3 KiB

id, name, status, depends_on, scope, risk, impact, level, tags
id name status depends_on scope risk impact level tags
tunnels/corrmiss-forwarded-gate API: the forwarded-op establisher variant (ForwardedGate + register_tunnel_forwarded_openable_with_establisher) completed
tunnels/corrmiss-adr
moderate medium component implementation
api
establisher
forwarded-op
correlation-miss
review-003

Description

Implement the additive establisher variant for the forwarded op's serving half (plan 003 W1's API item): an assembly-injected correlation gate that the crate wraps into the alkcall OpenEstablisher dance, registered via register_openable_with_establisher: a stale listen token now returns a typed channel:open_failed before the channel's data plane is served, instead of succeeding at the call plane and declining post-open (the phantom-channel shape the review records as fully silent on the fabric side).

Candidate shape (the plan's W1 design decision to finalize, which this task finalizes it):

/// The injected correlation gate: `Err` at the gate → typed
/// `channel:open_failed` before the channel is born.
pub type ForwardedGate = Arc<
    dyn Fn(&TunnelForwardedParams) -> Result<(), TunnelEstablishError> + Send + Sync,
>;

pub fn register_tunnel_forwarded_openable_with_establisher(
    core: &ChannelCore,
    on_registry: &Arc<OperationRegistry>,
    auth: AuthContext,
    gate: ForwardedGate,
    handler: ForwardedHandler,
) -> Result<(), String>

The design decisions to finalize (and pin in doc comments)

  1. Gate input vs raw input. The gate receives the PARSED TunnelForwardedParams; the establisher parses (the params' serde pins, deny_unknown_fields, positive listen, are the loud gates; parse failure at the establisher maps through EstablishmentError::HandlerError → a typed channel:open_failed, which is an improvement over the base shape's silent birth-teardown for a malformed ingress; pin this deliberately, do not drift it). The handler still receives the raw input and re-parses as it does today, so keep make_forwarded_op_handler unchanged (the double parse is cheap and preserves deny-unknown at both layers; the handler must stay usable by the base shape).
  2. The establishment answer. A gate pass produces no resource handle (the correlation map is assembly state; the handler needs nothing it does not re-derive). It returns Establishment::new(plan) with an empty/marker plan payload (same typed-opaque ChannelPlan posture as ADR-049 amendment 2; () or an empty marker struct is the payload choice to pin). No reply-field contribution (the forwarded op's reply stays the wrapper's channel_id).
  3. Error mapping. A gate Err(TunnelEstablishError) maps through the EXISTING From<TunnelEstablishError> for EstablishmentError table (src/producer.rs:142-154); a stale token is UnknownResource → details.reason == "unknown_resource" on the wire. Do not mint new variants. The gate SHOULD document that a miss is TunnelEstablishError::UnknownResource (that is the canonical reason the ADR pins), but the crate passes whatever the gate returns through.
  4. No timeout change: timeout: None (the alkcall 10s default) as the other registrations do; the gate is an in-memory map lookup.
  5. No scope-table change: the same tunnel_forwarded_spec() / tunnel:forwarded gate runs (the registry runs the ACL before the establishment phase, alkcall's open-op wrapper order; pin the order in a doc comment + a test, see the tests task).

Also in this task (the doc half of W1's API item)

  • Update the in-code docs that describe the miss as assembly-side-only: make_forwarded_op_handler's correlation-miss-semantics block (src/producer.rs:1014-1020) and register_tunnel_forwarded_openable's doc (1047-1056); both gain the two-shape posture: the establisher variant is the recommended shape (the miss is a typed failure at the open); the base shape stays for compatibility and keeps the post-open teardown-at-birth semantics (still the residual-race fallback under the establisher variant).
  • Re-export the new surface from lib.rs (convention 16).
  • The base register_tunnel_forwarded_openable stays byte-for-byte compatible (additive-only, per plan decision 4).

NOT in this task: the sibling-repo adoptions (alkssh W2 / alksocks W3, pointer tasks exist) and the gate-miss tests (the dedicated tests task; but do not land the API without landing its tests task after).

Acceptance Criteria

  • ForwardedGate type + register_tunnel_forwarded_openable_with_establisher landed in src/producer.rs, re-exported from lib.rs
  • The establisher parse-failure → typed channel:open_failed mapping is deliberate + documented (design decision 1)
  • Gate errors pass through the existing From<TunnelEstablishError> table; no new error variants
  • make_forwarded_op_handler / the base registration are unchanged in behavior (byte-for-byte compat)
  • The in-code docs updated: the two-shape posture (establisher = recommended; base = compatibility + residual-race fallback) replaces the assembly-side-only framing
  • cargo test green (existing suite untouched); clippy --all-targets -- -D warnings clean (native + wasm32); cargo fmt --check clean; default crate stays wasm-clean
  • --all-features test run green (the local feature surface is untouched)

References

  • docs/plans/003-correlation-miss-resolve.md §W1 (the API bullet; the decisions this finalizes)
  • docs/architecture/decisions/008-listen-metadata-forwarded-op.md §3
    • the ADR this task's predecessor lands (Amendment 2 or successor; cite whichever the ADR task landed)
  • src/producer.rs:982-1082 (the base serving shape: ForwardedHandler, make_forwarded_op_handler, register_tunnel_forwarded_openable); 142-154 (the From<TunnelEstablishError> table); 641-673 (the bindfirst_establisher, the wrap-into-OpenEstablisher closure pattern to mirror, including the Box::pin shape)
  • alkcall 0.10.0 ChannelCore::register_openable_with_establisher (src/channels/operations.rs:761; the establisher runs AFTER allocation, BEFORE the reply; failure → typed channel:open_failed, no phantom channel; timeout: None = the 10s default)
  • AGENTS.md conventions 11 (the ADR-049 establishment surface is load-bearing; typed-opaque plans), 8 (teardown-at-birth posture), 16 (re-export posture)

Notes

  • ForwardedGate (sync closure over the parsed TunnelForwardedParams) wraps into a private make_forwarded_gate_establisher → register_openable_with_establisher(spec, Some(...), make_forwarded_op_handler(handler), registry, auth, None), so the base handler is reused byte-for-byte and the base registration stays untouched (additive-only).
  • The design decisions were finalized exactly as the task sketched: (1) gate receives the PARSED params; establisher parse failure maps TunnelEstablishError::HandlerError → typed channel:open_failed (pinned in the establisher doc, an improvement over the base shape's silent birth-teardown for malformed ingress); the handler re-parses the raw input (deny-unknown at both layers, handler unchanged); (2) gate pass → Establishment::new(plan) with an EMPTY MARKER plan payload, pinned as a new pub ForwardedPlan unit struct (chosen over (): self-documenting, downcast-able by assemblies that want to assert the gate ran; the same typed-opaque posture as ADR-049 amendment 2); no reply-field contribution; (3) gate errors pass through the existing From<TunnelEstablishError> table, no new variants; the gate doc pins UnknownResource as the canonical stale-token reason; (4) timeout: None (10s default); (5) no scope-table change; the wrapper order (schema → ACL → allocate → establisher → handler) is pinned in the establisher doc; the order-pinning test lands with the tests task.
  • Doc updates: make_forwarded_op_handler's correlation-miss block + register_tunnel_forwarded_openable's doc now carry the two-shape posture. Two adjacent doc drifts were synced in the same wave per ADR-008 Amendment 2 ("syncs with the API task's in-code doc updates"): wire.md §The Forwarded Op's "no establisher" prose line, and tunnel_forwarded_spec()'s "No establisher" doc block in src/params.rs.

Summary

Landed the forwarded-op establisher variant per ADR-008 Amendment 2: ForwardedGate, ForwardedPlan, make_forwarded_gate_establisher (private), and register_tunnel_forwarded_openable_with_establisher in src/producer.rs, re-exported from lib.rs. A gate Err (and an establisher parse failure) now yields a typed channel:open_failed before the channel is born; the base shape stays byte-for-byte compatible as the compatibility + residual-race-fallback posture. In-code docs updated (two-shape posture) plus the wire.md/params.rs doc sync the amendment rides on this task. Verification: cargo test green (existing suite untouched), clippy --all-targets -D warnings clean native + wasm32, cargo fmt --check clean, cargo doc --no-deps clean (0 warnings), --all-features test run green. Follow-ups: the dedicated gate-miss tests task (tunnels/corrmiss-gate-tests), then the sibling-repo adoptions (corrmiss-alkssh-adopt, corrmiss-alksocks-adopt).