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
@@ -0,0 +1,566 @@
1
+ # Development
2
+
3
+ ## Setup
4
+
5
+ Use [Mise](https://mise.jdx.dev/) >= 2026.9.14. CI pins 2026.10.19.
6
+ Install the [Ruby source-build prerequisites](https://github.com/rbenv/ruby-build/wiki#suggested-build-environment)
7
+ for your OS first; setup does not change system packages or global tool config.
8
+ Network access is required on first setup.
9
+
10
+ ```sh
11
+ mise run setup
12
+ mise exec -- ruby --version
13
+ mise exec -- bundle --version
14
+ ```
15
+
16
+ `mise.toml` pins CRuby 3.4.12 and 4.0.9, actionlint, and zizmor. Both Rubies
17
+ compile from upstream source. Local setup installs both, with 3.4.12 first on
18
+ PATH. CI uses official prebuilt runtimes instead; see
19
+ [CI](https://github.com/jimeh/connectrpc-ruby/blob/main/docs/ci.md). Run a
20
+ targeted local check on the other version with:
21
+
22
+ ```sh
23
+ MISE_RUBY_VERSION=4.0.9 mise run test
24
+ ```
25
+
26
+ The source-build installer is pinned to [ruby-build v20261023](https://github.com/rbenv/ruby-build/releases/tag/v20261023)
27
+ by commit in `ruby.ruby_build_repo`. Review its release age and commit alongside
28
+ Ruby updates. Compilation uses the host's compiler and system libraries, so
29
+ these locks do not promise bit-identical Ruby binaries across machines.
30
+
31
+ Setup installs only the declared project tools, then Bundler 4.0.23 into the
32
+ active Ruby, then the frozen `Gemfile.lock` into ignored `vendor/bundle/`.
33
+ Root Bundler user state is scoped to `vendor/bundler/`; optional bundles set
34
+ their own user directories.
35
+ Re-running setup is safe. Bundler config files are ignored so user-level
36
+ settings cannot silently change the bundle location or frozen behavior.
37
+ `mise.lock` currently locks Linux x64 downloads and checksums, including both
38
+ Ruby source archives. Add and review locks for other development platforms
39
+ with `mise lock --platform <platform>` before installing there.
40
+
41
+ ## Commands and evidence
42
+
43
+ `mise tasks` is the command index. Ruby operations use Rake where useful;
44
+ Mise owns setup ordering and verification composition.
45
+
46
+ | Command | Purpose |
47
+ | --- | --- |
48
+ | `mise run setup` | Install locked tools, pinned Bundler, frozen dependencies. |
49
+ | `mise run format` | Apply RuboCop Layout fixes. |
50
+ | `mise run format:check` | Check Layout without writing. |
51
+ | `mise run lint` | Run remaining RuboCop cops. |
52
+ | `mise run syntax` | Parse maintained library, specs, scripts, gemspec, Gemfile, Rakefile. |
53
+ | `mise run test -- spec/connectrpc_spec.rb` | Run focused RSpec examples; accepts `file:line` and RSpec options. |
54
+ | `mise run test` | Run all specs and generate coverage. |
55
+ | `mise run lint:workflows` | Run actionlint and offline zizmor. |
56
+ | `mise run check:static` | Run format, lint, and workflow checks once. |
57
+ | `mise run check` | Run static checks and Ruby syntax checks. |
58
+ | `mise run build` | Build `pkg/connectrpc-0.1.0.gem`. |
59
+ | `mise run check:package` | Build, install, and require the gem in isolation. |
60
+ | `mise run setup:generation` | Install task-local protoc 36.2 and development-only Buf 1.75.0. |
61
+ | `mise run generation:build` | Regenerate checked-in official messages and service glue using protoc; verify actual Buf glue parity/runtime. |
62
+ | `mise run generation:check` | Regenerate into temp trees, compare fixture bytes/glue parity, exercise both protoc and Buf output. |
63
+ | `mise run example:generated` | Compile the greeting proto and run its generated localhost client/handlers in both codecs. |
64
+ | `mise run example` | Run a typed ephemeral localhost Rack/NetHTTP example in both codecs. |
65
+ | `mise run example:server -- NAME [--mtls]` | Serve a [runnable example](../examples/README.md) server until Ctrl-C; `PORT` overrides 8080. `--mtls` (Async, Falcon) requires client certificates. |
66
+ | `mise run example:client -- NAME [RPC [ARGS...]]` | Call the running example server; `--list` prints servers, clients, RPCs and options. |
67
+ | `mise run example:verify` | Run every supported example pairing and representative refusals on ephemeral ports. |
68
+ | `mise run fixture:build` | Rebuild the shared descriptor fixture from its explicit Ruby schema builder. |
69
+ | `mise run fixture:check` | Compare the checked-in descriptor bytes to the builder. |
70
+ | `mise run setup:interop` | Install task-local Go 1.27.3 and download checksum-locked modules. |
71
+ | `mise run test:interop` | Run the Go-to-Go reference baseline and both cross-language directions/codecs, 18 original identity cases, the generated path, then 40 separate gzip cases. |
72
+ | `mise run setup:conformance` | Install task-local official runner 1.0.6 and protoc 36.2 with locked checksums. |
73
+ | `mise run conformance:build` | Generate descriptors from unchanged official schemas using protoc. |
74
+ | `mise run conformance:check` | Verify schema hashes and byte-for-byte descriptor regeneration. |
75
+ | `mise run test:conformance` | Run the declared identity and gzip client/server profiles with strict accounting; see the [conformance guide](../conformance/README.md). |
76
+ | `mise run verify` | Run core checks, specs, packages, generation, Go interop, official conformance, the selected Rails lane and the independent HTTP/2 and Falcon backends. |
77
+
78
+ Every entry point installs missing prerequisites through the same task graph.
79
+ During Ruby iteration, use focused tests and relevant static checks. Run
80
+ `check` for fast static feedback and `verify` for package/tooling changes and
81
+ final implementation handoff. Run `mise run verify` and then `mise run rack:verify`
82
+ for final handoff; moved middleware specs have their own canonical gate.
83
+ [CI](https://github.com/jimeh/connectrpc-ruby/blob/main/docs/ci.md) splits the same
84
+ canonical component tasks into nine core, three Rails, four optional HTTP/2,
85
+ two optional Rack, two optional testing, two optional RBS and four optional Falcon
86
+ checks, with syntax, specs, package isolation,
87
+ interop and official conformance on both Rubies. Hosted clean-environment and
88
+ matrix results remain CI evidence; a local pass cannot establish them.
89
+
90
+ Markdown is maintained manually: check relative links, commands, and fenced
91
+ examples when changing prose. There is no automatic Markdown formatter or
92
+ Node toolchain. Mise parses TOML, while actionlint and zizmor check workflows.
93
+ Docs-only edits do not require a test rerun unless an example changes behavior;
94
+ packaged-doc changes can be checked with `check:package`.
95
+
96
+ ### Official conformance
97
+
98
+ [The conformance harness](../conformance/README.md) documents exact pins,
99
+ provenance, supported axes, lifecycle, named selectors and result artifacts.
100
+ `test:conformance` accepts an optional `client` or `server` direction and then
101
+ one exact case name from `conformance/cases.json` for troubleshooting. CI runs
102
+ every configured case in both directions on both pinned Rubies; `verify` runs
103
+ both directions on the active Ruby.
104
+ The 18-case independent Go identity fixture and generated baseline stay separate.
105
+ Focused tasks are `test:interop:identity`, `test:interop:gzip`,
106
+ `test:conformance:identity` and `test:conformance:gzip`. Exact conformance selectors
107
+ automatically choose their inventory; explicit profile tasks keep runs bounded.
108
+
109
+ Identity requires 96 actual client and 90 server passes, including two exact
110
+ GET names per direction. The disjoint gzip profile requires 88 client and 82
111
+ server passes, for 184/172 in total. There are no expected-failure manifests or
112
+ waivers. All four caller-cancellation cases use the original 5 ms timer armed
113
+ before the blocking call, the original 200 ms response request, and no deadline.
114
+ Their causal ledger distinguishes positively observed cancellation before
115
+ transport, during setup and during exchange using closed call/transport scope
116
+ counts, source transition and the actual library canceled/nil-cause result.
117
+ Missing dispatch alone cannot establish an early cancellation. A separately
118
+ proved `response_observed` branch allows explicit cancellation before Client's
119
+ terminal commit after a complete transport response, without claiming Client
120
+ validated/decoded it or interrupted its socket. Completed writes
121
+ retain actual byte/body/name/no-timeout checks; interrupted writes leave byte
122
+ delivery unknown. Dedicated synchronized real-wire tests prove request progress,
123
+ interruption/retirement and cleanup separately from the official timer scenario.
124
+ Lifecycle close-call telemetry leaves interruption causality unknown because
125
+ NetHTTP can independently close the socket; it still requires observed closure
126
+ and retirement for attached sockets.
127
+ Never gate/restart the official timer after wire progress.
128
+ Actual wire coding, byte counts, lengths and digests remain independently
129
+ checked. Ordinary reference error responses may use identity for gzip requests.
130
+ Timeouts, duplicate metadata and remote canceled errors must also pass normally.
131
+ Raw runner logs, JSON telemetry and strict JSON summaries are preserved under
132
+ ignored `vendor/conformance/results/`. An accounting mismatch fails even if
133
+ the official runner exits successfully. This evidence is distinct from full
134
+ conformance or unsupported configuration-space totals.
135
+
136
+ ### Coverage
137
+
138
+ SimpleCov 1.3.2 starts in `spec/spec_helper.rb` before loading the library.
139
+ All `lib/**/*.rb` files, including unloaded files, are in scope; specs and
140
+ tooling are outside that scope. The test command prints line and branch
141
+ summaries and writes an ignored local HTML report to `coverage/index.html`.
142
+ Reports describe the current run, without merging prior focused runs. There is
143
+ no coverage threshold, upload, external service, or artifact job. A skeleton
144
+ with no branches may report zero total branches; its high line percentage
145
+ proves no protocol behavior. Coverage is regression evidence, not interoperability or production-readiness
146
+ proof. The separate Go peer tests establish the advertised unary subset.
147
+
148
+ ### Package isolation
149
+
150
+ Canonical builders share `scripts/package_build.rb`. It loads each gem's exact
151
+ manifest and metadata in its owning source directory, then builds from a private
152
+ temporary copy with executable files set to `0755` and ordinary files to `0644`.
153
+ Checkout permissions and bytes remain unchanged, and each package lane builds
154
+ its own archives in its existing output directory. `spec/package_build_spec.rb`
155
+ checks real archive headers, complete file bytes, manifests and metadata under
156
+ umasks `002` and `022` using group-writable fixture inputs.
157
+
158
+ `check:package` asserts an explicit reviewed package manifest, zero runtime
159
+ dependencies, MIT metadata and complete license bytes, and the push host. It runs `gem install --norc --local`
160
+ from a separate temporary working directory inside `Bundler.with_unbundled_env`,
161
+ with fresh `GEM_HOME`/`GEM_PATH` and cleared Ruby/Bundler options. It verifies
162
+ core require without Google/Rack, an ordinary missing `connectrpc/rack` file
163
+ error, and useful missing Google/generator dependency errors.
164
+ It independently builds the optional Rack archive under `vendor/package/rack`,
165
+ stages it and only the required runtime archives in the external cwd, and uses
166
+ normal offline dependency resolution. It installs the cached, locked optional
167
+ artifacts, including native
168
+ `bigdecimal`, Google protobuf and its `rake` dependency, Rack, rackup and
169
+ WEBrick. The installed gem performs real typed Rack/NetHTTP calls with both
170
+ binary and JSON scalar-wrapper payloads with identity and gzip, including errors. The helper scripts are copied to the
171
+ external cwd; library code loads exclusively from the fresh installed gem.
172
+ The installed executable first proves missing Google fails helpfully with empty
173
+ stdout. The task retains its task-local compiler activation, and the check
174
+ requires `protoc --version` to report `libprotoc 36.2`. Missing executables and
175
+ failed commands report the command and failure status. After the optional
176
+ artifacts are installed, actual protoc invokes only
177
+ `GEM_HOME/bin/protoc-gen-connectrpc-ruby` outside the checkout with cleared
178
+ Ruby/Bundler source paths. The generated tree performs typed success/error,
179
+ metadata/trailers, nested imported types and presence calls in both codecs.
180
+ Google package and explicit Ruby package fixtures import ordinary WKTs, assert
181
+ exact official message-class identity and perform typed calls in both codecs.
182
+ The check does not install the development JSON gem: it exercises the Ruby
183
+ default JSON and Net::HTTP gems. All installs are offline and temporary files
184
+ are removed. Nothing is published.
185
+
186
+ Core ships no `lib/connectrpc/rack.rb`. Core `sig/`, the explicitly selected
187
+ `rbs/google_protobuf_4_36/` bridge, remaining `lib/**/*.rb`,
188
+ `exe/protoc-gen-connectrpc-ruby`, `LICENSE`, `README.md`, and the
189
+ design/development/[RBS guide](rbs.md) documents ship. The executable metadata and packaged execute
190
+ bits are checked; syntax and RuboCop include the extensionless executable.
191
+ The optional Rack, Rails, Async, testing and Falcon packages carry byte-identical copies of the root
192
+ `LICENSE`; their package checks verify both the copies and archived notices.
193
+ Keep these copies in sync when updating the canonical notice.
194
+ The exact manifest in `scripts/check_package.rb` must be deliberately updated
195
+ when library files are added. Specs, fixtures, examples, peer code, tooling,
196
+ coverage and installed development dependencies do not ship.
197
+
198
+ ### Generator provenance and tools
199
+
200
+ Ordinary Specs load unique checked-in official outputs without installing
201
+ protoc, Buf, Go or the conformance runner. `spec/fixtures/generation/proto/`
202
+ owns unique schemas; `ruby/` contains protoc 36.2 message output and this
203
+ plugin's glue. `echo/` contains official outputs for the existing shared
204
+ `echo.proto`; these run in a separate process to avoid duplicate descriptor-pool
205
+ registration with `EchoExample`. Never hand-edit generated files.
206
+
207
+ `generation:check` regenerates messages/glue with protoc 36.2 and runs real Buf
208
+ 1.75.0 local plugins, including the official protoc Ruby builtin. Service glue
209
+ must be byte-identical between compilers. Their official message descriptor bytes
210
+ can differ, so each output tree is independently loaded and exercised. Compiler
211
+ version, comments and source-code info do not affect glue. Official `_pb.rb`
212
+ files alone are excluded from RuboCop; glue remains checked, with scoped naming
213
+ and named-forwarding exceptions for the specified generated API.
214
+
215
+ Mise locks Buf's Linux x64 archive SHA256
216
+ `7168a06e29bd5eb8d1530848f242264f7a86b9b8af3b2e811570fbea0c929a30`,
217
+ verified against upstream [sha256.txt](https://github.com/bufbuild/buf/releases/download/v1.75.0/sha256.txt).
218
+ The stable [release API](https://api.github.com/repos/bufbuild/buf/releases/tags/v1.75.0)
219
+ reports publication on 2026-10-22T01:02:29Z, over seven days before the
220
+ 2026-11-02 audit. Existing protoc and all other pins remain unchanged. Buf is
221
+ development-only and uses ignored `vendor/buf/cache`, never global config/cache.
222
+ Package CI installs both tools; Interop consumes checked-in output and needs
223
+ neither compiler. `example:generated` generates temporary greeting output and
224
+ cleans it up after observable localhost shutdown.
225
+
226
+ ### Independent interoperability and fixture provenance
227
+
228
+ `test:interop` builds a reference peer with Go 1.27.3,
229
+ `connectrpc.com/connect` v1.21.1 and `google.golang.org/protobuf` v1.36.12.
230
+ Go is task-local, with its Linux x64 download checksum in `mise.lock`;
231
+ ordinary setup/focused Ruby checks do not need it. `GOTOOLCHAIN=local`,
232
+ `GOWORK=off`, `GOFLAGS=-mod=readonly` and checked-in `go.sum` prevent implicit
233
+ toolchain/module updates. Go's module/build caches are project-local under
234
+ ignored `vendor/interop/`. Setup downloads the pinned modules; tests run
235
+ `go mod verify` and a readonly build.
236
+
237
+ The 18 fixture cases are success, deliberate error/details/metadata, and a
238
+ request limit, for both codecs across Go-to-Go, Ruby-to-Go and Go-to-Ruby.
239
+ They use the actual connect-go Client and Handler with dynamic message
240
+ initialization. The Go-to-Go baseline runs while the existing Go host is alive,
241
+ using the same client scenarios and configured limits as Go-to-Ruby. Twelve
242
+ cases establish cross-language interoperability; six validate the reference
243
+ baseline. Success compares typed messages semantically, covering optional
244
+ presence, int64, enum, timestamp and bytes. The Go baseline asserts separate raw
245
+ request/response metadata, trailers and error metadata. Go-to-Ruby keeps exact
246
+ WEBrick coalescing expectations for those known fixture fields. Raw repeated Go
247
+ response values remain separate at NetHTTP. NetHTTP joins outgoing request
248
+ arrays with comma+space, and the WEBrick host also coalesces incoming and
249
+ response values. Assertions preserve those observed semantics without splitting
250
+ arbitrary comma-containing values. The separate generated process adds Ruby->Go
251
+ success/error calls and reuses
252
+ the Go client's six existing success/error/limit scenarios against generated
253
+ Ruby handler registration. Both codecs execute, retaining the original 18-case
254
+ fixture result unchanged. This is not the Connect conformance suite.
255
+
256
+ `spec/fixtures/echo.proto` documents the schema. `scripts/build_fixture.rb`
257
+ explicitly builds a matching `FileDescriptorProto`; it is not protoc output or
258
+ a protobuf parser. The checked-in `echo.bin` is the single descriptor used by
259
+ both peers. `fixture:check` compares it byte-for-byte with a rebuilt descriptor,
260
+ Ruby mapping specs exercise its types, and Go reflection checks presence,
261
+ int64, enum and timestamp fields. If changing the schema, update both the
262
+ `.proto` and builder and regenerate the bytes with `fixture:build`.
263
+
264
+ Localhost tests/examples use port 0 and bounded readiness/shutdown signals.
265
+ Socket lifecycle tests establish request progress, then exercise stalled body,
266
+ aggregate response-header trickle and blocked outbound writes. Header-trickle
267
+ pacing measures real elapsed network behavior, not readiness. The Ruby 3.4
268
+ stdlib may lazily retain its process-wide Timeout service thread; tests exclude
269
+ that named service from call-owned worker checks. ConnectRPC watchdogs and
270
+ peer/host workers must all stop, and sockets must close.
271
+ Pre-dispatch race tests use a controlled monotonic clock and peer-observed EOF
272
+ before resuming dispatch, then check for queued replacement connections after
273
+ the call completes. Wire timeout assertions consume budget during preparation
274
+ and connection. Huge finite budgets exercise real localhost I/O, with capped
275
+ native waits and an unchanged logical deadline. Scaled-cap socket tests verify
276
+ `unavailable` before logical expiry and `deadline_exceeded` after actual expiry,
277
+ with closure and no replacement connection. Huge-budget tests exercise native
278
+ conversions, not the full elapsed native-operation ceiling. Proxy configuration assertions
279
+ set hostile proxy environment variables and restore their previous state.
280
+
281
+ ## Dependency and action updates
282
+
283
+ Keep exact direct dependency and tool pins. Check release dates against the
284
+ [primary RubyGems API](https://guides.rubygems.org/rubygems-org-api/) or upstream
285
+ GitHub releases before updating: stable releases must be at least seven days
286
+ old. Verify Ruby requirements for both supported Rubies. For actions, resolve
287
+ the upstream release tag to its commit and retain a full SHA with a tag comment.
288
+ Mise's minimum age filters fuzzy resolution, not explicit pins; review pinned
289
+ release ages manually. Bundler's source cooldown is seven days; old registries
290
+ or index entries lacking timestamps may not enforce it, so audit newly resolved
291
+ versions too. See [Bundler cooldown](https://bundler.io/man/bundle-config.1.html).
292
+
293
+ For a deliberate dependency update, edit the Gemfile, then resolve the lock
294
+ with frozen mode temporarily disabled. Keep the generic Ruby platform and
295
+ checksums, and do not add a `ruby` Gemfile directive or `RUBY VERSION` field
296
+ that excludes the other CI Ruby:
297
+
298
+ ```sh
299
+ mise exec -- env BUNDLE_FROZEN=false bundle lock --update --add-platform ruby --add-checksums
300
+ mise lock --platform linux-x64
301
+ mise run setup
302
+ mise run verify
303
+ ```
304
+
305
+ Review all lock changes, including transitive dependencies. CI uses frozen
306
+ installation. For an urgent security fix inside the seven-day window, obtain
307
+ an explicit maintainer exception, record the advisory/version/reason in the PR,
308
+ and override cooldown only for that resolution (for example,
309
+ `mise exec -- env BUNDLE_COOLDOWN=0 BUNDLE_FROZEN=false bundle lock --update <gem>`).
310
+ Do not weaken the persistent policy. Original project work is [MIT licensed](../LICENSE).
311
+ The invalid push host remains an accidental-push guard, not a security boundary
312
+ against someone editing metadata. Publishing, releasing or deploying requires
313
+ separate, explicit authorization.
314
+
315
+ ## Hooks and pull requests
316
+
317
+ Local hooks: deferred (`n/a`). This repository has only a tiny library and
318
+ canonical checks; automatic hooks add installation and partial-staging
319
+ semantics before there is evidence of recurring missed checks. Run `check`
320
+ while working and rely on CI for required verification. Reconsider a small,
321
+ non-writing pre-commit check if contributors repeatedly miss checks, after
322
+ measuring warm runtime and validating worktree/partial-staging behavior. No
323
+ pre-push hook is installed.
324
+
325
+ Keep PRs scoped. Describe the concrete result, relevant design boundaries,
326
+ commands and results (including RSpec example counts), and remaining limits.
327
+ Approved merges use squash merging; merge authorization remains separate.
328
+ Reuse valid evidence. Design changes should update [design.md](design.md).
329
+ Releases use a Release Please PR on main, local candidate tasks and a
330
+ tag-triggered publishing workflow. See the
331
+ [release guide](https://github.com/jimeh/connectrpc-ruby/blob/main/docs/releasing.md)
332
+ for App and trusted-publisher setup, synchronized versions and recovery.
333
+ Project MIT licensing is approved. Releasing still needs separate, explicit authorization.
334
+
335
+ ## Optional Rails development
336
+
337
+ The self-contained [Rails adapter guide](../gems/connectrpc-on-rails/README.md)
338
+ owns controller setup, lifecycle/security limits and generated-code placement.
339
+ Root `Gemfile`, lock, runtime dependencies, setup and specs do not require Rails.
340
+ Discover adapter commands with `mise tasks`:
341
+
342
+ ```sh
343
+ RAILS_LANE=8.0 mise run test:rails
344
+ RAILS_LANE=8.1 mise run verify:rails
345
+ MISE_RUBY_VERSION=4.0.9 RAILS_LANE=8.1 mise run verify:rails
346
+ mise run generation:check:rails
347
+ mise run build:rails
348
+ mise run check:package:rails
349
+ ```
350
+
351
+ The launcher prints Ruby, exact selected Rails, Bundler and Gemfile. Specs assert
352
+ actual loaded Rails/Bundler. The tested combinations are Rails 8.0.5.1/Ruby
353
+ 3.4.12 and Rails 8.1.4/Ruby 3.4.12 or 4.0.9. The default lane is 8.1.
354
+ `verify` includes the selected Rails lane, not all three combinations; CI runs
355
+ all three independently. Adapter setup depends on `setup:bundler`, not root
356
+ `setup`, and uses the root ABI-specific `vendor/bundle` cache.
357
+
358
+ Actual Appraisal 2.5.0 generates the lane Gemfiles. Its parser cannot accept a
359
+ source cooldown keyword, so the adapter development source is plain and tasks
360
+ and CI enforce `BUNDLE_COOLDOWN=7`. Root source cooldown/pins/lock stay intact.
361
+ There is no adapter base lock: select one generated checksummed lane lock for
362
+ frozen installation and actual `appraisal <lane>` execution. Do not run
363
+ `appraisal install` in CI. The launcher checks Bundler 4.0.23 is installed
364
+ before Appraisal can invoke its legacy installer fallback.
365
+ `generation:check:rails` runs the actual generator in a temporary relative-layout
366
+ copy and byte-compares both outputs without touching checked-in Gemfiles.
367
+ Changing dependencies requires the same seven-day primary registry release-age
368
+ and archive checksum audit as root dependencies, including native variants.
369
+
370
+ Adapter SimpleCov includes every adapter `lib/**/*.rb` file before requires
371
+ and writes to `vendor/rails/coverage`, separate from core. Real app tests use
372
+ matching Rails defaults, native Base/API dispatch and CSRF/session behavior,
373
+ Rack::Lint, eager load/reload and synchronized concurrent requests.
374
+ `build:rails` builds core, Rack and Rails exact archives into `vendor/rails/pkg` independently
375
+ of root build output. `check:package:rails` checks all three manifests, guards and
376
+ conventional runtime dependency requirements, stages only their runtime closure, installs offline into fresh
377
+ GEM_HOME/GEM_PATH outside the checkout, and exercises installed generated
378
+ binary/JSON clients against native Rails callbacks and structured failures.
379
+ The adapter README, `LICENSE`, library files and its owned `sig/` manifest/signatures ship in its archive. No dependencies
380
+ or fixtures are vendored into either package, and nothing is published.
381
+
382
+ ## Optional HTTP/2 development
383
+
384
+ The [backend guide](../gems/connectrpc-async/README.md) owns its public API,
385
+ reactor affinity, TLS/mTLS lifecycle, backend normalization and resource limits.
386
+ Use `mise run async:setup`, then focused `mise run async:test -- spec/tls_spec.rb`
387
+ or `async:check` while iterating. `async:build` builds its own core and backend
388
+ archives under `vendor/async/pkg`; `async:package` installs cached normal runtime
389
+ dependencies in a fresh outside-checkout GEM_HOME and performs generated
390
+ h2c/mTLS calls with identity and gzip plus core NetHTTP TLS/JSON in both encodings.
391
+ That process activates OpenSSL 4.0.2/JSON 3.1.0, so its TLS trust fixture is passed
392
+ only to the child process.
393
+ `async:example` runs the same generated localhost scenarios from development
394
+ sources. `async:interop` uses the existing pinned Go module graph and toolchain
395
+ with a separate peer executable. `async:verify` combines backend checks, product
396
+ specs, package isolation and independent Go interoperability; root `verify`
397
+ includes it alongside the existing core and selected Rails verification.
398
+
399
+ The backend Gemfile/lock is independent; do not resolve it into root or Rails
400
+ locks. All registry/platform artifacts require seven-day primary release-age
401
+ and archive checksum verification, including generic/native variants. Keep its
402
+ root dependency as a development-only relative path. Bundle installation and
403
+ native caches use `vendor/async/bundle`; Bundler user state uses
404
+ `vendor/async/bundler`. Only the observed generated backend `.bundle/` directory
405
+ is ignored. SimpleCov starts before backend/core requires, includes every
406
+ backend library file and writes `coverage/async/<Ruby>/`. Green coverage does
407
+ not establish interoperability or hosted clean-environment success.
408
+
409
+ ## Optional Rack development
410
+
411
+ The [middleware guide](../gems/connectrpc-rack/README.md) owns installation,
412
+ mounting and lifecycle limits. Run `mise run rack:setup`, focused
413
+ `mise run rack:test -- spec/rack_spec.rb`, and `mise run rack:check` while iterating.
414
+ The frozen checksummed Gemfile reuses exact root registry pins with core as a
415
+ development path dependency. Bundle, Bundler user state, lint cache and build
416
+ outputs are isolated under `vendor/rack`; SimpleCov starts before requires and
417
+ covers every package source in `coverage/rack/<Ruby>/`.
418
+
419
+ `rack:build` builds its own core and middleware archives, avoiding root/Rails
420
+ output races. `rack:package` checks exact manifests/dependencies/guards, installs
421
+ core alone to prove middleware is unavailable, then resolves the Rack package
422
+ normally offline using staged runtime archives in an external fresh GEM_HOME.
423
+ It runs real Google proto/JSON identity/gzip success, errors/details and metadata
424
+ under Rack::Lint over ephemeral localhost NetHTTP, asserting installed core and
425
+ Rack source ownership. Root installed checks also retain default JSON/NetHTTP
426
+ and the actual installed generator. Rails checks all three local owners.
427
+
428
+ Final local handoff requires `mise run verify` followed by `mise run rack:verify`.
429
+ Rack verification stays separate from root `verify`. CI runs the core checks and
430
+ two Rack specs/check/package lanes on the existing Rubies; clean-environment
431
+ matrix evidence remains a hosted responsibility. No runtime savings are claimed.
432
+
433
+ See the optional [in-process testing package](../gems/connectrpc-testing/README.md) for real encoded client/server round trips without sockets.
434
+
435
+ ## Optional RBS development
436
+
437
+ [The RBS guide](rbs.md) covers official message generation, opt-in service RBS,
438
+ strict Steep configuration, the explicit protobuf bridge and nominal alternatives.
439
+ `mise run rbs:setup` installs the independent frozen checksummed development
440
+ bundle. `rbs:check` checks protoc/Buf parity, unchanged default Ruby and official
441
+ message RBS, deterministic output, real generated runtime calls, RBS validation
442
+ and positive/negative strict consumers. It also runs the strict core
443
+ implementation target defined by `typecheck/source.rb`, reviewed source inventory
444
+ and per-package API callers.
445
+ Discover the focused `rbs:source`, `rbs:api`, `rbs:inventory` and capture-only
446
+ `rbs:proof` leaves with `mise tasks`. `rbs:package` checks every installed package
447
+ and composed optional environments outside the checkout, using the existing
448
+ scoped frozen optional setups to populate offline archives. Run both on each pinned Ruby.
449
+ These leaves keep their Bundler state, caches, bundle and package outputs under
450
+ `vendor/rbs/` and add no runtime or root-bundle dependencies.
451
+
452
+ ## Optional Falcon development
453
+
454
+ The [Falcon guide](../gems/connectrpc-falcon/README.md) owns the listener,
455
+ worker-local TLS provider and Rack/Rails fallback contracts. Start with
456
+ `mise run falcon:setup`, then use focused `falcon:test` selectors and
457
+ `falcon:check`. `falcon:build` writes separate core/Async/Falcon archives to
458
+ `vendor/falcon/pkg`. `falcon:package` installs normal dependencies offline in an
459
+ isolated GEM_HOME and runs the actual Falcon DSL/service outside the checkout.
460
+ `falcon:interop` runs identity and gzip against the pinned independent Go peer,
461
+ including actual Falcon process workers. `falcon:verify` combines these leaves;
462
+ root `verify` includes it. CI runs separate Falcon specs/package and Go checks
463
+ across Ruby 3.4.12 and 4.0.9.
464
+
465
+ Keep its checksummed Gemfile/lock, `vendor/falcon/bundle`, Bundler user state and
466
+ `coverage/falcon/<Ruby>/` independent. Audit every added registry/platform
467
+ artifact for seven-day release age and downloaded archive SHA256. Actual worker,
468
+ TLS, retirement and installed-caller tests establish different properties;
469
+ green coverage or baseline probes do not substitute for them.
470
+
471
+ ## Optional OpenTelemetry development
472
+
473
+ `mise run otel:setup` installs the independent checksummed API/SDK and integration
474
+ bundle under `vendor/otel`. Use `otel:test -- spec/tracing_spec.rb` for generated
475
+ in-process traces, `spec/native_spec.rb` for real Async/Falcon boundaries, and
476
+ `spec/rack_rails_spec.rb spec/rails_spec.rb` for Rack/NetHTTP and native Rails.
477
+ `otel:check`, `otel:build`, `otel:package`, `otel:example` and `otel:verify` are
478
+ discoverable leaves. Repeat `MISE_RUBY_VERSION=4.0.9 mise run otel:verify` for the
479
+ second pinned runtime. Coverage starts before requires and includes core and all
480
+ local integration libraries under `coverage/otel/<Ruby>/`. The two OTel CI leaves
481
+ run independently of the other package jobs. Runtime bundles never gain an SDK.
482
+ Package checks install normal dependencies offline into a fresh external
483
+ GEM_HOME, then run copied generated callers with actual SDK in-memory exports.
484
+ See the [package guide](../gems/connectrpc-otel/README.md) for the precise trace
485
+ baseline and unknown-method default-redaction limitation.
486
+
487
+ ## Native streaming verification
488
+
489
+ `async:test` and `falcon:test` include the incremental shape/codec/coding/security
490
+ matrix, bounded output/ingress, healthy same-connection siblings and cancellation.
491
+ `otel:test` includes actual SDK processor-task identity, mTLS peer identity,
492
+ codec/handler cleanup and separate processing/HTTP completion evidence.
493
+ Falcon H1 focused specs distinguish the unchanged absolute 100 ms / 256 KiB
494
+ discard (adapter remainder and full 64 KiB read reservations) from the cooperative
495
+ 500 ms total final-close expiry anchored at actual final HTTP flush. They check
496
+ EOF before hygiene, delayed callbacks spending the same budget, no reads or
497
+ pipeline parsing during remaining grace, admission joins before host cleanup,
498
+ owner interruption and real worker shutdown. Timings may overshoot under reactor
499
+ delay; they are not hard real-time bounds. SDK specs preserve completed delivery
500
+ after later hygiene/grace failure.
501
+
502
+ `async:interop:streaming` uses the unchanged pinned connect-go graph in both
503
+ directions for all three streaming shapes, both codecs and codings over
504
+ h2c/TLS/mTLS. `falcon:interop:streaming` runs the Go client against actual Falcon
505
+ process workers and checks worker shutdown. These supplement the original unary,
506
+ GET, generated, cancellation and tail-drain leaves; they do not replace them.
507
+
508
+ `async:conformance` executes native client and Async server profiles.
509
+ `falcon:conformance` executes only Falcon server profiles. Exact inventories are
510
+ 290/262 identity client/server and 268/246 gzip client/server, with zero expected
511
+ failures. Every run checks actual runner discovery, scheduled/received names,
512
+ passes and closed library/transport/server lifetimes. Source transitions and
513
+ actual canceled library results are recorded; socket interruption remains unknown
514
+ without controlled wire evidence. Original unary profiles and their 5 ms before-call,
515
+ 200 ms response-delay and nil-deadline semantics remain independent and unchanged.
516
+ Both native conformance and streaming Go leaves belong to each backend's verify
517
+ aggregate and the existing two-Ruby CI jobs. No test results are cached.
518
+ The Falcon-only `falcon:conformance:http1` leaf adds separate SS/CS server
519
+ profiles requiring 174 identity and 160 gzip passes. Root `verify` includes this
520
+ complete H1 gate, so use that pass for full-profile confirmation. Fresh exact
521
+ oversized CS selectors can be run separately for focused regression evidence;
522
+ do not weaken their original assertions or inventories.
523
+
524
+ `rbs:check` includes concrete generated streaming positives and exact negative
525
+ fixtures. `rbs:package` runs generated streaming network callers and a custom
526
+ streaming SPI under normal installed discovery outside the checkout, in both
527
+ package load orders. The [streaming guide](streaming.md) documents the fixed
528
+ one-second tail policy and its compatibility tradeoff.
529
+
530
+ ## Official browser development
531
+
532
+ `mise run browser:setup` installs development-only tools under `vendor/browser`;
533
+ ordinary core setup installs no JS runtime or browser. Linux x86_64 on Ubuntu
534
+ 24.04 is the audited platform. Python 3's standard ZIP support and tar extract
535
+ checksum-verified official archives without OS dependency installation. Exact
536
+ artifact hashes and primary publication dates live in `browser/artifacts.json`.
537
+ Bun 1.4.4 manages a frozen five-package lock and bundles an ESM entry to IIFE.
538
+ Node 24.22.1 executes Playwright 1.64.1 with Chromium 156.0.8078.4. grpc-web 2.1.1
539
+ and google-protobuf 4.0.3 are development-only. All pins exceed seven days.
540
+
541
+ `browser:generation:build` and `browser:generation:check` use official protoc
542
+ 36.2, protoc-gen-grpc-web 2.1.1 and protoc-gen-js 4.0.3 binaries. Generated
543
+ binary/text clients, protobuf messages and canonical Any/Status remain untouched
544
+ by hand. `browser:test` exercises actual cross-origin generated browser clients
545
+ against real Async, Falcon and native Rails fixtures. `browser:verify` combines
546
+ generation parity and the collected browser suite. The two Browser gRPC-Web CI
547
+ leaves run on Ruby 3.4.12 and 4.0.9 without replacing existing checks.
548
+
549
+ Playwright explicitly uses `chromiumSandbox: false` for its owned local process
550
+ and `ignoreHTTPSErrors: true` for fixture TLS. No host security or trust setting
551
+ changes. These browser TLS calls do not establish verified authentication; the
552
+ SDK/mTLS focused specs separately verify peer identity on native H2 and Falcon
553
+ H1. No screenshots, shared browser sessions or uploads are required.
554
+
555
+ The separate `browser/reference` Go module wraps the existing official generated
556
+ grpc-go service with improbable-eng/grpc-web 0.15.0 for independent text browser
557
+ controls. Audited compress 1.20.1/cors 1.11.1 pins keep its old default graph out
558
+ of use; core and existing Go graphs are unchanged. The same executable uses
559
+ existing connect-go 1.21.1 for independent binary unary/stream wire controls.
560
+ Caches, executables, browser output and fixture logs stay in `vendor/browser`.
561
+
562
+ `async:conformance:web`, `falcon:conformance:web` and `rack:conformance:web` run
563
+ separate exact binary inventories of 138 H2 unary/SS, 276 H1/H2 unary/SS and 37
564
+ portable plain-H1 unary cases. Each validates actual runner names, dispatch,
565
+ wire arrivals, processing, observer completion and shutdown. The official runner
566
+ has no text axis. Existing Connect/native-gRPC inventories remain independent.