Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 62 additions & 10 deletions docs/deviations.md

Large diffs are not rendered by default.

28 changes: 26 additions & 2 deletions docs/first-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ stated in the release notes rather than discovered at `bundle install`.
`#promote_to_request` and `#promote_to_exchange`, and where the operation name goes -- because no
phase builds a call path that does it and this is the one place a generated client's author meets
it (`CTX-16`, `CTX-14`, `SEAM-28`; the *Behavioural asymmetries* entry below)
- [ ] **The eighteen recorded corrections (fourteen until phase 10 completed the set on 2026-09-25) to `docs/sdk-design-ruby/` §3, §4, §5, §8, §9, §10, §11, §12 and to
- [x] **The eighteen recorded corrections (fourteen until phase 10 completed the set on 2026-09-25) to `docs/sdk-design-ruby/` §3, §4, §5, §8, §9, §10, §11, §12 and to
appendix C's `SSE-19` row applied — or the release notes stating which design sentences a reader
should not trust.** Filed 2026-09-13 by phase 10's design. Every one is a place where the **rule**
is right, the **mechanism sentence** is wrong about Ruby or about a library, and the phase that
Expand Down Expand Up @@ -243,7 +243,16 @@ stated in the release notes rather than discovered at `bundle install`.
`P10-21`-`P10-38` -- into design §10.** Each phase's status note ends "consolidation into design §10
is a human's"; phase 10 is the last phase and the chapter is frozen to it. **The alternative form holds
for this half too**: a release may ship with the ledgers unconsolidated if its notes list the phase
ledgers a reader must consult beside §10
ledgers a reader must consult beside §10. **Ticked 2026-09-25, both halves, by a human.** `C1`-`C19`
were each re-verified against as-built source and applied in place, with a dated, bracketed note quoting
the sentence replaced and citing its code half — `C11` as a recommendation note on appendix C's two rows,
the only edit to `docs/product-spec/`; six more of the same kind the reconciliation found, `C20`-`C25`,
were applied the same way and are written out in `docs/deviations.md`'s amendment set. The consolidation
is done: every phase's ledger rows were triaged against the reference contract, the genuine deviations
§10 did not carry became entries 20-38 (or dated clauses on entries 1, 2, 4, 6, 9, 13, 15, 17 and 18),
and the rows that record only a departure from the design or the plan, with the specification met, were
excluded. `gates:ledger_audit` holds the 38 rows of `docs/deviations.md` to the 38 entries. No release
note needs to list a sentence a reader should not trust on this account
- [ ] An RBS sig-diff baseline established, so a later release can be checked against it for an
accidental breaking change. **Phase 10 is the phase that can**: its plan, Task 5 is the last change
to `sig/` in every gem, so Task 18 establishes the baseline over a tree nothing else will move —
Expand Down Expand Up @@ -414,6 +423,21 @@ find it in a design document.
it reads is the configured `Dexpace::IO.max_materialized_bytes`. Cites `SERDE-27`, `SEAM-21`, `IO-9`,
`BODY-32`.

- **Two MUST clauses unmet on a stated domain, recorded in §10 on 2026-09-25 when the phase ledgers were
consolidated.** Neither is a new finding — each was a phase ledger row — but neither had reached this
section. **`HTTP-45`'s no-pinning clause (and `BODY-22`'s once-drain)** holds for threads and not for a
second fiber of the same thread with no `Fiber.scheduler` installed: `Thread::Mutex` and
`Thread::ConditionVariable` defer to an installed scheduler and otherwise park the carrier thread, so such a
fiber arriving mid-parse blocks the thread, and deadlocks if the parsing fiber is never resumed (3b's
`P3-27`, design §10 entry 22, `gems/dexpace-core/lib/dexpace/http/typed_response.rb`). Ruby has no
scheduler-independent primitive that serialises without parking; the pick-up trigger is a consumer that
drives one response's typed value from two fibers of one thread without a scheduler. **`TRANSPORT-14`'s
header-name clause (with `TRANSPORT-27`'s SHOULD `Content-Length` half)** is unreachable on
`dexpace-transport-async_http`, where `protocol-http1` refuses the head out of the read, so the whole
response fails as a retryable `Dexpace::TransportError` (8c's `P8-38`, design §10 entry 21); the waiver and
its disclosure are the conformance entry above. Its trigger is an `async-http`/`protocol-http1` release
that tolerates such a header.

### SHOULD- and MAY-level requirements declined for v1

Each bullet: the IDs with their level, why v1 declines them, the trigger that would reopen the decision,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,7 @@ Every normative requirement defined in this specification, aggregated for confor
| SSE-16 | MUST | Server-Sent Events and streaming | The reader MUST be single-pass and stateful: only the 'BOM already consumed' flag persists across next() calls; the last-event-id is NOT carried forward between events — each event surfaces only the id present in its own block. A reimplementation MUST NOT maintain a WHATWG-style persistent last-event-id buffer inside the parser. |
| SSE-17 | MUST | Server-Sent Events and streaming | The reader MUST NOT own or close the underlying byte source; source lifecycle is the caller's responsibility. (Resource ownership is introduced only by the SseStream facade — see SSE-23.) |
| SSE-18 | MUST | Server-Sent Events and streaming | A single reader instance MUST be driven from one thread at a time; the parser is not internally synchronized and offers no thread-safety for concurrent next() calls. A port MAY leave the parser non-thread-safe. |
| SSE-19 | MAY | Server-Sent Events and streaming | The parser MAY accept arbitrarily long lines / data values with no built-in size cap; the reference implementation imposes no maximum line or event size (a growable byte accumulator expands by doubling). |
| SSE-19 | MAY | Server-Sent Events and streaming | The parser MAY accept arbitrarily long lines / data values with no built-in size cap; the reference implementation imposes no maximum line or event size (a growable byte accumulator expands by doubling). [Amended 2026-09-25, amendment C11 of docs/deviations.md, as a recommendation in the idiom of the Ruby design's §11: this row omits the port sanction its chapter grants — 13-server-sent-events-and-streaming.md ends SSE-19 with "a port MAY add a configurable cap and reject/truncate oversized lines, documenting the divergence" — and the chapter says "lines/values" where this row says "line or event". The two statements together are the complete SSE-19. The Ruby port's two rejecting caps, dexpace-core's SSE::MAX_LINE_BYTES and SSE::MAX_EVENT_BYTES, are recorded in its design §10 entry 18.] |
| SSE-20 | MUST | Server-Sent Events and streaming | The parsed event value MUST be immutable and hold a defensively-copied, read-only data list so that neither the caller's originally-supplied list nor later mutations can reach inside a constructed event, and any copy-with-changes operation MUST likewise copy the data list. |
| SSE-21 | SHOULD | Server-Sent Events and streaming | The event value SHOULD provide structural value semantics — equality and hash derived from all five fields (id, event, data, comment, retry), and a stable string form — so events can be compared and used in collections. |
| SSE-22 | SHOULD | Server-Sent Events and streaming | The event value SHOULD expose an 'is-empty' predicate that is true only when all five fields are unset/empty; because a comment counts as content, a comment-only (keep-alive) event SHOULD report non-empty. |
Expand Down Expand Up @@ -506,7 +506,7 @@ Every normative requirement defined in this specification, aggregated for confor
| OBS-26 | MUST | Instrumentation and observability | An instrumentation/trace context MUST expose W3C-Trace-Context-compliant identifiers: a trace id, a span id (16 lowercase hex chars), trace flags (a two-hex-char byte, e.g. the sampled bit), and a vendor trace-state list. It MUST expose validity and remoteness flags. The reserved invalid sentinels MUST be: trace id of 32 hex zeros, span id of 16 hex zeros, trace flags '00', and empty trace-state; an all-zero trace/span id MUST be treated as invalid/no-trace. |
| OBS-27 | MUST | Instrumentation and observability | Trace-id generation MUST support at least the W3C flavour (128-bit value rendered as 32 lowercase hex chars) and a Datadog flavour (64-bit unsigned integer rendered as a decimal string), plus a no-op flavour that always yields the invalid sentinel. Generation MUST NOT produce the reserved all-zero id — a zero draw MUST be coerced to a non-zero value. |
| OBS-28 | SHOULD | Instrumentation and observability | The SDK SHOULD provide an HTTP-shaped tracer event vocabulary richer than start/end: operation started/succeeded/failed; per-attempt started/failed(with next-delay)/retries-exhausted; and transport milestones (request URL resolved, connection acquired with host+port, request sent with byte count, response headers received with status+headers, response received with byte count). Every event method SHOULD default to a no-op so adding a new event is a non-breaking change and implementers override only what they need. |
| OBS-29 | MUST | Instrumentation and observability | HTTP-tracer lifecycle ordering MUST hold: operationStarted fires once at the start; operationSucceeded and operationFailed are mutually exclusive and each fires exactly once at the end; attempt events may fire multiple times; retries-exhausted (when it fires) is immediately followed by operationFailed with the same throwable. One tracer instance corresponds 1:1 to a single logical operation lifecycle (created by the factory per operation). This is a documented emission contract; pipeline/transport wiring to emit it is a follow-up, so it is not yet runtime-enforced. |
| OBS-29 | MUST | Instrumentation and observability | HTTP-tracer lifecycle ordering MUST hold: operationStarted fires once at the start; operationSucceeded and operationFailed are mutually exclusive and each fires exactly once at the end; attempt events may fire multiple times; retries-exhausted (when it fires) is immediately followed by operationFailed with the same throwable. One tracer instance corresponds 1:1 to a single logical operation lifecycle (created by the factory per operation). This is a documented emission contract; pipeline/transport wiring to emit it is a follow-up, so it is not yet runtime-enforced. [Amended 2026-09-25, amendment C11 of docs/deviations.md, as a recommendation in the idiom of the Ruby design's §11: the chapter statement, 15-instrumentation-and-observability.md, carries neither the "(created by the factory per operation)" parenthetical nor the follow-up clause above, and carries a Conformance clause this row drops — drive a succeeding and a failing (retry-exhausted) operation through a conformant emitter and assert the ordering and the exhausted-then-failed pairing. The two statements together are the complete OBS-29; read from the chapter alone, the emission wiring looks like an open obligation, which is how five Ruby-port documents came to carry it as an open decision.] |
| OBS-30 | MUST | Instrumentation and observability | Tracer and HTTP-tracer callbacks MUST be safe to invoke concurrently and from transport threads other than the caller's, and implementations MUST NOT throw from any callback. The metrics instruments MUST likewise be safe to call concurrently from request threads and MUST NOT throw. The instrumentation runtime does not defensively catch these callbacks (see OBS-20), so violating must-not-throw breaks the caller's request. |
| OBS-31 | MUST | Instrumentation and observability | The metrics SPI MUST expose a Meter that manufactures at least a monotonic integer counter and a floating-point histogram, each accepting per-measurement key/value attributes. The default Meter MUST be a no-op that discards every measurement and returns shared instrument singletons, and the core MUST NOT pull a metrics runtime into its dependencies. |
| OBS-32 | SHOULD | Instrumentation and observability | Metric instrument names, descriptions, and units SHOULD follow OpenTelemetry semantic conventions and UCUM unit symbols. The default HTTP instrumentation SHOULD emit a request counter named 'http.client.request.count' (unit '{request}') and a latency histogram named 'http.client.request.duration' (unit 'ms'), tagged with method and either status code (success) or error type (failure). |
Expand Down
6 changes: 6 additions & 0 deletions docs/sdk-design-ruby.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ layer; it specifies only the runtime primitives a generator would target (§3.5)
protocol beyond HTTP/1.1 for the reference transport, nor does it design the service-client surface a generator
would emit on top of it.

**Reconciled 2026-09-25 against the as-built tree.** The chapters below were written before the code and are kept
as written, except where the shipped code proved a sentence wrong: each such sentence is corrected in place with a
dated, bracketed note quoting what it replaced and citing its amendment in `docs/deviations.md` (`C1`–`C25`), and
§10 — the deviation ledger — gains entries 20–38, consolidating every deviation from the reference contract the
phases' own ledgers recorded and the chapter did not. §12 is brought into line with both.

---

## Table of Contents
Expand Down
Loading
Loading