16 Commits
Author SHA1 Message Date
glm-5.3-flash 50182d7298 feat(review 008 Unit 3c): the gate-2 e2e harness; 0.8.0 release bookkeeping
- src/channels/gate2_tests.rs (ADR-051 gates 6/7, review 008 gate 2):
  the full consumer -> hub (HubLegTemplate) -> spoke relay e2e —
  the open resolves with the hub-allocated channel_id, `bound`
  survives the relay, data flows both directions with a fake 8-byte
  chunk header riding verbatim (the hub never parses the data plane,
  ADR-034/035), spoke-side close cascades to clean reclaim on both
  legs; hub-side disconnect (the consumer's duplex end dropped via a
  killable transport proxy) tears down both legs with the ledger
  decremented; the mid-establishment window (ADR-051 §6) pinned with
  its two reclaim signals — the consumer leg reclaims at its own
  transport EOF, the spoke channel (allocated before the establisher
  replied) is the honest residual, reclaimed when the spoke-leg
  transport ends; the channels/tty/sub standard-shape companion pins
  no derivation regression.
- src/channels/relay.rs: the relay's adopted producer-leg channel
  entry now reclaims when the relayed pump completes (teardown after
  pump_bidi) — the consumer-leg wrapper's teardown cannot see the
  producer leg's manager; the RelayPlan carries the spoke id for the
  reclaim.
- Release bookkeeping: 0.7.1 -> 0.8.0, the CHANGELOG entry covering
  Units 1-3 (Establishment reply projection, open_channel_with_reply,
  flavor-form discovery derivation, ChannelRelay, HubLegTemplate,
  gate-2 harness); review 008 Status -> Resolved with the U-1/U-2
  commit refs and the 955->945 errata note; ADR-051 Status ->
  all units landed + the §6 mid-establishment residual expanded to
  the two-reclaim-signal shape the gate pins; the stale "0.7.2"
  version mentions in ADR-047/049 corrected to 0.8.0 (the units land
  unreleased).

Verification: cargo test 669 passed / 0 failed; clippy --all-targets
-- -D warnings clean; fmt --check clean; cargo doc --no-deps clean;
cargo check + clippy on wasm32-unknown-unknown clean;
cargo publish --dry-run --allow-dirty passed.
2026-09-18 05:15:29 +00:00
glm-5.3-flash 098090d564 chore: bump to 0.7.1, changelog for the MSRV floor raise (1.85 -> 1.88)
Verification: 631 default / 648 all-features tests on stable, 1.88
toolchain test + clippy clean, clippy (all-targets, wasm32) clean,
fmt clean, doc clean, semver-checks clean, publish dry-run OK.
2026-09-09 19:36:25 +00:00
glm-5.3-flash db5530ed34 feat(cf-005): connect-side serving identity — ServingConfig.identity + transport-identity propagation
Verifies and fixes CF-005 (alktunnels reverse-flow POC W1): the
connect-side serving path built channel 0 internally and never set an
identity, so a scope-gated serving op could only be satisfied via the
payload auth_token. Token is now the fallback (hub-forwarding /
browser path); transport/key-based identity is the primary path.

- ServingConfig gains identity: Option<Identity> — the explicit
  override (remediation a). Semver-relevant struct-literal change →
  0.7.0 (minor bump at 0.x, wire surface unchanged).
- from_connection_with_serving propagates the transport
  Connection::identity() to the channel-0 connection via set_identity
  before the serving loop starts (remediation b) — mirrors the accept
  side's install-hook set_identity; process-local, nothing new on the
  wire.
- Dispatch identity precedence on the serving loop: payload
  auth_token → identity_provider, then ServingConfig.identity, then
  transport identity; identity-less dispatch still fails closed
  (FORBIDDEN).
- Public core::auth::NoopIdentityProvider (resolves nothing; the
  ServingConfig::default() provider — three private test copies
  existed).
- Regression gates: four cf005_* e2e tests (transport propagation,
  override precedence, identity-less denial, token fallback +
  precedence).
- Ledger CF-005 → resolved; ADR-022 §connect-side-serving amended;
  README example updated; changelog 0.7.0.

Verification: cargo test (629) + --all-features (646), clippy
(all-targets, all-features, -D warnings), fmt --check, doc
--no-deps, wasm32 check, semver-checks (no update required at
0.7.0), publish --dry-run.
2026-09-07 11:00:47 +00:00
glm-5.3-flash dc4ad2bc6d feat(review 007 Unit 1): Establishment carries the channel plan (R-01) + lifetime doc (R-02)
Implements ADR-049 amendment 2 — the reserved Establishment payload is
filled, and the OpenHandler lifetime contract is documented.

- Establishment { plan: Option<ChannelPlan> } with ChannelPlan =
  Arc<dyn Any + Send + Sync>: typed-opaque, because the payload an
  establisher hands the pump handler is a live handle (dialed socket,
  TTY handle), not JSON — the review's Option<Value> sketch could not
  satisfy its own verification gate. #[non_exhaustive] keeps a future
  carrier change from being another break. Construction:
  Establishment::new(plan) / Establishment::default().
- OpenHandler gains the plan parameter:
  Fn(Value, Option<ChannelPlan>, Connection, AuthContext) ->
  JoinHandle<()>. Separate parameter (not merged into input) — a
  typed payload cannot ride the JSON input; no schema collision.
  Wire surface unchanged: the plan is process-local (establisher ->
  wrapper -> handler).
- run_open_wrapper threads establishment.plan to the handler; None
  when no establisher is registered. Kills the alktunnels-POC
  side-channel handoff (resource-keyed slot + poll loop) whose
  concurrent same-resource race is now unreachable — each open's
  establisher result flows to its own handler.
- Lifetime contract documented (R-02, doc-only half): the returned
  JoinHandle must track the data-plane lifetime — the wrapper awaits
  it and its completion triggers teardown; early return = teardown
  at birth. Noted on the OpenHandler type docs and both registration
  entry points.
- Breaking at 0.6.0 (the point of landing it before alktunnels
  Phase 1): Ok(Establishment {}) sites become
  Ok(Establishment::default()) mechanically.

Verification: cargo test (621 passed, +4: plan-flows-to-handler,
concurrent same-resource opens get distinct plans, no-establisher
None plan, Establishment construction), clippy -D warnings, fmt
--check, doc clean, test --all-features clean.
2026-09-07 08:43:17 +00:00
glm-5.3-flash 2586c3b217 feat(review 006 Unit 1): channel-open establishment phase + typed client error (E-01, N-1)
Implements ADR-049 Unit 1 — the open-op wrapper gains an awaited,
bounded establishment phase, and the client stops erasing the error.

- OpenEstablisher hook + Establishment/EstablishmentError types:
  register_openable_with_establisher awaits the establisher bounded
  (earlier of dispatch deadline and per-registration timeout, else
  ESTABLISHMENT_TIMEOUT = 10s) after allocation, before the reply and
  before the pump handler is spawned (ADR-049 §1/§2). Implementation
  note: the establisher takes (input, auth) only — the channel's
  yield-once BiStream belongs exclusively to the pump handler
  (amendment recorded in ADR-049).
- Establishment failure: teardown_channel + opener-ledger take +
  policy.on_close un-increment (allocation and teardown balance;
  the ledger take is the atomic gate, ADR-047 §7), reply
  channel:open_failed with details {reason, message} — reason ∈
  dial_failed / unknown_resource / resource_shortage / handler_error
  / timeout (ADR-049 §3). SSH contract consumer-visible: a failed
  open never returns a channel_id.
- register_openable unchanged (no establisher = always-OK; existing
  registrations compile and behave identically — compat gate test).
- ChannelClient::open_channel returns ChannelOpenError (breaking at
  0.5.0): CallFailed { error: CallError } carries the wire error
  verbatim (establishment_reason() branches on details.reason);
  MissingChannelId / AdoptFailed cover the local-only shapes
  (ADR-049 §4, review 006 N-1).
- Tests cover all four verification gates from the review: e2e
  establisher failure through a real channels connection (typed
  reason + no-channel + ledger un-increment), bounded timeout,
  no-establisher compat, establisher-success pump round-trip; plus
  reason-vocabulary mapping and bound arithmetic.
- Bump to 0.5.0 (open_channel error-type change is semver-relevant).

Verification: cargo test (608 passed), clippy --all-targets -D
warnings, fmt --check, doc --no-deps, wasm32 check — all clean.
2026-09-06 19:00:04 +00:00
glm-5.3-flash a22b2b84c9 fix: park early-arrival chunks for un-adopted channels (open/first-data race)
The connect side adopts a channel (installs local routing state) only
after the open-op response arrives, but the accept side's OpenHandler
can start pumping data the moment the channel opens — the two race and
the demux's lenient unknown-channel drop (REQ-CH-04) silently lost the
producer's first chunks (a TTY backend's banner, a sub protocol's
greeting).

route_payload now parks up to 64 payloads per unknown channel_id in a
bounded early-arrival buffer; adopt_channel drains them into the new
receiver in order. Beyond the cap the chunk drops with the existing
debug log + dropped_unknown_chunks counter (which now also counts
overflow). clear_all drops parked buffers with the connection.

Surfaced by alktty's consumer end-to-end test (review #001 L3): the
session never resolved because the producer's first chunks (stdout
sentinel + exit chunk for an immediately-resolving backend) arrived
before the adopt and were dropped. REQ-CH-04 wording updated by this
behavior; ADR-039 §demux loop describes the lenient drop for genuinely
unknown channels, which remains the case past the cap.

Verification: cargo test 597 (2 rewritten for the new semantics +
route_payload_to_unknown_channel_parks_until_adopt gate);
--all-features 614; clippy -D warnings clean; fmt clean; doc 0
warnings; publish --dry-run ok; standalone probe (handler-writes-first
e2e over one connection) shows 0 dropped chunks with the fix vs 1
without.
2026-09-05 07:04:30 +00:00
glm-5.3-flash 574f58442a feat: enforce input_schema at call time (ADR-016 INVALID_INPUT leg)
- OperationSpec.input_schema was advertise-only: services/schema
  disclosed it but no dispatch entry point consulted it (the only
  enforced schema was publish_schema per-chunk on Pub ops, P-03).
- compile input_schema once at registration, same fail-closed rule as
  publish_schema/CF-003: an un-compilable schema is a registration
  error, never a silently-skipped contract. Validator cache mirrored
  on fork and in OperationRegistryBuilder like the publish validators.
- check after the ACL gate in all three dispatch entry points:
  invoke, invoke_streaming, invoke_sink (via resolve_sink_handler,
  preserving the P-08 single-source-of-truth property). Violations
  return INVALID_INPUT with the input echoed in details.
- raw-JSON-Schema semantics (permissive on unknown keys); adapters
  wanting closed-by-default keep their own hardening (alkhttp's
  CompiledInputSchema composes unchanged).
- motivated by alktty review #001 L1: the channels open-op wrapper
  hands the registry-checked input to the OpenHandler as the
  authoritative params, which requires the registry to validate it.

Verification: cargo test 596 lib (6 new: invoke/streaming/sink
enforcement, fail-closed registration, permissive-{} compile,
fork-carries-validator); --all-features 613; clippy -D warnings
clean; fmt clean; doc 0 warnings; publish --dry-run ok. alkhttp
438+16 tests pass against 0.3.1 (registry) — re-verify against the
published 0.4.0 after upload.
2026-09-05 06:33:00 +00:00
glm-5.3-flash ba94c70eb2 chore: bump to 0.3.1, changelog for the UP-03 list-peers fix
Verification: 590 default / 607 all-features tests, clippy
(all-targets, all-features, wasm32) clean, fmt clean, publish
dry-run OK.
2026-09-04 16:01:56 +00:00
glm-5.3-flash 1e20bb77d0 chore: prepublish review — bump to 0.3.0, changelog, doc alignment
0.2.0 is already on crates.io (2026-08-31, c16b069); the review
004/005 remediation work is unreleased on top of it and lands as
0.3.0.

- Bump version 0.2.0 -> 0.3.0. Two OperationRegistry methods changed
  borrowed returns to owned (registration, list_operations) —
  source-breaking for annotated call sites, minor bump per 0.x
  semver rules. cargo semver-checks passes (196 checks) against the
  published baseline; the return-type changes were caught by manual
  diff review.
- CHANGELOG 0.3.0: connect-side serving (from_connection_with_serving
  + ServingConfig), OperationRegistry::fork + builder from_registry,
  registry::op_register (bootstrap op, collision policy,
  ALREADY_EXISTS), install_bootstrap_discovery, spec_to_json_pub +
  resource_id_path round-trip, overlay accessors, concurrent serving
  loops, &self registration.
- README: serving-as-consumer section, consumer role table update,
  drop the stale `mut` on the registry example.
- AGENTS.md: ADR range 001..047 -> 001..048.
- Fix rustdoc private-intra-doc-link warning on StartedDispatch.

Verification: 589 default / 606 all-features tests, clippy
(all-targets, all-features, wasm32) clean, fmt clean, doc clean,
publish dry-run OK.
2026-09-04 14:14:22 +00:00
glm-5.3-flash c3d4fa1b30 chore: bump to 0.2.0
First release carrying the consumer-findings remediation (CF-001..004),
the feature-gated gateway dispatch spine (ADR-048), and the
registration-time publish_schema validation behavior change (CF-003).
Gate is version-only for existing consumers: the public 0.1.1 API
surface is unchanged (probe-verified).
2026-08-31 09:56:15 +00:00
glm-5.2 570fea76f4 chore: changelog, exclude list, wasm support, slim tokio features
- Add CHANGELOG.md (Keep a Changelog format) covering 0.1.1 and 0.1.0
- Exclude AGENTS.md from the published crate (docs/reviews/ and
  docs/sdd_process.md were already excluded)
- Slim tokio features: full → rt, sync, time, io-util, macros (no
  net/fs/process usage in the crate)
- Enable wasm32-unknown-unknown: uuid rng-getrandom + getrandom 0.4
  wasm_js so request-ID generation works on wasm
- Add wasm + semver-checks to AGENTS.md pre-release verification

Verification:
- cargo test: 543 passed, 0 failed
- cargo clippy --all-targets -- -D warnings: clean
- cargo fmt --check: clean
- cargo doc --no-deps: clean
- cargo check --target wasm32-unknown-unknown: clean
- cargo clippy --target wasm32-unknown-unknown -- -D warnings: clean
- cargo semver-checks check-release: 196 pass, no semver update required
- cargo publish --dry-run --allow-dirty: succeeds (103 files, 1.8MiB)
2026-08-17 06:58:17 +00:00
glm-5.2 44d4b496e8 refactor: drop alktype dependency; add BAST doc for chunk header
- Remove alktype from Cargo.toml (its only usage was a thin wrapper
  over jsonschema::options().build())
- Replace alktype::validation::build_validator with direct jsonschema
  in dispatch.rs
- Add docs/architecture/chunk-header.bast.json — the chunk header's
  BAST (Binary Abstract Syntax Tree) machine-readable wire spec
- Embed as channels::wire::CHUNK_HEADER_BAST via include_str! so
  downstream Rust crates can consume it without a file lookup
- Add test asserting the embedded BAST doc is valid JSON and matches
  the wire format
- Update AGENTS.md §10 and implementation-specialist.md: BAST docs
  are the contract; trivial/hot-path formats stay hand-rolled,
  complex formats use the alktype engine or codegen

Verification:
- cargo test: 543 passed, 0 failed
- cargo clippy --all-targets -- -D warnings: clean
- cargo fmt --check: clean
- cargo doc --no-deps: clean
- BAST doc compiles + round-trips against alktype v0.2.0 engine
2026-08-17 06:08:13 +00:00
deepseek-v4-pro 08e7df2aa0 feat: rename ALPN prefix from alknet/ to alk/ (v0.1.1)
- CHANNELS_ALPN: b"alknet/channels" → b"alk/channels"
- CallAdapter::alpn(): b"alknet/call" → b"alk/call"
- derive_alpn_from_op_name: alknet/ prefix → alk/ prefix
- All ALPN string literals in src/ and docs/ updated
- ADR-004 amended with prefix rename rationale
- AGENTS.md, README.md updated
- Version bumped to 0.1.1

Review: docs/reviews/003-alpn-prefix-rename.md

Verification:
- cargo test: 542 passed, 0 failed
- cargo clippy --all-targets -- -D warnings: clean
- cargo fmt --check: clean
- cargo doc --no-deps: clean
2026-08-14 13:55:28 +00:00
glm-5.2 f362c9e596 fix: Unit 8 — Pub protocol semantics (P-03, P-04, P-07)
The publish path's decided-but-unimplemented behaviors land: per-chunk
schema validation, initiator call.error terminates the stream, and an
early handler return short-circuits the feed. alktype::validation::
build_validator is now used on both dispatch paths (the dependency was
declared and unused); jsonschema was added as a direct dependency
because its Validator type is part of alktype's public API.

P-03 — OperationSpec::publish_schema was declared, had a builder,
defaulted to None, and was never read. It now round-trips through
discovery: spec_to_json emits "publish_schema" when Some,
operation_spec_schema() advertises the property, and rebuild_spec_for
parses it back (null/absent treated as None, so an imported Pub op no
longer loses the schema). Both dispatch paths (pump_sink and
run_loop_single_stream) validate each call.published chunk's input
against the schema before yielding it to the SinkHandler; on
validation failure an Err(CallError::invalid_input(...)) with the
offending chunk in `details` is injected and the feed terminates,
matching the SinkHandler contract that an Err terminates the stream.
The validator is built once at dispatch time and carried in the
SinkDispatch (and, for the single-stream path, in a new InFlightSink
struct alongside chunk_tx); a schema that fails to compile logs a
warn and falls back to no validation. When publish_schema is None,
chunks are yielded as-is (unchanged behavior).

P-04 — pump_sink matched only call.published/call.completed/call.
aborted and dropped everything else into a debug-log ignore branch; a
call.error from the initiator was silently ignored. Both dispatch
paths now match call.error, parse the CallError from the payload,
inject it as Err(call_error) into the sink's chunk_tx, and terminate
the feed — the handler sees the initiator's error, not a synthetic
"aborted" message. A malformed payload falls back to
CallError::internal("publish error from initiator (malformed)").

P-07 — pump_sink used tokio::join!(feed_fut, handler) and only wrote
the response after both completed; an early-returning handler (e.g.
rejects after chunk 1) had its response deferred until the initiator
finished publishing or the next chunk_tx.send failed. Replaced with a
tokio::select! loop over handler.fuse() and reader.read_frame(): when
the handler completes first, the response is captured and the loop
breaks immediately, the feed is short-circuited (chunk_tx dropped,
the handler's PublishStream sees EOF), and the response is written
without waiting for the feed. The feed-wins branch (natural end /
abort / error / read failure) awaits the handler after the loop as
before. This removes the unbounded stall on a path ADR-046 intends
for long-lived streams.

Tests (15 new, 480 total):
- discovery: spec_to_json emits/omits publish_schema; operation_spec_
  schema documents the property (3).
- from_call: rebuild_spec_for parses publish_schema (present/omitted/
  null) and round-trips with spec_to_json (4).
- dispatch (stream-per-request): publish_schema rejects invalid chunk
  + passes valid chunks + no-schema yields as-is (3); initiator
  call.error terminates with the initiator's error + malformed
  fallback (2); early handler return short-circuits a slow feed and
  the response is not deferred (1).
- channels/client (single-stream): publish_schema rejects invalid
  chunk over channel 0; initiator call.error terminates the publish
  over channel 0 (drives run_loop_single_stream directly with crafted
  frames since the public publish() API takes Stream<Item = Value> and
  cannot emit an initiator call.error) (2).

Verification:
- cargo test              → 480 passed, 0 failed (was 465; +15 new tests)
- cargo clippy --all-targets -- -D warnings → clean
- cargo fmt --check       → clean
- cargo doc --no-deps     → 0 warnings
2026-08-13 09:43:39 +00:00
glm-5.2 f305f8c0a5 feat: implement channels protocol + ADR-047 (openable ALPNs are operations)
ADR-047: the unifying decision that  dissolves into
per-ALPN ops (, ) with a
 marker on . Each openable ALPN registers
its own ops with their own , ,
, and the marker. The  field is replaced
by  (Sub/Pub). The generic ops (channel/close,
channel/control, channel/resources/subscribe) stay, keyed by
channel_id. Resolves Gaps A-G from the research findings (Gap B broker
named out-of-scope for alkcall; Gap C relay wrapper is consumer
concern; Gap D connection-owner allocates; Gap E extension trait;
Gap F boolean marker on wire; Gap G ACL/ownership complementary).

ADR-037 amended:  dissolves;  removed; generic
ops stay;  preview dropped from resources/subscribe.

Spec docs updated: channel-operations.md (unified model, opener ledger,
ACL flow), operation-registry.md (channel_open marker, ChannelOpenSpec),
README.md (ADR-047), open-questions.md (OQ-31..38 resolved).

Source changes:
- spec.rs: ChannelOpenSpec struct, channel_open field on OperationSpec,
  with_channel_open builder, 3 tests
- discovery.rs: spec_to_json emits channel_open boolean,
  operation_spec_schema includes channel_open, 2 tests
- from_call.rs: rebuild_spec_for parses channel_open marker,
  derive_alpn_from_op_name helper, 6 tests

Channels module (src/channels/, 10 files, ~2400 lines):
- wire.rs: 8-byte chunk header (ChunkHeader, parse/write_header,
  read_header/write_chunk/write_eof async helpers), 12 tests
- reassembly.rs: MpscRecvStream (tokio::mpsc::Receiver<Bytes> →
  AsyncRead), MpscSendStream (AsyncWrite → tokio::mpsc::Sender<Bytes>),
  REQ-CH-01 shutdown sentinel, REQ-CH-02 sender-drop EOF, 10 tests
- mux.rs: MuxHandle (clone-able, register(channel_id)),
  MuxRunner (per-channel pump tasks, exits when handles drop),
  OpenerLedger (ADR-047 §7), 4 tests
- manager.rs: ChannelManager (channel map, open_channel,
  install_channel_zero, route_payload, teardown_channel, clear_all),
  11 tests
- source.rs: ChannelBidiStreamSource (yield-once accept_bi),
  channel_source helper, 4 tests
- adapter.rs: ChannelsAdapter (ProtocolHandler for alknet/channels,
  demux loop, install_channel_zero hook), 1 test
- operations.rs: ChannelOperations (registers channel/close,
  channel/control, channel/resources/subscribe), ChannelCore
  (check_open/on_close wrappers), 4 tests
- policy.rs: ChannelLifecyclePolicy trait, NoCap, PerIdentityChannelPolicy
  (default 256, per_identity_caps override), default_policy, 8 tests
- env.rs: ChannelOperationEnv extension trait (ADR-047 §4),
  ChannelsSessionEnv impl, 2 tests
- client.rs: ChannelClient (from_connection, call_open_op,
  take_call_connection), 1 test

Verification: 432 tests pass (66 new channels + 10 marker + 356
existing), clippy clean, fmt clean, cargo doc generates.

Cargo.toml: +bytes dependency.
2026-08-12 12:13:53 +00:00
glm-5.2 4bc7a19695 feat: extract alknet-call into alkcall, vendor core types
Phase 1 of the alknet-call + alknet-channels unification. The call crate
is extracted verbatim from /workspace/@alkdev/alknet/crates/alknet-call
and the needed alknet-core types are vendored into src/core/ — alkcall is
the home for these types going forward (no separate alkcore crate).

Vendored core types (src/core/):
- auth.rs: Identity, AuthToken, AuthContext, IdentityProvider
- ownership.rs: OwnershipProvider, OwnershipStore, InMemoryOwnershipStore,
  OwnershipError
- types.rs: ProtocolHandler, Connection, BiStream, BidiStreamSource,
  SendStream, RecvStream, HandlerError, StreamError, Capabilities, Secret,
  IdentityAlreadySet
- Stripped: quinn/iroh/rustls deps, Connection::from_quinn/from_iroh
  (the dial lives in the consumer per ADR-089), config/credentials/
  fingerprint/store modules, ConfigIdentityProvider/IdentityStore

Call crate (src/client, src/protocol, src/registry/):
- Copied verbatim from alknet-call; alknet_core::{auth,types,ownership}
  rebound to crate::core::{auth,types,ownership}
- ALPN strings unchanged (alknet/call — wire-stable per ADR-006)
- No behavioral changes

Verification:
- cargo test: 343 passed (0 failed)
- cargo clippy --all-targets -- -D warnings: clean
- cargo fmt --check: clean
- cargo doc --no-deps: 1 pre-existing broken intra-doc link
  (CallAdapter in dispatch.rs — Phase 2 cleanup)

Next: Phase 2 — channels greenfield implementation + ALPN rename +
architecture doc porting.
2026-08-12 05:52:18 +00:00