phase 4: connectrpc on the gateway authenticated edge

Replace the native-gRPC server bootstrap with a single
`connectrpc.com/connect` HTTP/h2c listener. Connect-Go natively
serves Connect, gRPC, and gRPC-Web on the same port, so browsers can
now reach the authenticated surface without giving up the gRPC
framing native and desktop clients may use later. The decorator
stack (envelope → session → payload-hash → signature →
freshness/replay → rate-limit → routing/push) is reused unchanged
behind a small Connect → gRPC adapter and a `grpc.ServerStream`
shim around `*connect.ServerStream`.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Ilia Denisov
2026-05-07 11:49:28 +02:00
parent 39b7b2ef29
commit 118f7c17a2
30 changed files with 1009 additions and 772 deletions
+73 -31
View File
@@ -423,46 +423,88 @@ Targeted tests:
- `gateway/authn` cross-module parity tests as listed under
Artifacts.
## Phase 4. ConnectRPC Support in Gateway
## ~~Phase 4. ConnectRPC Support in Gateway~~
Status: pending. Cross-service phase — work happens in `gateway/`,
not `ui/`.
Status: done. Cross-service phase — work happened in `gateway/` and
`integration/`, not `ui/`.
Goal: enable browsers to call the gateway's authenticated gRPC surface
through ConnectRPC, while preserving the existing native gRPC ingress
for desktop and mobile clients.
Goal: enable browsers to call the gateway's authenticated edge surface
through ConnectRPC, without keeping a separate gRPC server bootstrap
alive purely for test clients.
Artifacts:
Decision (taken with the project owner before implementation): the
existing native-gRPC `grpc.NewServer` bootstrap was replaced with a
single `connectrpc.com/connect` HTTP/h2c listener, since Connect-Go
natively serves the Connect, gRPC, and gRPC-Web protocols on the same
port. No production gRPC clients existed to preserve. The package
`gateway/internal/grpcapi` keeps its name for diff-size reasons and
documents the historical labelling in its package doc.
- ConnectRPC handler registered alongside existing gRPC server in
`gateway/internal/...` using `connectrpc.com/connect`
- `gateway/buf.gen.yaml` extended to generate Connect-Go code from
existing `.proto` files
- updated `gateway/README.md` and `gateway/openapi.yaml` reflecting
Connect ingress endpoints
- updated `docs/ARCHITECTURE.md` §15 if the deployment topology changes
- `gateway/internal/.../connect_server_test.go` integration test
exercising a unary Connect call and a server-streaming Connect call
Artifacts (delivered):
Dependencies: Phase 3 (canonical bytes are needed for the integration
fixtures used here).
- `gateway/buf.gen.yaml` extended with `buf.build/connectrpc/go`,
generating `gateway/proto/galaxy/gateway/v1/gatewayv1connect/edge_gateway.connect.go`
- `gateway/internal/grpcapi/server.go` rewritten around `http.Server`
+ `h2c.NewHandler` + `gatewayv1connect.NewEdgeGatewayHandler`
- new `gateway/internal/grpcapi/connect_handler.go` adapting the
existing `gatewayv1.EdgeGatewayServer` decorator stack to the
Connect handler interface, including a `grpc.ServerStreamingServer`
shim around `*connect.ServerStream[GatewayEvent]` and a gRPC
`status.Error` → `*connect.Error` translation helper
- new `gateway/internal/grpcapi/connect_observability.go` Connect
interceptor recording the same metric and structured-log shape the
gRPC interceptors emitted; the rate-limit decorator now reads peer
IP from a context value populated by the interceptor instead of
`peer.FromContext`
- updated `gateway/README.md` (Transport Matrix + "Authenticated Edge
Surface"), `gateway/docs/runtime.md`, `gateway/docs/flows.md`,
`gateway/docs/runbook.md`, and `docs/ARCHITECTURE.md` §15
- migrated tests: `gateway/internal/grpcapi/server_test.go`,
`test_fixtures_test.go`, and every `*_integration_test.go` in that
package now drive a `gatewayv1connect.EdgeGatewayClient` over
HTTP/2 cleartext loopback
- migrated harness: `integration/testenv/grpc_client.go` →
`connect_client.go`. `SignedGatewayClient` keeps the same public
shape (`Execute`, `SubscribeEvents`, `Close`) but speaks Connect
internally; `Is*` helpers now use `connect.CodeOf`
Acceptance criteria:
Dependencies: Phase 3 (canonical bytes are needed for the
fixture-level signing the migrated tests use).
- a curl-based unary Connect call from outside the gateway process
succeeds end-to-end against the authenticated surface;
- server-streaming `SubscribeEvents` works over Connect with at least
one delivered event;
- existing native gRPC clients continue to work unchanged;
- both gRPC and Connect handlers share the same upstream business code
(no duplication beyond the protocol layer).
Acceptance criteria (met):
Targeted tests:
- unary Connect calls from outside the gateway process succeed
end-to-end against the authenticated surface — verified by the
migrated `grpcapi/server_test.go` and `command_routing_integration_test.go`
scenarios driving the Connect client over loopback h2c;
- server-streaming `SubscribeEvents` works over Connect with the
signed `gateway.server_time` bootstrap event delivered first —
verified by `TestSubscribeEventsValidEnvelopeSendsBootstrapEventAndWaitsForCancellation`;
- the unified listener still natively accepts gRPC and gRPC-Web
framing for any future native client (Connect-Go's documented
multi-protocol support);
- the Connect handler shares the same upstream business code as the
unified listener — there is exactly one decorator stack
(`grpcapi.NewServer` → `s.service`).
- Connect unary integration test against a running gateway+backend;
- Connect streaming integration test asserting at least one push event
delivery;
- existing gateway test suite stays green.
Targeted tests (delivered):
- Connect unary integration tests in `gateway/internal/grpcapi/`
exercising the full envelope → signature → freshness/replay →
rate-limit → routing pipeline through the new Connect transport;
- Connect streaming integration tests asserting bootstrap-event
delivery, replay rejection on stream open, and shutdown closure;
- the existing gateway test suite (`go test ./gateway/...`) stays
green.
Decision deviation note: the planned standalone
`gateway/internal/grpcapi/connect_server_test.go` was not added as a
separate file because the migrated `*_test.go` files in the same
package already cover unary happy + streaming bootstrap + protocol-
version reject through the Connect client. A duplicate file would not
add coverage. Future contributors looking for "the Connect tests" can
read any file in `gateway/internal/grpcapi/` — they all use the
Connect client now.
## Phase 5. WASM Build, `WasmCore` Adapter, `GalaxyClient` Skeleton