connectrpc 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +501 -0
  4. data/docs/design.md +504 -0
  5. data/docs/development.md +566 -0
  6. data/docs/grpc-web.md +165 -0
  7. data/docs/grpc.md +75 -0
  8. data/docs/rbs.md +469 -0
  9. data/docs/streaming.md +251 -0
  10. data/exe/protoc-gen-connectrpc-ruby +23 -0
  11. data/lib/connectrpc/bindings/descriptors/status.bin +15 -0
  12. data/lib/connectrpc/bindings/google_protobuf.rb +92 -0
  13. data/lib/connectrpc/cancellation.rb +220 -0
  14. data/lib/connectrpc/client.rb +260 -0
  15. data/lib/connectrpc/compression.rb +120 -0
  16. data/lib/connectrpc/deadline.rb +81 -0
  17. data/lib/connectrpc/error.rb +72 -0
  18. data/lib/connectrpc/generator/names.rb +105 -0
  19. data/lib/connectrpc/generator/rbs_renderer.rb +93 -0
  20. data/lib/connectrpc/generator/renderer.rb +115 -0
  21. data/lib/connectrpc/generator/runtime_names.rb +84 -0
  22. data/lib/connectrpc/generator/schema.rb +218 -0
  23. data/lib/connectrpc/generator/streaming_methods.rb +29 -0
  24. data/lib/connectrpc/generator.rb +42 -0
  25. data/lib/connectrpc/get_query.rb +159 -0
  26. data/lib/connectrpc/grpc.rb +200 -0
  27. data/lib/connectrpc/grpc_client.rb +23 -0
  28. data/lib/connectrpc/grpc_server.rb +158 -0
  29. data/lib/connectrpc/grpc_web.rb +131 -0
  30. data/lib/connectrpc/grpc_web_cors.rb +135 -0
  31. data/lib/connectrpc/grpc_web_io.rb +156 -0
  32. data/lib/connectrpc/grpc_web_server.rb +207 -0
  33. data/lib/connectrpc/instrumentation.rb +216 -0
  34. data/lib/connectrpc/message_io.rb +47 -0
  35. data/lib/connectrpc/metadata.rb +105 -0
  36. data/lib/connectrpc/method.rb +68 -0
  37. data/lib/connectrpc/protocol.rb +189 -0
  38. data/lib/connectrpc/server.rb +398 -0
  39. data/lib/connectrpc/streaming/client.rb +104 -0
  40. data/lib/connectrpc/streaming/client_execution.rb +87 -0
  41. data/lib/connectrpc/streaming/client_scope.rb +105 -0
  42. data/lib/connectrpc/streaming/framing.rb +123 -0
  43. data/lib/connectrpc/streaming/framing_io.rb +61 -0
  44. data/lib/connectrpc/streaming/method.rb +33 -0
  45. data/lib/connectrpc/streaming/server.rb +122 -0
  46. data/lib/connectrpc/streaming/server_execution.rb +88 -0
  47. data/lib/connectrpc/streaming/server_terminal.rb +70 -0
  48. data/lib/connectrpc/streaming/session.rb +143 -0
  49. data/lib/connectrpc/streaming/session_completion.rb +134 -0
  50. data/lib/connectrpc/streaming/session_io.rb +107 -0
  51. data/lib/connectrpc/streaming/session_messages.rb +119 -0
  52. data/lib/connectrpc/streaming/strategy.rb +95 -0
  53. data/lib/connectrpc/streaming/streams.rb +116 -0
  54. data/lib/connectrpc/streaming/terminal.rb +52 -0
  55. data/lib/connectrpc/transports/net_http.rb +246 -0
  56. data/lib/connectrpc/version.rb +5 -0
  57. data/lib/connectrpc.rb +38 -0
  58. data/rbs/google_protobuf_4_36/runtime.rbs +41 -0
  59. data/sig/connectrpc.rbs +772 -0
  60. data/sig/manifest.yaml +2 -0
  61. data/third_party/README.md +16 -0
  62. data/third_party/google/LICENSE +201 -0
  63. data/third_party/google/rpc/status.proto +48 -0
  64. data/third_party/protobuf-LICENSE +32 -0
  65. metadata +106 -0
data/docs/rbs.md ADDED
@@ -0,0 +1,469 @@
1
+ # Public API signatures
2
+
3
+ The [supported 0.x contract](support.md) defines the target compatibility
4
+ boundary and the remaining adoption gates.
5
+
6
+ Every package ships its own gem-discovered signatures and dependency manifest.
7
+ Core also generates mixed unary/streaming service signatures. RBS
8
+ and Steep are development tools; neither is required by the runtime. Default
9
+ Connect generation produces exactly the same Ruby files as before. Only the
10
+ plugin parameter `rbs=true` adds `<stem>_connect.rbs` for each service-bearing
11
+ input. Unknown, duplicate, invalid and trailing parameters fail without files.
12
+
13
+ ## Generate messages and services
14
+
15
+ Use protoc 36.2, google-protobuf 4.36.2, RBS 4.2.0 and Steep 2.1.0 for the
16
+ tested upstream message-signature path. Create `generated/`, then run the
17
+ installed plugin alongside the official Ruby and RBS generators:
18
+
19
+ ```sh
20
+ protoc -I proto --ruby_out=generated --rbs_out=generated \
21
+ --connectrpc-ruby_out=rbs=true:generated example.proto
22
+ ```
23
+
24
+ Use schema-relative input names with `-I`. Also generate
25
+ `--rbs_out` for imported message schemas. If your schema imports Timestamp or
26
+ Empty, include `google/protobuf/timestamp.proto` and
27
+ `google/protobuf/empty.proto` as additional compiler inputs. Their official
28
+ Ruby implementations may come from google-protobuf; their signatures must enter
29
+ the RBS environment too. The compiler's standard include directory supplies
30
+ these schemas. Other imports need their normal `-I` directories.
31
+
32
+ Buf 1.75.0 supports the official RBS builtin. An equivalent v2 template is:
33
+
34
+ ```yaml
35
+ version: v2
36
+ plugins:
37
+ - protoc_builtin: ruby
38
+ out: generated
39
+ - protoc_builtin: rbs
40
+ out: generated
41
+ - local: protoc-gen-connectrpc-ruby
42
+ out: generated
43
+ opt: rbs=true
44
+ ```
45
+
46
+ Buf generates local inputs by default. Generate imported/WKT signatures with
47
+ protoc as above or include their schema closure in your generation inputs.
48
+ Connect emits service signatures only. It never generates or rewrites message
49
+ fields, constructors or official `_pb.rbs` output.
50
+
51
+ ## Check an application
52
+
53
+ Select the small protobuf bridge explicitly in your Steepfile. It lives outside
54
+ the gem's automatically discovered `sig/`, so `library "connectrpc"` introduces
55
+ no Google declarations. With the gem installed, use a path relative to your
56
+ Steepfile for the bridge (Steep signature patterns use project-relative paths):
57
+
58
+ ```ruby
59
+ require "pathname"
60
+ bridge = Pathname(Gem::Specification.find_by_name("connectrpc").full_gem_path)
61
+ .join("rbs/google_protobuf_4_36")
62
+ .relative_path_from(Pathname(__dir__)).to_s
63
+
64
+ target :app do
65
+ library "connectrpc"
66
+ signature "generated", "sig", bridge
67
+ check "app"
68
+ configure_code_diagnostics(Steep::Diagnostic::Ruby.strict)
69
+ end
70
+ ```
71
+
72
+ Run `bundle exec steep check` with your application's development bundle.
73
+ Strict diagnostics are required: Steep's defaults classify some incorrect
74
+ request constraints and assignments as hints, which do not fail a normal check.
75
+ The repository's `typecheck/consumers/` has executable positive and negative
76
+ examples, including concrete callable objects and inline lambdas. Each negative
77
+ file must produce exactly its expected diagnostic at its own source path.
78
+
79
+ The bridge is deliberately limited to the shapes emitted by protoc 36.2:
80
+ `AbstractMessage`, the empty `_MessageClass[T]` compatibility marker,
81
+ `RepeatedField` indexing/writing/appending, `Map` indexing/writing, enum
82
+ lookup/resolve, and descriptor-pool lookup/message-class/enum-module access.
83
+ Upstream literal lookup overloads retain specific message/enum tokens; arbitrary
84
+ lookup results stay opaque because the runtime can also return other descriptors.
85
+ It promises no collection iteration, Enumerable or Enumerator behavior. The
86
+ marker does not promise codec methods. The binding signature erases custom
87
+ runtime tokens at its encode/decode boundary instead of inventing a generic
88
+ decode association.
89
+
90
+ Official field signatures are best effort, as their generated header states.
91
+ They do not validate numeric ranges, serialization, every protobuf runtime API,
92
+ or arbitrary custom bindings. Do not combine this bridge with the old
93
+ collection google-protobuf 3.22 signatures, whose collection shapes differ.
94
+ Applications supplying protobuf runtime signatures must omit the bridge.
95
+
96
+ ## Keep application-owned message signatures
97
+
98
+ Existing application message signatures work without the bridge. Alternatively,
99
+ minimal nominal declarations check RPC identities, and need no custom
100
+ `initialize` declaration:
101
+
102
+ ```rbs
103
+ module Example
104
+ class Request
105
+ end
106
+ class Reply
107
+ end
108
+ end
109
+ ```
110
+
111
+ Declare every referenced message class, including imported/nested types, in
112
+ your application's `sig/`. A separate field extension remains application-owned:
113
+
114
+ ```rbs
115
+ class Example::Request
116
+ attr_accessor text: String
117
+ end
118
+ ```
119
+
120
+ Use these declarations instead of duplicate official message signatures. Nominal
121
+ identities alone do not check field or constructor arguments. The repository's
122
+ `typecheck/nominal/` checks this alternative with a separate field extension.
123
+
124
+ ## Associations and typed construction
125
+
126
+ Generated method constants describe `Method[Request, Reply]`; generated client
127
+ methods accept Request and return `Response[Reply]`. Both Method and Response
128
+ are invariant. Handlers receive `(Request, Context)` and return the reply
129
+ message, rather than a Response envelope. Their argument signatures accept a
130
+ concrete proc or a signed callable object. The proc arm lets Steep infer inline
131
+ lambda request/context types. Returned handler-map values retain `call`;
132
+ heterogeneous maps do not encode a particular key's method association. The
133
+ core Server registration map erases those associations only after generated
134
+ `.handlers` has checked each callable.
135
+
136
+ Method's `request_type` and `response_type` are arbitrary binding tokens typed
137
+ as Object. A manual `Method.new` cannot infer instance types from those tokens.
138
+ Supply an explicit application assertion after verifying your binding contract:
139
+
140
+ ```ruby
141
+ # @type var method: ConnectRPC::Method[Example::Request, Example::Reply]
142
+ method = ConnectRPC::Method.new(service: "example.API", name: "Call",
143
+ request_type: Example::Request,
144
+ response_type: Example::Reply)
145
+ ```
146
+
147
+ This annotation asserts the association; it does not prove that a custom
148
+ binding honors it. Generated constants establish it from schema information.
149
+
150
+ Response, TransportRequest and TransportResponse remain mutable Ruby Structs.
151
+ Fully initialized `new` and class `[]` calls return precise records. Response's
152
+ message type is inferred, and Client still returns `Response[Reply]` with
153
+ non-null Metadata and trailers. Omitted or nil fields produce a partial-record
154
+ interface with nullable readers; a supplied Response message retains its own
155
+ type. Keyword arguments and positional keyword Hashes are both accepted.
156
+
157
+ ```ruby
158
+ complete = ConnectRPC::Response.new(message: reply,
159
+ metadata: ConnectRPC::Metadata.new, trailers: ConnectRPC::Metadata.new)
160
+ complete[:metadata].to_h
161
+ partial = ConnectRPC::Response[message: reply]
162
+ partial.metadata&.to_h
163
+ partial[:metadata] = nil
164
+ # complete[:metadata] = nil # Rejected: precise records retain their field types.
165
+ ```
166
+
167
+ Field readers, writers and literal Symbol/String/integer indexing preserve each
168
+ field's type, including negative indices. Common read-only Struct operations
169
+ (`to_h`, `members`, `size`, `length`, `to_a`, `values`, `each`, `each_pair`,
170
+ `values_at`, `deconstruct`, `deconstruct_keys`) are declared. Unrestricted
171
+ reflection, such as `send` or `instance_variable_set` that invalidates precise
172
+ fields, is an application escape boundary. Arbitrary computed member names,
173
+ recursive `dig`, block-form `to_h` and custom Struct subclass behavior are outside
174
+ this declared surface. These signatures do not change Ruby's runtime mutation.
175
+
176
+ ## Unary GET options
177
+
178
+ `Method#idempotency` is the immutable `:unknown | :no_side_effects | :idempotent`
179
+ enum. Generated constants preserve standard protobuf method options. Core
180
+ Client accepts `use_get: bool` and `get_url_limit: Integer`; Server accepts
181
+ `get_query_limit: Integer?` and `call(query: String)`. Runtime constructors still
182
+ validate booleans and positive integer bounds. Rails `connect_limits` also
183
+ accepts `get_query_limit`. Generated wrappers keep their existing call options.
184
+ The strict implementation target includes the private GET parser and builder;
185
+ manual/generated positive callers and exact negative diagnostics cover these
186
+ options alongside installed package discovery.
187
+
188
+ ## Package APIs and extension contracts
189
+
190
+ Use the installed package names in a Steepfile:
191
+
192
+ ```ruby
193
+ target :app do
194
+ library "connectrpc"
195
+ library "connectrpc-rack" # Only the integrations in your application bundle.
196
+ library "connectrpc-testing"
197
+ library "connectrpc-on-rails"
198
+ library "connectrpc-async"
199
+ library "connectrpc-falcon"
200
+ library "connectrpc-otel"
201
+ check "app"
202
+ configure_code_diagnostics(Steep::Diagnostic::Ruby.strict)
203
+ end
204
+ ```
205
+
206
+ Each optional manifest resolves only its dependency closure. Core declares no
207
+ Google runtime or optional hosting/tracing namespace and has no runtime tool
208
+ dependencies. Its GoogleProtobuf binding class signature is available without
209
+ the explicit message-runtime bridge; the binding itself still requires the
210
+ optional runtime require.
211
+
212
+ | Owner | Supported API and extension SPI |
213
+ | --- | --- |
214
+ | Core | Method, non-generic Service, Client/Server options and calls, records, Metadata, CancellationSource/Token/Registration, Deadline/Context, Error/ErrorDetail, explicit NetHTTP and Google binding entry points |
215
+ | Core shared, unary and observation SPI | `_Cancellation`, `_Binding`, `_Transport`, `_Input`, `_Handler`, `_Invoker`, callable clocks/error handlers, `_Subscriber`, four separate optional observer callback capabilities, host lifecycle Handle |
216
+ | Core streaming and gRPC/Web SPI | `_StreamingTransport`, `_StreamExchange`, streaming input/output, `_GRPCTransport`, `_GRPCStreamExchange`, `_GRPCStatusCodec`, native server publication/output interfaces including `_GRPCWebOutput`; see the protocol-specific sections below |
217
+ | Rack | Middleware constructor/call and shared `_App`, `_Body`, `_CallableBody`, `_Stream`, env and response contracts |
218
+ | Testing | In-process Transport constructor/call, reusable as the core transport |
219
+ | Rails | Controller inclusion and `connect`; documented private overrides for service, binding, handlers, limits, instrumentation and Web protocol/status/CORS; nullable request/context/method readers |
220
+ | Async | Application/native HTTP capabilities, Host, Transport, HTTP1Transport, ServerSession/accepted handles, TLSConfiguration, complete static/provider record shapes, status/error callbacks, rotation/drain/identity |
221
+ | Falcon | Endpoint's supported operations, Environment overrides and Server/native application/TLS lifecycle |
222
+ | OTel | Subscriber configuration and narrow tracer-provider/tracer/span/propagator capabilities; SDK and context implementation remain application owned |
223
+
224
+ `Async::Host#close`, `Async::ServerSession#close` and `Falcon::Server#close`
225
+ return `bool?`. Nil means a nonjoining owned request or coordinator reentry is
226
+ pending. External callers join actual cleanup and receive the final shared
227
+ boolean; once closed, all calls receive that boolean. Owned positive-grace
228
+ initiation preserves ordinary asynchronous drain and admitted sibling progress.
229
+ Repeated requests retain the first closer and expiry. Pending nil does not
230
+ decide an outcome or publish completion. The API callers narrow nil separately
231
+ from completed booleans, and the negative inventory rejects treating a pending
232
+ server close as an unconditional bool. Client transport close signatures remain
233
+ `bool`.
234
+
235
+ Constructors include their actual options and return types. Type checks reject
236
+ wrong field, callback and configuration types. Runtime checks still enforce
237
+ names, byte limits, finite numeric ranges, required TLS key presence, TLS/wire
238
+ validity, reactor affinity, admission and lifecycle ordering. RBS records with
239
+ nullable fields do not prove those keys were explicitly present. There is no
240
+ claim of fully type-safe RPC execution.
241
+
242
+ Custom hosts must pass the actual HTTP method to
243
+ `Server#start_call(path:, headers:, http_method: "POST")` before dispatch. Omitting
244
+ the keyword retains POST classification for existing direct callers. Core,
245
+ Rack and native hosts forward it. Server subclasses overriding `start_call`
246
+ must accept and forward `http_method:` (or compatible keyword arguments);
247
+ external overrides with narrower keyword signatures require an update.
248
+ The method fixes `Call.protocol` before subscribers start while preserving the
249
+ raw immutable header carrier. Native packages share core's private selection;
250
+ it is not an additional public protocol API.
251
+
252
+ Instrumentation subscribers return an opaque object or nil. They may implement
253
+ any subset of attach, detach, processing_finished and finished. The separate
254
+ `_ObserverAttach`, `_ObserverDetach`, `_ObserverProcessingFinished` and
255
+ `_ObserverFinished` interfaces let applications deliberately check a capability;
256
+ returning an arbitrary object from `start` does not automatically validate all
257
+ its optional callbacks. Core resolves methods once into private typed callback
258
+ records at one reflection boundary. Call, Outcome and Completion are shallowly
259
+ immutable Data values. In particular, the client Call's carrier remains mutable
260
+ for validated propagation injection. Handle exposes `activate`, `finish`,
261
+ `record_failure`, `host_rejected!` and `host_rejected?` for hosting. Construction,
262
+ processing bookkeeping and Instrumentation.start/safely/carrier are internal.
263
+
264
+ Optional class signatures describe ConnectRPC-owned supported operations. They
265
+ do not promise full upstream framework inheritance or describe all Rack/Rails,
266
+ Async/Falcon or OpenTelemetry APIs. No broad external namespace declarations are
267
+ shipped through normal discovery. Falcon checks fallback status, scalar/repeated
268
+ headers and enumerable/callable response bodies using small local structural
269
+ capabilities. These also accept `ConnectRPC::Rack::_App`; the Falcon closure does
270
+ not require the `connectrpc-rack` gem. Async owns shared native HTTP/TLS
271
+ capabilities reused by Falcon.
272
+
273
+ ## Source inventory and implementation checks
274
+
275
+ The reviewed `typecheck/surface.json` records definitions, attributes, visibility,
276
+ constructor parameters/defaults and Struct/Data members across all maintained
277
+ library source. Each entry is explicitly supported, extension SPI or internal.
278
+ `rbs:inventory` checks the syntax tree against that inventory and requires shipped
279
+ declarations for supported members, including record readers/writers. A new
280
+ source method, changed option/visibility/member or omitted signature fails.
281
+ Review source and docs before updating an inventory entry; Ruby-public helpers
282
+ are not automatically compatibility promises. This is a practical source drift
283
+ check, not semantic proof of arbitrary Ruby metaprogramming.
284
+
285
+ Protocol, Compression, MessageIO, Method.validate_name!, private callback records,
286
+ core processing helpers, socket internals, framework glue internals and generator
287
+ implementation helpers stay outside the supported API. Generator invocation and
288
+ schema-generated service APIs retain their existing contract and parity checks.
289
+
290
+ `typecheck/Steepfile` checks actual core source and private state under
291
+ `Steep::Diagnostic::Ruby.strict`, including streaming and gRPC/Web.
292
+ [`typecheck/source.rb`](../typecheck/source.rb) defines the exact source set.
293
+ `rbs:source` validates the resolved source list and Ruby syntax before checking.
294
+ Its implementation-only declarations live outside every gem's `sig/` directory.
295
+ Erasure is limited to custom bindings/heterogeneous messages, external JSON and
296
+ optional observer discovery. The internal Numeric arithmetic declaration fills
297
+ a missing upstream base signature; custom numeric arithmetic remains a runtime
298
+ requirement. Narrow assertions record established phase, slice and format facts.
299
+
300
+ NetHTTP socket/watchdog implementation, Google codec internals, generator
301
+ internals and optional backend/framework implementation checking are later stages.
302
+ Their supported public signatures and consumer checks are included here.
303
+
304
+ Callable-body stream capabilities include the standard Rack read, write, `<<`,
305
+ flush, close, half-close and closed-state operations. Native HTTP header
306
+ capabilities return an Enumerator of name/value tuples. Struct `each_pair`
307
+ yields one name/value tuple, which a block may explicitly destructure.
308
+
309
+ Custom OpenTelemetry providers must supply the finite span-context capabilities
310
+ used by the default W3C propagator: valid state, raw trace/span ids, sampled
311
+ flags and serialized tracestate. These declarations introduce no SDK or upstream
312
+ namespace; static types do not prove valid id lengths or sampling policy.
313
+
314
+ ### Opt-in framework compatibility
315
+
316
+ Rails inheritance and Falcon's configuration DSL need declarations for upstream
317
+ classes that currently ship no RBS. Each integration packages a finite bridge
318
+ outside its automatically loaded `sig/`:
319
+
320
+ | Package | Explicit signature directory | Tested upstream versions |
321
+ | --- | --- | --- |
322
+ | `connectrpc-on-rails` | `rbs/action_controller_8` | actionpack 8.0.5.1 and 8.1.4 |
323
+ | `connectrpc-falcon` | `rbs/async_service_0_25` | Falcon 0.57.0, async-service 0.25.0 |
324
+
325
+ Add only the selected bridge paths to an application's Steep target, using the
326
+ same gem-relative `Pathname` pattern as the Google bridge above. Omit these
327
+ bridges when the application already supplies declarations for those upstream
328
+ classes. Normal library discovery introduces neither `ActionController` nor
329
+ `Async::Service` declarations.
330
+
331
+ The Rails bridge covers nominal `ActionController::API` and `Base` inheritance
332
+ and `before_action` with named Symbol callbacks. It does not type the rest of
333
+ Rails or its callback option/block forms. The Falcon bridge covers
334
+ `Loader#service(name)` with a builder block, `include`, String `url`, Integer
335
+ `count`, block `prepare!`, and value/block forms of `connectrpc_server`,
336
+ `rack_app`, `tls`, `tls_provider` and `server_options`. Deferred option blocks
337
+ have the ConnectRPC Environment capabilities. No general `method_missing`
338
+ acceptance or complete upstream inheritance is declared. The repository checks
339
+ actual Rails subclasses and a real Falcon-loaded service configuration with
340
+ these declarations and runtime counterparts. The Falcon fixture accepts the real loader as a typed method argument, letting
341
+ Steep infer the builder block's self type without a file-level self override.
342
+
343
+ ## Repository checks
344
+
345
+ ```sh
346
+ mise run rbs:setup
347
+ mise run rbs:check
348
+ mise run rbs:source # Focused strict implementation leaf.
349
+ mise run rbs:api # Focused supported API callers.
350
+ mise run rbs:inventory # Source/signature drift.
351
+ mise run rbs:proof # Independent capture red/green probes.
352
+ mise run rbs:package
353
+ MISE_RUBY_VERSION=4.0.9 mise run rbs:check
354
+ MISE_RUBY_VERSION=4.0.9 mise run rbs:package
355
+ ```
356
+
357
+ The independent checksummed `typecheck/Gemfile.lock` pins only development tools.
358
+ Bundles, Bundler state, compiler caches and local evidence live under
359
+ `vendor/rbs/`. The package check installs into a fresh GEM_HOME and exercises
360
+ normal installed signature/executable discovery outside the checkout. It clears
361
+ checkout Ruby and Bundler load paths. Both Ruby lanes run these feature gates in
362
+ CI. `rbs:check` includes strict source, inventory and all API consumers as well as
363
+ the generated positive/negative and nominal/bridge cases. The generated and
364
+ API negative inventories each use one fresh Steep invocation with a single
365
+ worker. Files remain separate, with independent inference contexts
366
+ and unchanged strict signature inputs. The harness verifies the fixture inventory,
367
+ normal exit 1, complete checker progress/summary and exactly one expected error
368
+ per known path. Missing, extra, duplicate, malformed and wrong-severity records
369
+ fail. Parsing uses the pinned formatter's raw record boundaries; escaped message
370
+ newlines cannot introduce diagnostics. Controlled fixture paths cannot contain
371
+ ambiguous annotation properties. Positives, strict implementation, RBS validation
372
+ and Falcon-only checks remain separate.
373
+ `rbs:package` uses the existing frozen optional bundle setups for offline runtime
374
+ archives, builds every owned signature package and checks each library through
375
+ normal installed discovery. It checks composed environments in both load orders,
376
+ real Rails subclass inclusion, Rack/Testing calls, native session/TLS operations,
377
+ Falcon endpoint integration and API-only OTel without loading an SDK. The original
378
+ package guards and generated installed callers remain. Complete negative batch
379
+ logs are retained under `vendor/rbs/`, including nested installed API logs on
380
+ failure. Core, optional and tool bundles stay independent; gem dependencies and
381
+ pins remain unchanged. CI caches dependency installations, always reruns frozen
382
+ setup and every check, and never caches the fresh external consumer. See
383
+ [CI cache ownership and invalidation](ci.md#optional-rbs-lanes).
384
+
385
+ `rbs:proof` captures independent source inodes without Git metadata, proves wrong
386
+ return/data/callback diagnostics and source-addition/signature-omission drift,
387
+ restores every captured byte, and reruns green. It never mutates delivery source.
388
+ The captured executable alone uses 0755 for existing archive guards; source modes
389
+ remain unchanged. Logs and capture bindings live under ignored `vendor/rbs/`.
390
+
391
+ ## Cancellation types
392
+
393
+ Client and generated unary methods accept `cancellation: CancellationToken?`.
394
+ Pass `CancellationSource.new.token`; the source itself is rejected. Tokens expose
395
+ only observation and listener registration. `CancellationRegistration#unregister`
396
+ returns bool. Token reasons are `:canceled | nil`; Context's combined
397
+ `cancellation_reason` also admits `:deadline_exceeded`.
398
+
399
+ `TransportRequest` appends `cancellation: _Cancellation?` after `lifecycle`;
400
+ negative indices shift accordingly (`-1` is cancellation, `-2` is lifecycle).
401
+ Complete and partial Structs retain their precise field contracts and aggregate
402
+ unions. `_Cancellation` is the read-only transport/host extension SPI, with
403
+ `cancelled?`, `reason`, `check!` and `on_cancel`. Server/Context accept that SPI
404
+ and own separate processing arbitration. Deadline remains an actual Deadline or
405
+ nil. Runtime cancellation, reactor and blocking-I/O limits still apply.
406
+
407
+ ## Streaming types
408
+
409
+ The invariant streaming declarations are siblings of unary Method. Generated
410
+ mixed-service handlers carry concrete request/reply and StreamingContext types;
411
+ readers, writers and duplex endpoints preserve those associations. Block-only
412
+ `each` and required operation blocks are checked. StreamRequest/StreamHead fields,
413
+ partial Structs and literal indices use the same precise record policy as unary.
414
+ The records are named `StreamRequest` and `StreamHead` in Ruby/RBS.
415
+
416
+ The typed `_StreamingTransport` explicitly declares finite cardinalities and
417
+ provides a writable exchange before waiting for headers. `_StreamExchange`
418
+ includes bounded response-head/read/finish waits. Existing unary `_Transport`
419
+ consumers need no new method. New core streaming files participate in the strict
420
+ implementation target, with private declarations outside gem archives. Installed
421
+ checks generate real mixed services and run native Async/Falcon streaming over
422
+ localhost with all seven normally installed packages, both composition orders,
423
+ codecs and codings. These remain separate from independent Go/official evidence.
424
+ Custom server hosts may pass `capabilities: Array[:server_stream | :client_stream |
425
+ :bidi_stream]?` to `Server#call_stream`. Nil preserves the legacy `http2:` behavior;
426
+ a non-nil list must be nonempty, unique and cannot accompany `http2: true`.
427
+ Runtime validation enforces those array constraints. Strict source and installed
428
+ positive/negative consumers cover the capability SPI. Private native routing
429
+ and retirement helpers are internal implementation, not supported host APIs.
430
+ See the [streaming guide](streaming.md) for state, ownership and timing policies.
431
+
432
+ ## Native gRPC types
433
+
434
+ Client accepts `protocol: :connect | :grpc`; Server additionally accepts `:auto`.
435
+ `GRPCHead` is deeply immutable and separate from the existing Struct records.
436
+ The independent `_GRPCTransport` advertises all four finite RPC shapes and yields
437
+ `_GRPCStreamExchange`; it needs no unary `call`. Existing `_Transport`,
438
+ `_StreamingTransport`, `_StreamExchange` and `_Binding` requirements are unchanged.
439
+
440
+ A separate `_GRPCStatusCodec` supports composed custom binding tokens. Its
441
+ `decode_status(String, code: Integer)` compares the raw numeric code before
442
+ normalizing unknown codes. Native `Server#call_grpc` requires explicit HTTP/2,
443
+ `_GRPCOutput.finish(headers:)` and a `_PublishGRPCHead` callback. See the
444
+ [gRPC guide](grpc.md) for publication, EOF and transport completion semantics.
445
+
446
+ ## gRPC-Web server types
447
+
448
+ Server protocol selection accepts `:grpc_web` and explicit distinct protocol
449
+ arrays; `:auto` remains Connect plus native gRPC. Client protocol types remain
450
+ `:connect | :grpc`. `_GRPCWebOutput` separates prepared writes from staged
451
+ `finish(terminal:)`; `call_grpc_web` grants only unary by default.
452
+ `GRPCWebCORS` is an immutable public configuration value, and `cors_response` is
453
+ the pure host evaluation/decorating SPI. Rails declares its three private Web
454
+ hooks and two status limits. Call.protocol includes `:grpc_web` before observer
455
+ start. All new core files enter the strict source set and internal signatures;
456
+ API callers include exact client-mode, output, capability and CORS negatives.
457
+ The installed consumer runs manual encoded Testing requests and real mixed
458
+ Web/Connect H2 calls in both package load orders, without loading an OTel SDK.
459
+
460
+ ## HTTP/1 client types
461
+
462
+ The Async package owns the independent `HTTP1Transport` constructor, unary
463
+ transport and existing SS/CS exchange signatures. Its finite capabilities omit
464
+ bidi and its public surface has no gRPC entry point. Strict callers cover valid
465
+ constructor/exchange use and reject invalid option types, missing blocks and
466
+ gRPC methods. The source inventory classifies lease, parser and exchange
467
+ implementation helpers as internal. Installed generated callers use the normal
468
+ Async package with Falcon H1 for safe GET and SS/CS in both codecs/codings,
469
+ without adding another package or changing the existing streaming SPI.