Commit Graph
15 Commits
Author SHA1 Message Date
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.
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