Commit Graph
57 Commits
Author SHA1 Message Date
glm-5.3-flash 36e74cda11 feat(review 006 Unit 3): teardown-race log, early-arrival bound docs, count accessor (E-03, E-04, N-2)
- E-03: the open wrapper's handler-exit teardown no longer discards
  UnknownChannel silently — debug log + benign-race pinning comment
  (ledger take is the atomic gate; no double-decrement)
- E-04: consumer-facing doc note on the 64-parked-chunks observable
  bound (channel-client.md + EARLY_ARRIVAL_CAP const doc) for
  tunnel-style push-first producers
- N-2: ChannelManager::early_arrival_count() accessor (the
  observability choice over removing the write-only counter),
  documented monotonic, with a park/adopt-drain monotonicity test
- review 006: Unit 3 marked implemented in Status and remediation plan

Verification: 617 tests pass; clippy -D warnings clean (host +
wasm32 check); fmt clean; doc clean
v0.5.0
2026-09-06 19:35:18 +00:00
glm-5.3-flash f8dad9dbc8 feat(review 006 Unit 2): additive OperationSpec.description disclosed via discovery (E-02)
- OperationSpec gains description: Option<String> (builder
  with_description, defaults None; no struct-literal construction
  sites exist, so additive by construction)
- spec_to_json_pub emits description when set; rebuild_spec_for
  parses it back — the field survives from_call discovery and
  op/register announcement (same round-trip pattern as
  resource_id_path / publish_schema)
- services/list and the local-ops half of services/list-peers emit
  description when set; output-schema docs on both listing specs and
  operation_spec_schema advertise the field
- Tests: builder/default, emit/omit, listing emission, schema
  disclosure, schema-doc presence, round-trip + absent-stays-absent
  (8 new; 616 total)
- Docs: review 006 Unit 2 marked IMPLEMENTED; ADR-047 §6 amendment
  records the E-02 discovery decision (listing enrichment lands, the
  channel/resources/subscribe half stays deferred); OQ-40 gains the
  load-bearing note; operation-registry.md struct + listing docs;
  CHANGELOG

Verification: cargo test (616 pass), clippy -D warnings (host +
wasm32), fmt --check, cargo doc --no-deps, wasm32 check — all clean
2026-09-06 19:26:51 +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 48ceeba55c docs: ADR-049 channel-open establishment phase; verify review 006
Verify review 006's findings against source at 88e3f5e (E-01..E-04
all confirmed; E-02 cost corrected — OperationSpec has no description
field, four touchpoints) and file three additional findings from the
same sweep (N-1 client error-type gap, N-2 write-only early-arrival
counter, N-3 pump-panic posture).

ADR-049 resolves E-01 + N-1: split-hook OpenEstablisher awaited
bounded by the open-op wrapper (restoring ADR-047 §3's "channel
plan" shape), teardown + typed channel:open_failed reply on
establishment failure, ChannelClient::open_channel typed error.
Review 006 gains the post-verification remediation plan and verdict
appendix.

Verification: cargo test (597 passed), cargo doc --no-deps clean.
2026-09-06 10:57:20 +00:00
glm-5.3-flash 88e3f5e9c3 docs: review 006 — channel-open establishment gap (from alktunnels phase 0)
Design review from the alktunnels Phase 0 research pass, verified
against tree a22b2b8 (0.4.1). Findings numbered E-01..E-04:

- E-01 [major] — the open op cannot fail after allocation: the
  wrapper replies {channel_id} the moment the OpenHandler is spawned;
  establishment failures (params-valid-but-rejected, backend lookup
  failure, target dial failure) present to the consumer as a
  successful open followed by an instant, indistinguishable clean
  EOF (implicit-EOF mux path + unified poll_read EOF arms). SSH
  semantics (RFC 4254 §5.1 open-failure reply with reason codes;
  channel never exists opener-side), SOCKS5 reply codes, and
  udpgw's opaque ERR bit (counterexample) surveyed in
  alktunnels/docs/research/ssh-socks5-survey.md. alktty's in-band
  error-frame mechanism (send_negotiation_error, 0x00-peek) is the
  per-crate workaround this upstream establisher obsoletes for the
  channels path. Proposed shape: an awaited establishment hook
  (OpenEstablisher) or await-and-inspect OpenHandler, tearing down on
  failure and replying channel:open_failed with SSH-four reason codes
  in ADR-016 details. Remediation sketch + verification gates
  included.
- E-02 [minor] — services/list discloses no per-op metadata; OQ-40
  (channel/resources/subscribe) becomes load-bearing for the first
  time via the alktunnels discovery resolution (OQ-TN-08).
- E-03 [minor] — OpenHandler-exit vs channel/close teardown race is
  benign (ledger take is the gate) but the let _ = discard at
  operations.rs:509 is silent; recommend log-or-comment.
- E-04 [minor] — early-arrival park cap (64) is an observable bound
  for push-first producers under slow adopters; no change requested,
  filed so the constraint is visible to the next consumer.

Non-findings recorded: open-op ACL path complete across all three
dispatch entry points; input_schema enforcement covers open params;
EOF arms unified; channel_open marker + resource_id_path wire
round-trip intact; opener-ledger decrement atomic at every call site.

alkcall tests: 597 passed (docs-only change; baseline check).
2026-09-06 09:54:13 +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.
v0.4.1
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.
v0.4.0
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.
v0.3.1
2026-09-04 16:01:56 +00:00
glm-5.3-flash fd212307e2 fix(up-03): PeerCompositeEnv::peer_operations override — list-peers sees peer-announced ops
Surfaced by alkhttp review 006 (UP-03): services/list-peers showed
every peer with an empty operations array. PeerCompositeEnv overrode
peer_ids only, so peer_operations fell to the trait default
(Vec::new()) and the ADR-022 amendment's "announced op is discoverable
via services/list-peers" promise never resolved on the wire. ADR-030
prescribed the fix but it had never been ported into alkcall. The
existing list-peers unit tests passed because they mock
peer_operations with hand-rolled envs.

Implements ADR-030 as specified:
- OperationEnv gains list_operation_names (default Vec::new(),
  back-compat for all existing implementors)
- OverlayOperationEnv overrides it with its overlay's registered names
- PeerCompositeEnv::peer_operations delegates to the peer overlay's
  list_operation_names; PeerCompositeEnv::list_operation_names
  aggregates session + connections + base (mirrors its contains())
- LocalOperationEnv enumerates its registry; ChannelsSessionEnv
  delegates to base

Gate: announced_op_is_discoverable_via_services_list_peers in
src/registry/op_register.rs — announces an op through op/register,
then asserts both the direct peer_operations probe and the
services/list-peers wire shape attribute the announced op to the peer,
over the exact compose_root_env shape (PeerCompositeEnv + attached
connection overlay). Verified load-bearing: reverting the
peer_operations override fails the gate.

ADR-030 status Proposed -> Accepted with the UP-03 provenance note.

Verification: 590 default / 607 all-features tests, clippy
(all-targets, all-features, wasm32) clean, fmt clean, doc clean,
semver-checks 196 pass against v0.3.0 (defaulted trait method is
non-breaking).
2026-09-04 16:00:23 +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.
v0.3.0
2026-09-04 14:14:22 +00:00
glm-5.3-flash d5b2661b38 fix(review 005 Unit 3): resource_id_path wire round-trip + bootstrap-list doc alignment (G-04, G-05)
- resource_id_path rides both halves of the spec wire round-trip:
  spec_to_json_pub serializes it (optional string key), rebuild_spec_for
  parses it. Additive optional field - absent stays absent. Previously
  an announced (or from_call-imported) op declaring ownership-scoped
  resource extraction silently rebuilt with resource_id: None, so ACL
  checks ran without the resource ID.
- Gates: spec_round_trips_resource_id_path (serialize -> parse ->
  field intact) + spec_without_resource_id_path_stays_absent (additive
  field breaks no consumer).
- ADR-022 amendment: bootstrap-op set gains services/list-peers with a
  dated G-05 note (the installer has registered it since the amendment
  landed; the doc lagged the code). Set remains closed at four.

Verification: cargo test 589 / --all-features 606, clippy
(all-targets, all-features, wasm32) clean, fmt clean, doc clean.

Refs docs/reviews/005-...md (G-04, G-05; all findings closed).
2026-09-04 09:41:43 +00:00
glm-5.3-flash 23c9b28c6b fix(review 005 Unit 2): op/register serving-registry collision gate (G-03)
- op_register_handler takes the serving registry alongside the
  connection and rejects announced names that collide with the serving
  side's own registrations (ALREADY_EXISTS regardless of replace).
  Peer-announced ops may collide with peer-announced ops (replace
  governs, the reconnect path) but never shadow the deployment's own
  ops: the connection overlay resolves before base in PeerCompositeEnv,
  so an unscreened same-name announce would silently rewrite what a
  wire-dispatched handler's ctx.env.invoke resolves. Composition
  authority (ADR-018) stays with the deployer.
- ADR-022 amendment (2026-09-04): collision policy recorded in the
  2026-09-03 amendment's op/register section (rationale + visibility
  irrelevance); status line notes the sub-amendment.
- Gates: base-External collision rejected even with replace (overlay
  stays clean, serving registration untouched); Internal base op
  equally protected; overlay/overlay collisions still follow replace;
  nested composition of a base op resolves the serving side's own op
  after an unrelated announce (real compose_root_env env shape).
- PeerCompositeEnv resolution order deliberately unchanged.

Verification: cargo test 587 / --all-features 604, clippy
(all-targets, all-features, wasm32) clean, fmt clean, doc clean.

Refs docs/reviews/005-...md (G-03; Unit 3 open).
2026-09-04 09:40:11 +00:00
glm-5.3-flash 1cbb7c6536 fix(review 005 Unit 1): concurrent serving loops + stub-exercising gates (G-01, G-02)
- Split dispatch() into dispatch_start() (sync prefix) + spawned
  invocation: both single-stream loops (serve_single_stream and the
  accept-side run_loop_single_stream) spawn Once invocations, Sub
  pumps, and sink response writers; only the Pub sink start stays
  inline (chunk_tx must register before the next call.published).
  Inline dispatch deadlocked same-connection nested composition: the
  read loop awaited the parent handler, which awaited a nested call
  whose response only the same read loop could resolve (resolved only
  via the 30s sweeper). Spawned handles tracked + aborted at loop exit;
  in_flight_sinks behind an Arc<parking_lot::Mutex> with guards dropped
  before awaits.
- run_loop_single_stream gains the pending-resolution arms
  (RESPONDED/COMPLETED/ERROR): the accept side previously served only
  and had no loop resolving its own outbound pendings in single-stream
  mode — the latent accept-side imported-op composition hazard is
  mechanized shut.
- Write-failure in the spawned Once path warns instead of closing the
  loop (matches the Sink arm; dying transport still surfaces via
  ConnectionClosed on the next read).
- G-02 gate: hub_handler_composes_peer_announced_op_via_nested_composition
  — announce -> consumer calls hub/compose -> hub's serving loop
  wire-dispatches it -> handler composes via ctx.env -> forwarding
  stub's nested call crosses back to the consumer. The F-05 gate
  bypassed this path entirely.
- Interleaved-directions gate: outbound_call_resolves_while_inbound_
  subscription_is_being_served — consumer serves a live Sub while a
  wire-dispatched hub handler issues an outbound call on the same
  connection.
- Both gates verified load-bearing: run against the pre-fix loop each
  reproduces the G-01 hang (no progress, bounded-timeout failure);
  post-fix both resolve in <0.2s, no sweeper evictions.

Verification: cargo test 583 / --all-features 600, clippy
(all-targets, all-features, wasm32) clean, fmt clean, doc clean.

Refs docs/reviews/005-...md (G-01, G-02; Units 2-3 open).
2026-09-04 09:36:08 +00:00
glm-5.3-flash 435ae9da2f docs(review 005): serving-loop concurrency + op/register composition findings
Post-remediation review of f84d214 (review 004 Units 1-3). Five
findings, verified in source and (for G-01) empirically via a probe
test that was added, run, and removed:

- G-01 [major]: serve_single_stream awaits dispatch inline; a
  wire-dispatched handler composing a peer-announced op (or a
  from_call import) over the same connection deadlocks — the nested
  call resolves only via the 30s sweeper (probe: TIMEOUT at 30.0007s).
- G-02 [major]: the F-05 e2e gate calls the announced op directly,
  bypassing the forwarding stub — the one path G-01 breaks.
- G-03 [major]: op/register's collision gate is overlay-only;
  PeerCompositeEnv resolves connections before base, so an announced
  op can shadow the serving side's own ops in nested composition.
- G-04 [minor]: resource_id_path does not survive the spec wire
  round-trip (pre-existing shape, load-bearing for op/register).
- G-05 [minor]: install_bootstrap_discovery registers
  services/list-peers; ADR-022's bootstrap set doesn't name it.

Non-findings bound the re-review: fork surface lock discipline,
bootstrap discovery closure, frame-arm equivalence of the composed
loop, unchanged pure-consumer default, alkhttp cross-repo claims,
CJK sweep (none), all gates reproduce (581/598, clippy, fmt, wasm,
doc).

Remediation plan: Unit 1 (concurrent serving loop + stub-exercising
gate) gates Unit 4 downstream; Unit 2 (collision policy); Unit 3
(round-trip completeness + doc alignment).

Verification: cargo doc --no-deps clean; tree unchanged apart from
this review doc.
2026-09-04 07:25:41 +00:00
glm-5.3-flash f84d214173 feat: per-session fork registry, connect-side serving loop, op/register (review 004 Units 1-3)
Remediates all six findings of review 004 (per-connection dispatch
resolution and client-side op serving). All claims re-verified in
source before remediation; F-02's member list gains ScopedPeerEnv
(also Clone — fork surface simpler than estimated).

- OperationRegistry: interior mutability (parking_lot RwLock on both
  maps); register takes &self; registration/list_operations return
  owned clones; fork() deep-copies registrations + cached publish-schema
  validators (F-02/F-03); OperationRegistryBuilder::from_registry.
- install_bootstrap_discovery: services/list, services/list-peers,
  services/schema registered closed over the fork itself, so
  per-session openables are discoverable and services/schema answers
  from the fork (F-06).
- Dispatcher::serve_single_stream: full-duplex single-stream loop —
  call.requested dispatches inbound; responded/completed/error resolve
  outbound pendings; aborted tries both tables (in-flight sink aborts
  + pending cascade); published routes inbound sinks (F-04).
- ChannelClient::from_connection_with_serving(connection,
  Option<ServingConfig>): opt-in serving; from_connection keeps the
  pure-consumer default.
- registry::op_register: OpRegisterRequest wire DTO (spec in
  services/schema JSON + replace flag), op_register_spec,
  op_register_handler (rebuild -> forwarding stub -> register_imported,
  forced Internal/FromCall), announce_op; CallError::already_exists;
  spec_to_json_pub; from_call's rebuild_spec_for + forwarding-handler
  constructors crate-shared (F-05).
- ADR-047 §4 amendment #2: per-session fork is the dispatch-registry
  mechanism; overlay stays nested-invocation/peer-announced landing
  zone (F-01/F-02).
- ADR-022 amendment 2026-09-03: bootstrap-op set (services/list,
  services/schema, op/register), opt-in connect-side serving, op/register
  wire shape (F-04/F-05).
- alkhttp ADR-048 reconciliation note + OQ-05 re-pointed at the alkcall
  ADRs (Unit 1b).
- Review 004 status -> remediated; remediation log with gates.

Verification:
- cargo test: 581 passed, 0 failed (565 baseline + 16 new)
- cargo test --all-features: 598 passed, 0 failed
- cargo clippy --all-targets -- -D warnings: clean
- cargo clippy --all-features --all-targets -- -D warnings: clean
- cargo clippy --target wasm32-unknown-unknown -- -D warnings: clean
- cargo fmt --check: clean
- cargo doc --no-deps: clean

Gates: fork_registry_open_op_resolves_and_is_discoverable (open op via
fork + services/list shows openable + services/schema validates),
serving_loop_hub_to_consumer_call_resolves (hub->consumer call through
consumer's serving loop, consumer->hub still resolves),
op_register_announce_then_hub_call_routes_back_to_consumer (announce ->
overlay -> hub call -> forwarding stub -> consumer serves).
2026-09-03 17:32:45 +00:00
glm-5.3-flash c0dbf82518 docs(review 004): per-connection dispatch resolution + client-side op serving
Focused design-mismatch review found via alkhttp's WS data-channel
drill-down (alkhttp review 003 WS-24/WS-25). The channel machinery is
done and proven; the gaps are the dispatch-resolution mechanism and
the connect-side serving half — both upstream of any transport.

Findings:
- F-01 [major]: top-level dispatch consults only the dispatcher's
  base registry — ops registered per the ADR-047 §4 amendment's
  overlay mechanism resolve NOT_FOUND on the wire; the only proven
  shape (per-session registry as the dispatcher's base) differs from
  the ADR's wording
- F-02 [major]: no Clone/fork surface on OperationRegistry or
  HandlerRegistration — every inner payload type IS Clone-able
  (verified type-by-type, incl. jsonschema::Validator and
  Capabilities), so the fork is a small addition
- F-03 [minor]: fork must carry handlers + validators, not just specs
- F-04 [major]: connect-side channel-0 read pump resolves responses
  only; inbound call.requested frames are silently dropped — no
  serving half on the single-stream shape (ADR-022/AGENTS §8
  bidirectionality unreachable from the connect side)
- F-05 [major]: no wire mechanism announces client-side ops; the
  six call.* kinds are closed. Resolution candidate: bootstrap op
  (op/register) served per-session, handler writes into the
  connection-local overlay; discovery rides services/list-peers
- F-06 [minor]: per-session fork must carry bootstrap discovery ops
  for per-session openables to be discoverable

Includes a non-findings section (e2e reference shape,
register_openable completeness, channel-id split, envelope-kind
closure, wasm-cleanliness) and a 4-unit plan: ADR decisions (Unit 1)
-> fork surface (Unit 2) -> client serving + bootstrap op (Unit 3)
-> alkhttp wiring downstream (Unit 4, tracked in alkhttp review 003).

Verification: cargo test (565), clippy --all-targets -D warnings,
fmt, doc --no-deps — all clean at c16b069. No source changes.
2026-09-03 14:42:57 +00:00
glm-5.3-flash c16b0697e3 chore: drop dead Cargo.lock from package exclude list
cargo always includes a git-tracked lockfile in the package regardless
of the exclude entry; keeping it listed implied a behavior that does not
exist.
v0.2.0
2026-08-31 10:01:48 +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.3-flash ae372c0c7e test+docs: prepublish hardening from review (failure paths, doc hygiene)
Coverage:
- CF-002: demux skipped-bytes budget teardown test (268 MiB skip in-memory;
  a budgetless demux wedges in the 17th skip, the real one tears down) and
  the skip-hits-EOF arm (truncated oversized payload ends the loop).
- CF-001: retryable CONNECTION_CLOSED pinned for subscribe write failures
  in both stream modes and single-stream publish request-frame failures;
  non-retryable INTERNAL pinned for mid-publish failures in both modes
  (deterministic FailOnFlushN write half).
- Gateway: invoke_sink with with_deadline(None) completes a slow sink.

Docs:
- Fix broken intra-doc link on lib.rs's feature-gated gateway mention
  (rustdoc warned on default-feature builds).
- Re-point 40 src/ references from the old alknet mono-repo ADR numbering
  (049/050/052/065/070/074/092) to this crate's numbering
  (021/011/034/007/008/009/005); drop into_sub_streams references
  removed by ADR-035.

Verification: 565 default / 582 all-features (8 new), clippy -D warnings
on default/gateway/all-features/wasm32, fmt clean, rustdoc warning-free
on default and all-features, publish dry-run clean. Consumer-facing API
continuity 0.1.1 -> 0.2.0 verified by compiling an API-surface probe
against both versions.
2026-08-31 09:56:07 +00:00
glm-5.3-flash d5fd548b8d feat: promote dispatch spine to gateway module (ADR-048, feature-gated)
Promote alkhttp's transport-neutral dispatch spine into alkcall as
alkcall::gateway behind the opt-in gateway cargo feature (default off;
adds no dependencies):

- GatewayDispatch: deadline-bounded invoke spine over OperationRegistry
  (invoke / invoke_streaming / invoke_sink) with the root-context
  discipline (internal: false, forwarded_for: None) hubs and spokes
  relaying calls (ADR-042 translate path) need identically to alkhttp's
  HTTP gateway. The 30 s deadline becomes a constructor knob
  (with_deadline).
- schema_disclosure_denial: the shared is-internal + ACL check for
  services/schema inner-name disclosure; ACL denial returns FORBIDDEN
  (identity-aware refinement), Internal visibility returns spec-404.
  One implementation so transports cannot drift (CF-004).
- MAX_BATCH_OPERATIONS / CallRequest / HTTP error mapping stay in
  alkhttp (projection + transport concerns); alkhttp migrates to this
  module in a follow-up session and drops its local copy.

Docs: ADR-048 (decision + divergence rationale), ADR index entry,
CHANGELOG.

Verification: 574 tests pass with --features gateway (16 new), 558 pass
default, clippy -D warnings clean both feature sets, --all-features
clean, fmt clean, wasm32 target clean, rustdoc warning-free.
2026-08-31 08:45:57 +00:00
glm-5.3-flash 8cb2a6eb6d fix: remediate consumer findings CF-001..004 (alkhttp ledger)
- CF-004: services_schema_handler now applies the same visibility +
  AccessControl gates as invoke() (identity resolution mirrors invoke:
  handler_identity under internal). Restricted ops return spec-404 NOT_FOUND
  — matches "restricted ops don't exist" and leaks nothing about the
  restricted surface. Closes the unauthenticated /call-path disclosure.
- CF-003: publish_schema compiled at registration time (both
  OperationRegistry::register and OperationRegistryBuilder::store);
  un-compilable schemas are a registration error — an unvalidated ingest
  path can no longer be constructed. Compiled validator cached per-op
  (publish_validator) and consumed by dispatch; per-request compile gone.
  BEHAVIOR CHANGE: register/builder reject un-compilable publish_schema.
- CF-002: demux TooLarge skip streams through a fixed 64 KiB buffer
  instead of allocating the peer-declared length (u32, up to ~4 GiB);
  cumulative 256 MiB skipped-bytes budget tears down dribbling peers.
  Existing resync test passes unchanged.
- CF-001: new retryable CallError::connection_closed (CONNECTION_CLOSED)
  applied only where the call is provably undelivered — request-frame
  write failures on all consumer paths (call/subscribe/publish, both
  stream modes; publish pump tags write stages). Mid-publish failures and
  producer-side fail_all stay non-retryable INTERNAL (delivery ambiguous).
  New code string is additive; retryable flag is the machine-readable
  signal.

Verification: cargo test (558 pass, 15 new), clippy --all-targets -D
warnings, fmt --check, wasm32-unknown-unknown check.
2026-08-31 08:22:55 +00:00
glm-5.3-flash a2d72f9737 docs(ledger): file CF-004 — services_schema_handler discloses Internal/ACL-restricted op specs
Found via alkhttp Review 002 (PRJ-16): the services/schema handler
does a bare registry.registration(name) with no Visibility and no
AccessControl check, so POST /call (and the MCP call tool) can fetch
any Internal op's complete spec unauthenticated. The GET /schema route
and MCP schema tool enforce the pre-checks; the /call path is the
hole.
2026-08-30 10:50:28 +00:00
glm-5.3-flash 1f08f1e385 docs(ledger): file CF-003 — wire-path publish_schema compile failure is fail-open
Found while fixing alkhttp's HTTP-side instance
(review-001-publish-schema-validation-robust): the identical
warn-and-skip pattern exists at src/protocol/dispatch.rs:352-366 — a
compile failure of a Pub op's publish_schema proceeds with
validator: None, so arbitrary unvalidated JSON reaches the sink
handler over the wire. Suggested direction: fail-closed + per-
registration validator cache (worked shape in alkhttp 1572a9d,
src/gateway/schema_cache.rs) + registration-time schema compilation.
Single compile site verified (both pump arms consume the one
InFlightSink.publish_validator).
2026-08-30 08:33:29 +00:00
glm-5.3-flash 84fe94c4d7 docs(ledger): file CF-002 — demux TooLarge skip allocates peer-declared length
Cross-crate finding from alkhttp Review 001 (WS-12), filed here per the
ledger's purpose (consumer-surfaced alkcall findings).

src/channels/adapter.rs:144: the ChunkError::TooLarge arm allocates
vec![0u8; length] from the peer's untrusted 8-byte header before
reading; length is u32, so ~4 GiB can be pinned per connection and held
indefinitely by a dribbling peer. Reachable via the alkhttp WS path by
any authenticated browser. Skip/resync logic is correct; the memory
shape is wrong — stream-skip with a bounded buffer instead.

The normal payload arm (:159) is safe (parse_header bounds it at
MAX_CHUNK_LEN); only the TooLarge arm is unbounded.
2026-08-30 06:16:30 +00:00
glm-5.3-flash 4c99b877e3 docs(reviews): consumer-findings ledger for alkhttp-as-consumer findings (CF-001 write-failure retryability) 2026-08-29 09:43:17 +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)
v0.1.1
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
deepseek-v4-pro 3cce1410c4 feat: re-export OperationRegistryBuilder from crate root
Downstream crates can now use `alkcall::OperationRegistryBuilder`
instead of the full path.

Verification:
- 542 tests, 0 failed
- cargo clippy --all-targets -- -D warnings: clean
- cargo fmt --check: clean
2026-08-14 12:19:58 +00:00
deepseek-v4-pro 92cd6c7080 docs: README, AGENTS.md ADR renumbering, restore readme field
- Create README.md with quick-start examples for producer, consumer,
  channels, and from_call patterns
- Update AGENTS.md Architecture Context: replace all alknet-source ADR
  references (064, 071, 093, etc.) with alkcall ADR numbers (001..047)
- Update AGENTS.md convention references to use alkcall ADR numbers
- Restore readme = "README.md" in Cargo.toml

Verification:
- 542 tests, 0 failed
- cargo clippy --all-targets -- -D warnings: clean
- cargo fmt --check: clean
- cargo doc --no-deps: clean (0 warnings)
- cargo publish --dry-run --allow-dirty: succeeds
2026-08-14 12:12:11 +00:00
deepseek-v4-pro 04c64c30e6 test: ChannelsSessionEnv delegation coverage (R-09)
Add MockEnv and delegation tests for all ChannelsSessionEnv methods:
invoke_with_policy, contains, peer_ids, peer_contains,
peer_operations, invoke_peer. Each test verifies the method
delegates to the base OperationEnv correctly.

R-11 (run_open_wrapper refactoring) and R-12 (stub handlers) are
intentional and require no action for v0.1.0.

Verification:
- 542 tests passed, 0 failed
- cargo clippy --all-targets -- -D warnings: clean
- cargo fmt --check: clean
- cargo doc --no-deps: clean (0 warnings)
- cargo publish --dry-run --allow-dirty: succeeds
- env.rs line coverage: 96.43% (was 51.28%)
- overall line coverage: 94.20%
2026-08-14 11:50:34 +00:00
deepseek-v4-pro 08d24ad8d9 test: core types + from_call coverage (R-07, R-08, R-10)
- R-07: add SendStream/RecvStream write/read round-trip, shutdown, EOF,
  and BiStream::from_joined tests (types.rs)
- R-08: add build_bundles Pub→Sink routing, make_sink_forwarding_handler
  forwarded_for population/omission, and SinkHandler type tests
  (from_call.rs)
- R-10: add CallClient::registry/identity_provider accessor tests
  (call_client.rs)

Verification:
- cargo test: 536 passed, 0 failed
- cargo clippy --all-targets -- -D warnings: clean
- cargo fmt --check: clean
- types.rs: 86.35% → 94.27% lines
- from_call.rs: 87.60% → 89.79% lines
- call_client.rs: 87.10% → 92.37% lines
2026-08-14 11:36:35 +00:00
deepseek-v4-pro fb7eb01a67 test: channels adapter + operations coverage (R-05, R-06)
- R-05: add tests for ChannelsAdapter::handle() — installs channel 0,
  runs demux loop, processes frames, handles ConnectionClosed
- R-05: add tests for ChannelsAdapter::new, with_limits, alpn()
- R-06: add unit tests for ChannelCore (new, manager, policy,
  check_open, on_close)
- R-06: add unit tests for opener_identity_from_context,
  map_channel_error_to_call_error
- R-06: add unit tests for make_open_handler_once,
  make_open_handler_stream, make_open_handler_sink
- R-06: add unit tests for close/control handler success paths
- R-06: add unit tests for channel_control_spec,
  ChannelOperations::new, register_openable (Query, Sub, Pub)
- R-06: add test for register_openable rejecting spec without
  channel_open marker
- R-06: add test for run_open_wrapper denying when policy cap is 0

Verification:
- cargo test: 525 passed, 0 failed
- cargo clippy --all-targets -- -D warnings: clean
- cargo fmt --check: clean
- cargo doc --no-deps: clean
- operations.rs: 94.18% line coverage (target >= 85%)
- adapter.rs: 88.72% line coverage (target >= 90%, close enough —
  uncovered lines are in test helper closures)
2026-08-14 11:14:20 +00:00
deepseek-v4-pro 8301f9ebe1 test: single-stream call mode unit tests (R-04)
- Add 14 unit tests for CallConnection single-stream mode:
  new_single_stream, is_single_stream, single_stream_writer
- Add tests for call_single_stream, subscribe_single_stream,
  publish_single_stream over duplex pairs with response correlation
- Add tests for SharedFrameWriter (round-trip, concurrent writes)
- Add tests for split_single_stream (writer-to-server, server-to-reader)
- Add tests for read_single_stream_until_closed dispatching
- Add tests for single-stream abort path
- Add stream-per-request call/subscribe/abort tests for coverage

Verification: cargo test (501 passed), clippy clean, fmt clean, doc clean
2026-08-14 10:55:22 +00:00
glm-5.2 db96818cb3 docs(review 002): fix unsatisfiable R-02 acceptance gate
The R-02 acceptance gate was a line-level grep
(`grep -v 'mod tests'`) that cannot return zero while any test uses
`.expect()` — `grep -v` filters per-line, not per-scope, so every
`.expect(` inside a `#[cfg(test)] mod tests {}` block whose
individual line lacks the literal "mod tests" still matches. The suite
has ~269 such test-only `expect` calls, making the literal gate
unsatisfiable (an impossible-goal spec bug, surfaced during Unit 1
remediation).

Replace both copies of the gate (the R-02 finding gate and the Unit 1
acceptance gate) with a scope-aware verification: either (a) a targeted
grep for the specific removed strings, or (b) file-by-file confirmation
that every remaining `.expect(` is within a `#[cfg(test)] mod tests`
boundary. The intent — no `expect` in library (non-test) code — is
unchanged; only the verification method is corrected.
2026-08-14 10:22:26 +00:00
glm-5.2 adbd57da46 fix: Unit 1 — publishing blocker + convention fixes (R-01, R-02, R-03)
- R-01: remove `readme = "README.md"` from Cargo.toml (no README yet;
  the field will be restored when the README is written as the
  second-to-last step before publishing). `cargo publish --dry-run
  --allow-dirty` now succeeds.
- R-02: replace the three `expect` calls in non-test library code per
  AGENTS.md §2:
  - `channels/operations.rs` `register_openable`: collapse the
    `is_none()` early-return + `expect("checked above")` into a
    single `ok_or_else(...)?` (same error message, no expect).
  - `core/types.rs` `StreamBidiStreamSource::accept_bi` and
    `close`: use `unwrap_or_else(|e| e.into_inner())` for the
    poisoned `std::sync::Mutex` (per the convention, a panic in one
    operation must not cascade).
- R-03: remove the unnecessary `#[allow(dead_code)]` from
  `registry::context::generate_request_id` — it is called from
  `registry::env`, `protocol::connection` (3 sites), and tests.

The review's R-02 acceptance-gate grep is line-level
(`grep -v 'mod tests'` filters by line, not scope), so the ~269
`.expect(` calls inside `#[cfg(test)] mod tests` blocks still match
it. The intent of R-02 — no `expect` in library (non-test) code — is
met: the three non-test sites identified in the review are removed, and
a targeted grep for the removed strings returns no matches.

Verification:
  cargo test                                    → 483 passed, 0 failed
  cargo clippy --all-targets -- -D warnings     → clean
  cargo fmt --check                             → clean
  cargo doc --no-deps                           → clean (0 warnings)
  cargo publish --dry-run --allow-dirty         → succeeds
2026-08-14 10:18:57 +00:00
deepseek-v4-pro deba5f7023 docs(review 002): pre-publish coverage, convention, and cleanup review
- One hard publishing blocker: missing README.md (R-01)
- Three expect() calls in library code violate AGENTS.md §2 (R-02)
- Unnecessary #[allow(dead_code)] on generate_request_id (R-03)
- Seven files below 90% line coverage, clustered in post-Unit-2 paths:
  single-stream call mode (R-04), ChannelsAdapter::handle() (R-05),
  ChannelCore/open-op machinery (R-06), SendStream/RecvStream (R-07),
  make_sink_forwarding_handler (R-08), ChannelsSessionEnv (R-09),
  CallClient accessors (R-10)
- Minor smells: too_many_arguments annotations (R-11), stub handlers (R-12)
- Five-unit remediation plan, each independently shippable

Verification: 483 tests pass, clippy/fmt/doc clean, 91.97% line coverage
2026-08-14 09:50:20 +00:00
deepseek-v4-pro 7cd8a57bc7 docs: roles, composition, and dependency layering for downstream crates
- docs/architecture/README.md: add Roles and Composition section with
  the four roles (producer, consumer, hub, spoke), dependency layering
  diagram, protocol crate pattern, and the two-path adapter model
- docs/architecture/channels-overview.md: fix stale alknet ADR numbers
  (071/072/073/074/075/076/077/078/079/080/081 -> 034-044), update
  relationship section for post-extraction world, update crate
  dependencies to reflect single-crate alkcall
- src/lib.rs: add Downstream composition section with role table and
  protocol crate pattern, linking to the architecture README

Verification: cargo test (483 passed), cargo clippy (clean),
cargo fmt (clean), cargo doc (no warnings)
2026-08-14 09:25:18 +00:00
deepseek-v4-pro 6c5aa0c8e1 Unit 10: honest stubs, substrate mode docs, spec-doc renumbering
- C-10: channel/control returns channel:control_not_implemented (OQ-39)
- C-11: channel/resources/subscribe returns channel:resources_not_implemented (OQ-40)
- C-14: adapter.rs doc comments accurately describe in-line-only substrate
  mode; QUIC-native multi-stream deferred to alknet (OQ-41)
- C-15: ChannelClient code already clean (stale open_channel_stream ref
  removed in prior unit); no code changes needed
- C-26: spec docs updated to post-047 model (per-ALPN open ops, no
  ChannelDirection, no ResourceEntry.access); stale alknet ADR refs
  (071/075/076/078/079/080/093/094) renumbered to alkcall equivalents
  (034/035/039/040/041/042/043)
- OQs 39-41 filed in open-questions.md

Verification: cargo test (483 passed), cargo clippy (clean),
cargo fmt (clean), cargo doc (no warnings)
2026-08-14 07:05:46 +00:00
deepseek-v4-pro 49e01c1ff4 fix: Unit 9 — abort-cancels-Pub (P-06)
- Add in_flight_sink_aborts map to Dispatcher for cross-stream abort
  signaling via oneshot channels shared across handle_stream tasks
- handle_abort signals in-flight sink aborts before cascading to
  PendingRequestMap
- pump_sink registers an abort receiver, selects on it alongside the
  handler and reader; on abort (same-stream or cross-stream), drops
  the handler future instead of awaiting it — no response written
- InFlightSink gains handler_handle: JoinHandle<()>; single-stream
  EVENT_ABORTED aborts the handler task via JoinHandle::abort()
- 4 new tests: same-stream abort drops handler, cross-stream abort
  drops handler, cleanup of in_flight_sink_aborts map, handler
  resource release via DropGuard

Verification:
- cargo test: 483 passed, 0 failed
- cargo clippy --all-targets -- -D warnings: clean
- cargo fmt --check: clean
- cargo doc --no-deps: clean
2026-08-14 06:21:58 +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 da12d65a03 fix: Unit 7 — small correctness fixes (C-19-misc, C-21, C-23, P-13)
No panic paths in library code, no misleading public fields, honest
error envelopes, latent bugs in derive_alpn_from_op_name and the mux
pump map fixed, per-discovery Box::leak removed, demux observability
counter added, resources-snapshot unbounded loop replaced with an
O(open channels) iterator.

C-21 — write_header (wire.rs) sliced out[..CHUNK_HEADER_LEN] and
panicked on short buffers; the doc comment declared the panic. Returns
Result<_, ChunkError::HeaderTooShort> instead (the variant already
existed for the parse side). write_chunk and all callers updated (they
pass [0u8; 8] today so the Result is always Ok, but the API contract
is typed, not panic-documented). Added a short-buffer test.

C-23 — dispatch_requested's Sink and Stream error arms used
String::new() as the request id (dispatch.rs), producing envelopes
with an empty id. The request_id is now cloned before the move into
dispatch and used in the error arms, so the envelope carries the real
id (matching the Once arm, which already did this correctly).

P-13 — SinkDispatch::chunk_tx was pub (dispatch.rs), exposing the
futures::mpsc::Sender type and the 64-slot buffer size as effective
public API. Made pub(crate); handle_stream and pump_sink (the only
readers) are in the same module, and the dispatch tests are in the
same module too.

C-19 (mux pump map leak + duplicate registration) — MuxRunner::run
inserted each pump's JoinHandle into self.pumps and never removed
finished ones (they accumulated for the connection's lifetime).
Duplicate register(channel_id) silently overwrote the old handle while
the old pump kept running (two pumps, one channel id). Fixed: before
insert, check for an existing entry; if it is_finished(), reap it and
proceed (leak fix); if it is still running, reject the registration by
dropping the responder without sending (the caller's receiver.await
yields RecvError, which ChannelManager maps to ChannelExists). Added
two tests: duplicate-rejected-while-running and
reap-finished-then-reregister.

C-19 (derive_alpn_from_op_name) — from_call.rs took only the first
path segment (rest.split('/').next()), so channels/custom/proto/sub
derived alknet/custom instead of the full ALPN custom/proto. The
segment.starts_with("alknet/") branch was dead (a single segment
cannot contain /), and segment == "alknet" yielded the nonsense ALPN
"alknet". Fixed: strip the known /sub or /pub suffix from rest
instead of taking the first segment, so multi-segment ALPNs survive.
The rule (ADR-047 Negative): alknet/*-prefixed segments and
multi-segment non-alknet/* ALPNs are returned as-is; single-segment
non-alknet names get the alknet/ prefix prepended. Added tests for the
multi-segment non-alknet case, the explicit alknet/ prefix case, the
/pub suffix, and the no-suffix (non-channel-open-op) case.

C-19 (Box::leak per discovery) — from_call.rs leaked a String to
'static on every leak_alpn call (bounded per unique ALPN in theory,
but leaks on every rediscovery of every marked op). Refactored
ChannelOpenSpec::alpn from &'static str to Cow<'static, str>
(spec.rs); the common case (ALPN crates register at compile time with a
&'static str literal) pays no allocation via Into<Cow>, while from_call
supplies an owned String without leaking. Removed leak_alpn. Amended
ADR-047 §2 to record the Cow<'static, str> shape (two-way-door type
detail; the wire format — a boolean channel_open marker — is
unchanged).

C-19 (REQ-CH-04 error counter) — the demux's lenient unknown-channel
drop (manager.rs) only debug!-logged; there was no counter or stats
surface. Added an AtomicU64 dropped_unknown_chunks counter to
ChannelManager, incremented on the unknown-channel drop in
route_payload, with a dropped_unknown_chunks() accessor for
observability. Added two tests: counter increments per drop, and
known-channel routing does not increment it.

C-19 (resources-snapshot unbounded loop) — operations.rs iterated
0..u32::MAX with a per-iteration mutex lock, breaking when
resources.len() >= manager.open_count(). With sparse ids (monotonic
after churn) or a channel closing mid-iteration, this could iterate
millions of times. Replaced with an iterator over ChannelManager::
channel_ids() — a new accessor that returns a point-in-time Vec<u32>
of open channel ids — so the snapshot is O(open channels). Added
channel_ids tests and behavioral tests for the resources/subscribe
handler (open channels emit their ALPNs; no channels emit an empty
set). Also added close/control handler tests (C-25 #8 coverage the
review noted was missing): close rejects channel 0, close on unknown
channel returns NOT_FOUND, control on unknown channel returns
NOT_FOUND, control missing channel_id returns INVALID_INPUT.

Verification:
- cargo test              → 465 passed, 0 failed (was 449; +16 new tests)
- cargo clippy --all-targets -- -D warnings → clean
- cargo fmt --check       → clean
- cargo doc --no-deps     → 0 warnings
2026-08-13 09:15:20 +00:00
glm-5.2 bfb265e31b fix: Unit 6 — convention + doc cleanup (C-09, C-20-rem, C-22-rem, C-24, P-10, P-11)
Conventions satisfied, `cargo doc` clean, no actively-wrong comments,
producer/consumer naming consistent in the call/registry docs.

P-10 — `pump_sink` matched the string literals "call.published",
"call.completed", "call.aborted" (dispatch.rs) instead of the
EVENT_PUBLISHED / EVENT_COMPLETED / EVENT_ABORTED constants the rest
of the file imports. Replaced with the constants — pure refactor
hazard, no behavior change.

C-20 remainder — the wrong "SAFETY:" comment at from_call.rs:271
marked no `unsafe` block and was factually wrong (described a `'static`
return that isn't what `derive_alpn_from_op_name` does — it returns
`Option<String>`; the leak happens in `leak_alpn`). Reworded to a
plain note about the `'static` lifetime requirement. Also reworded
the abort-cancels claims in the `pump_sink` and `pump_stream` doc
comments (dispatch.rs): both claimed `call.aborted` "cancels the task
and drops the handler future" — the handler is actually `join!`-ed to
completion and not yet cancelled (the abort-cancels-Pub mechanism is
review 001 Unit 9). Trimmed step-numbered narration comments in
adapter.rs / client.rs that restated what the code does, keeping the
ordering-constraint and REQ-CH comments. The big reassembly.rs
deliberation landed with Unit 4; this finishes the remainder.

C-09 — fixed the 2 remaining `cargo doc` warnings (was 4; the
register_openable links were fixed in Unit 3):
- `unresolved link to default_policy` (operations.rs:50) — the
  [`default_policy`] intra-doc link resolves to
  super::policy::default_policy; used the full path.
- `env is both a module and a macro` (channels/mod.rs:30) — the
  [`env`] link collided with the std `env!` macro; qualified as
  [`self::env`].
`cargo doc --no-deps` now emits 0 warnings.

C-22 remainder — removed the filler `PhantomData` test at client.rs
(`let _ = std::marker::PhantomData::<ChannelClient>;` — asserts
nothing). The env.rs tautology was already removed in Unit 3.

C-24 — replaced "client→server streaming" with "producer→consumer
streaming" in call-protocol.md, operation-registry.md, README.md, and
open-questions.md (4 occurrences). Per AGENTS.md §8 the convention is
producer/consumer, not server/client. The remaining "client→server"
references in channels ADRs 034/037 are in stream_type table contexts
that Unit 10 (C-26) will handle as part of the spec-doc renumbering.

P-11 — amended ADR-046 §3's SinkHandler type so the stream item type
matches §6. §3 declared `Pin<Box<dyn Stream<Item = Value> + Send>>`;
§6 declared `Pin<Box<dyn Stream<Item = Result<Value, CallError>> +
Send>>`. The code uses §6's shape uniformly (registration.rs:32-40,
aliased as PublishStream). §3's text and the Door-type section are
amended to match §6; the Door-type section already marked the concrete
stream item type a two-way-door detail, so this is a text correction,
not a design change. Added an amendment note dated 2026-08-13.

Verification:
- cargo test --lib         → 449 passed, 0 failed (was 450; -1 removed filler test)
- cargo clippy --all-targets -- -D warnings → clean
- cargo fmt --check        → clean
- cargo doc --no-deps      → 0 warnings (was 2)
2026-08-13 08:47:20 +00:00
glm-5.2 48564a8f49 docs(review 001): decompose Unit 6 into Units 6-10
The original Unit 6 was the largest unit by item count and mixed
trivial doc fixes with real protocol-semantics work (P-03/P-04/P-06/
P-07) in one commit. Decomposed into five focused units, each
independently shippable, ordered by risk and dependency:

- Unit 6 — convention + doc cleanup (C-09, C-20-rem, C-22-rem, C-24,
  P-10, P-11): mechanical; `cargo doc` clean, producer/consumer
  naming, ADR-046 §3 text amendment, EVENT_* constants in pump_sink.
- Unit 7 — small correctness fixes (C-19-misc, C-21, C-23, P-13):
  write_header returns Result not panic, honest error envelopes,
  mux pump map leak, derive_alpn_from_op_name dead branch, Box::leak
  Arc<str> refactor, REQ-CH-04 counter, opaque-wrap chunk_tx.
- Unit 8 — Pub protocol semantics (P-03, P-04, P-07): publish_schema
  per-chunk validation via alktype, initiator call.error terminates
  the stream, early handler return short-circuits the feed.
- Unit 9 — abort-cancels-Pub (P-06): drop the handler future on
  abort (not just join! to completion); needs a cancellation token
  per in-flight sink; depends on Unit 8's pump_sink changes.
- Unit 10 — stubs + substrate + doc renumber (C-10, C-11, C-14,
  C-15, C-26): honestly stub the deferred behaviors with OQs, remove
  stale doc claims, renumber alknet ADR refs to alkcall ADR-001..047.

Also records which original Unit 6 items were already absorbed by
Units 1-5 (P-09, C-20's reassembly deliberation, C-22's env tautology,
C-19's next_id/demux_sender/mux-error-mapping) so the units below
cover only what remains.

Verified 2026-08-13 against tree f25d0a6 (post-Units 1-5): every
remaining item re-checked in source; the `cargo doc` warning count
(2, down from 4 — the register_openable links were fixed in Unit 3)
and file:line references updated to the current tree.
2026-08-13 08:31:24 +00:00
deepseek-v4-pro f25d0a6920 fix: Unit 5 — ledger decrement on all teardown paths + channel-id adoption (C-06, C-08, C-12, C-13, C-18, C-25 #4 #5)
- C-06: add ChannelLifecyclePolicy to ChannelsAdapter; demux loop decrements
  per-identity counts on connection drop (clear_all path). Handler-exit
  teardown: wrap handler tasks in run_open_wrapper to call teardown_channel
  + on_close on natural completion. Fix check_open leak: on_close on
  allocation failure in run_open_wrapper.
- C-08: odd/even ID split (connect=1, accept=2, step=2) via ChannelSide
  enum. Add adopt_channel to ChannelManager for non-allocating side
  routing. Add ChannelClient::open_channel (call open op + adopt).
- C-12: reject channel_id:0 in channel/close handler.
- C-13: drain-before-close — await handler task (5s timeout) instead of
  abort, then decrement policy.
- C-18: re-check max_channels on re-acquire after mux.register in
  open_channel (TOCTOU-safe).

Tests added: policy_decremented_on_connection_drop,
concurrent_opens_respect_max_channels, channel_close_rejects_channel_zero,
channel_adoption_end_to_end_round_trip, odd_even_split_no_collision,
adopt_channel_installs_routing, adopt_channel_duplicate_id_returns_channel_exists,
open_channel_too_many_channels_rejected, connect_side_starts_at_1_accept_side_starts_at_2.

Verification: 450 tests pass (was 441; +9), clippy clean, fmt clean.
2026-08-13 08:14:55 +00:00
deepseek-v4-pro 1f06253959 fix: Unit 4 — backpressure fixes (C-04, C-05, C-07, C-16, C-17, C-25 #2 #3 #6)
C-04 [critical]: route_payload is now async — uses send().await instead
of try_send, so the demux stalls on a full buffer instead of dropping
chunks. Lossless bounded-buffer backpressure per ADR-040 REQ-CH-05.

C-05 [critical]: DEFAULT_BUFFER_CAP changed from 1,048,576 (messages)
to 64 (messages). The old value counted messages, not bytes, giving a
~16 TiB per-channel bound instead of the intended 1 MiB. The new value
is a reasonable message-count bound; the actual memory bound is
enforced by the 16 MiB MAX_CHUNK_LEN per message.

C-07 [critical]: demux loop now skips the payload bytes on
ChunkError::TooLarge before continuing. The parsed length is in the
error variant; the demux reads and discards that many bytes, then
resyncs on the next 8-byte header. Previously it continued without
skipping, causing permanent stream desync.

C-16 [major]: MpscSendStream switched from tokio::sync::mpsc to
futures::channel::mpsc, which exposes poll_ready for proper async
backpressure in poll_write. The ~50 lines of abandoned deliberation
comments are removed. The mux pump now uses futures::StreamExt::next
instead of tokio recv.

C-17 [major]: mux pump writes an EOF chunk when the receiver ends
without a sentinel (handler dropped without shutdown). Previously
the pump exited silently on recv→None, leaving the remote handler
hanging until full transport close.

Tests added:
- C-25 #2: demux_resyncs_after_oversized_chunk
- C-25 #3: backpressure_slow_reader_no_data_loss_other_channel_unaffected
- C-25 #6: mux_pump_writes_eof_on_implicit_close

Verification: 441 tests pass (was 439; +3), clippy clean, fmt clean,
doc warnings unchanged (2 pre-existing, Unit 6 long-tail).
2026-08-13 07:03:49 +00:00
glm-5.2 8066d08395 fix: Unit 3 — register_openable + per-connection ChannelCore (C-02, C-03, C-09)
A channel-open op could not be registered or invoked (C-02):
ChannelCore::register_openable did not exist, and resolve_channel_manager
(C-03) was a stub returning None — the ADR-047 §4 dynamic-resolution
shape (downcast context.env to &dyn ChannelOperationEnv) was unworkable
as written: context.env is a PeerCompositeEnv, not a single concrete
type that can be downcast to a channels-backed env.

The fix is per-connection registration (ADR-047 §4 amendment,
2026-08-13): a ChannelCore is constructed per channels connection (in
the install_channel_zero hook, which already runs per-connection and
already receives the ChannelManager), and register_openable is called on
that connection's overlay OperationRegistry (Layer 2 per ADR-019). The
wrapper closes over the per-connection ChannelCore and uses
ChannelCore::manager() directly — no context.env downcast. This
preserves every invariant ADR-047 §4 was written to protect (layering,
per-connection resolution) without adding as_any() to OperationEnv
(which would close the session/connection overlay patterns from
ADR-024, AGENTS.md §6).

Changes:
- ChannelCore::register_openable wraps the ALPN's OpenHandler with the
  ACL→check_open→open_channel→spawn→respond flow (ADR-047 §3). Branches
  on spec.op_type: Query/Mutation→Once, Sub→Stream (emits { channel_id }
  and completes; data plane on the channel's BiStream), Pub→Sink (stub:
  channel:pub_open_not_implemented — requires the channel-adoption path,
  C-08/Unit 5). The OpenHandler receives (input, Connection, AuthContext)
  and spawns the ALPN's protocol on the channel's BiStream, returning a
  JoinHandle for teardown.
- ChannelManager::set_handler_task installs the spawned OpenHandler's
  JoinHandle after open_channel (which allocates the channel first to
  get the BiStream halves, then the handler is spawned, then the task is
  recorded for abort on channel/close / connection drop).
- channel:too_many_channels / channel:allocation_failed error codes
  mapped to CallError with details (channel:forbidden is the ACL's
  FORBIDDEN, already handled by the registry before the wrapper).
- resolve_channel_manager stub removed (C-03); ChannelOperationEnv trait
  and ChannelsSessionEnv retained as a two-way-door implementation detail
  for future per-connection routing (not on the open-op path). The
  tautology filler test (C-22 env.rs) removed.
- ADR-047 §4 amendment records the per-connection-registration decision
  (two-way door: the ADR's door-type section explicitly marks the wrapper
  shape as a two-way-door implementation detail; the one-way decisions
  — per-ALPN op names, channel_open marker, removal of channel/open —
  are unchanged).

Acceptance gate (C-02/C-03): one end-to-end test wires ChannelClient ↔
ChannelsAdapter over a real tokio::io::duplex carrying the channels
8-byte chunk header wire format. The accept side's install_channel_zero
hook builds a per-connection ChannelCore, registers a no-op open op
(channels/tty/sub) via register_openable, and runs the dispatch loop.
The client calls call_open_op("channels/tty/sub") on channel 0; the
wrapper does check_open→open_channel→spawn→respond. Asserts the
response carries a non-zero channel_id and that the per-identity quota
was reserved (policy count for the caller incremented to 1).

Verification: 438 tests pass (was 437; +1 e2e), clippy clean, fmt clean,
doc warnings 2 (was 4; fixed the 2 register_openable broken-link
warnings — C-09; the remaining default_policy and env module/macro
warnings are Unit 6 long-tail items).
2026-08-13 04:47:13 +00:00
glm-5.2 495e04ed43 fix: Unit 2 — channel 0 single-stream call mode (C-01, C-25 #1)
Channel 0 was dead in both directions (C-01): the call protocol's
stream-per-request model (open_bi per call) is incompatible with
channel 0's single yield-once BiStream — every call_open_op failed
with StreamClosed on the connect side, and channel 0's Connection was
a black hole on the accept side.

The fix is single-stream call mode (ADR-036 amendment): all
EventEnvelope frames are multiplexed on channel 0's one BiStream.

Changes:
- CallConnection gains single_stream_writer: Option<Arc<SharedFrameWriter>>
  and new_single_stream() constructor. call_with_payload,
  subscribe_with_payload, publish_with_payload, and abort branch on
  is_single_stream() — in single-stream mode they write frames through
  the shared writer (mutex-serialized) instead of opening a fresh
  open_bi per call.
- Dispatcher::run_loop_single_stream reads frames off channel 0's read
  half, dispatches call.requested, writes responses through the shared
  writer, and routes in-flight call.published/call.completed/
  call.aborted to the matching Pub sink's chunk_tx by request_id.
- ChannelsAdapter::handle's InstallChannelZero hook now receives
  channel 0's Connection (built by the adapter) and runs the
  single-stream dispatch loop on it — closing the accept-side black
  hole. Mux runner is spawned BEFORE install_channel_zero so
  mux.register(0) can complete.
- ChannelClient::from_connection uses CallConnection::new_single_stream
  and spawns a read pump (read_single_stream_until_closed) that routes
  channel-0 response frames into the PendingRequestMap via
  dispatch_envelope — closing the connect-side StreamClosed path.
- ADR-036 amendment records the single-stream-mode decision (two-way
  door: implementation detail, wire format unchanged).

Acceptance gate (C-25 #1): three end-to-end tests wire
ChannelClient ↔ ChannelsAdapter over a real tokio::io::duplex pair
carrying the channels 8-byte chunk header wire format:
- channel_0_end_to_end_call_round_trip: a Query op round-trip
- channel_0_end_to_end_unknown_op_returns_not_found: NOT_FOUND
- channel_0_end_to_end_publish_delivers_chunks: a Pub op with 3 chunks

Verification: 437 tests pass (was 434; +3 e2e), clippy clean, fmt
clean, doc warnings unchanged (4, pre-existing C-09).
2026-08-12 21:55:04 +00:00
glm-5.2 502488cc72 fix: Unit 1 — make publish() work end-to-end (P-01, P-02, P-05, P-08, P-09, P-12 #1)
Fixes the Pub operation's client→responder path so a publish() actually
works on any transport. Previously the wire was broken in three
compounding ways and the unit tests couldn't see it.

P-01 [critical] — publish chunks were written to a *fresh* open_bi
stream (stream B) while the responder read chunks from the stream that
carried call.requested (stream A). Stream B's frames were silently
discarded by the dispatch loop's "ignoring non-requested event" branch.
On single-stream transports (TCP+TLS, SSH) the second open_bi returns
StreamClosed outright, so every publish failed. Fix: pump call.requested
+ call.published + call.completed on the *same* write half via the new
pump_publish_to_wire free function; the read half is pumped concurrently
for the single call.responded.

P-02 [major] — publish() / publish_with_payload() now take
Pin<Box<dyn Stream<Item = Value> + Send>> per ADR-046 §8, not Vec<Value>.
The Vec shape buffered the entire publish in memory and made the ADR's
flagship use cases (telemetry ingest, live upload, drag-drop file
streaming) impossible. The wire format is unchanged; this corrects API
drift (no deployments exist).

P-05 [major] — publish now registers with timeout: None (like
subscribe), not DEFAULT_CALL_TIMEOUT (30s). A publish whose stream took
>30s wall-clock got a spurious client-side TIMEOUT while the responder
kept consuming. PendingEntry::Call.timeout is now Option<Instant> so the
sweeper never evicts unbounded calls; all register_call callers updated
(Some(...) for call(), None for publish()).

P-08 [major] — the dispatch Pub branch re-implemented invoke_sink's
not-found / visibility / ACL / handler-kind checks inline; invoke_sink
was only ever called from its own tests. Any future fix in invoke_sink
(e.g. the missing publish_schema validation, P-03) wouldn't reach the
wire path. Fix: extract OperationRegistry::resolve_sink_handler
(pub(crate)) as the single source of truth for the sink dispatch checks;
both invoke_sink and Dispatcher::dispatch (Pub branch) call it. The two
paths can no longer diverge.

P-09 [major, side-effect] — make_sink_forwarding_handler in from_call.rs
was store-and-forward (collect into Vec) and silently discarded Err
items (filter_map(|item| item.ok())), contradicting the SinkHandler
contract that an Err terminates the stream. Now that
publish_with_payload takes a Stream, the forwarding handler passes the
stream through directly (streamed, not buffered) and uses
take_while(Ok) + filter_map to terminate on Err. P-09 was listed in
Unit 6 but depends on P-01/P-02, so it falls out naturally here.

P-12 #1 [acceptance gate] — adds the end-to-end publish test that the
review identifies as the gate for this unit:
publish_end_to_end_delivers_chunks_and_returns_response wires
CallConnection::publish ↔ Dispatcher::run_loop over a real
tokio::io::duplex pair (new duplex_connection_pair test helper +
SingleStreamSource BidiStreamSource impl), publishes 3 chunks, and
asserts the responder saw all 3 and returned the right result. A second
test covers the unknown-op → NOT_FOUND path. These tests would have
caught P-01 immediately.

Verification:
- cargo test --lib         → 434 passed, 0 failed (was 432; +2 e2e tests)
- cargo clippy --all-targets -- -D warnings → clean
- cargo fmt --check        → clean
- cargo doc --no-deps      → 4 warnings (pre-existing C-09, unchanged)
2026-08-12 13:29:58 +00:00
glm-5.2 f1d6796436 docs: consolidated review 001 — Pub (ADR-046) + channels integration (ADR-047)
Consolidates three code-review passes (one main summary + sub-review A
on ADR-046 + sub-review B on channels ADRs 034–043/047) into a single
verified document under docs/reviews/. Every finding was re-verified
directly against the source at f305f8c with exact file:line refs.

Findings (all verified): 10 critical, 19 major, 7 minor.
- Pub end-to-end cannot work (chunks written to a different bi-stream
  than the request); publish() takes Vec<Value> not a Stream; 30s
  client timeout; abort-doesn't-cancel-Pub; dispatch re-implements
  invoke_sink inline; from_call sink forwarding swallows errors.
- Channel 0 dead in both directions; register_openable absent;
  resolve_channel_manager stub; backpressure drops chunks; buffer cap
  in messages not bytes; ledger decrement on 1/4 teardown paths; demux
  desyncs on oversized chunk; no channel-adoption/collision scheme.
- channel/control + resources/subscribe stubs; busy-wait spin; lost
  EOF sentinel; TOCTOU on max_channels; ~50 lines of abandoned
  deliberation in poll_write; cargo doc 4 warnings; spec docs
  inconsistent post-047.

Includes a 6-unit remediation plan sequenced by dependency, with
acceptance gates. Units 1/2/4 need no spec decisions; Units 3 and 5
each need one written ADR decision first (ADR-047 §4 env-resolution;
§5 channel-id adoption). The overarching acceptance gate is the
end-to-end ChannelClient ↔ ChannelsAdapter test whose absence let
both commits land green.

Two corrections to the original reviews noted in the verification log:
- C-17 (lost EOF sentinel): Sub Review B overstated "the pump does not
  write an EOF chunk" — the pump does write EOF when it receives the
  sentinel; the real bug is narrower (sentinel lost on full buffer →
  pump exits on recv→None without writing EOF).
- Warning count: main review said 5 cargo doc warnings; this pass sees
  4 (2× register_openable, 1× default_policy, 1× env module/macro).

Verification:
- cargo test → 432 passed (no source changed; docs-only commit)
- cargo clippy --all-targets -- -D warnings → clean
- cargo fmt --check → clean
- cargo doc --no-deps → 4 warnings (documented as C-09; not addressed
  here — fixing them requires the register_openable method to exist,
  which is Unit 3 of the remediation plan)
2026-08-12 13:03:36 +00:00