diff --git a/.claude/skills/housekeeping/chapters.rb b/.claude/skills/housekeeping/chapters.rb new file mode 100644 index 0000000..1709d18 --- /dev/null +++ b/.claude/skills/housekeeping/chapters.rb @@ -0,0 +1,156 @@ +# frozen_string_literal: true + +# SPDX-License-Identifier: MIT +# .claude/skills/housekeeping/chapters.rb +# +# The probe's ninth check, `chapters` (phase 10's plan, Task 7; design R10 and addendum A10). +# +# A document asserting that a requirement ID is stated in a named `docs/product-spec/` chapter, +# where that chapter does not carry the ID. The unit is a CLAUSE, not a line: "A.md for X, Y; +# B.md for Z" pairs every ID with every chapter under a same-line rule, which is why a naive rule +# fires 23 times for 4 real defects (phase 10's Fact 6). So a line is split at `;`, each ID is +# associated with the NEAREST PRECEDING chapter reference in its clause -- or, for an ID run +# followed by "appears in", with the chapter that phrase introduces -- a range is expanded +# whether or not its endpoints are backticked, and a line whose two-line window carries a negation +# -- prose whose own subject is that the ID is NOT in that chapter -- is skipped. Appendix C +# carries every ID and can never be wrong about one, so it is exempt as a target. +# +# Two stated blind spots, printed in the check's own `GAPS` so a reader is never told the check +# saw something it did not: `continued_clause` (a chapter reference on the PRECEDING line is +# invisible to a line-oriented scanner -- 8c's design :70, `SEAM-15`, was caught by hand), and +# `dynamic_chapter_path` (a chapter named by an ellipsis such as `13-…md`, or by a variable, +# resolves to no file and is skipped). + +module Housekeeping + module Checks + # 9. A requirement ID attributed to a spec chapter that does not carry it. + class Chapters < Check + NAME = 'chapters' + PREFIXES = %w[SEAM HTTP IO BODY CTX PIPE RECOV RETRY REDIR AUTH PAGE SSE SERDE OBS CFG + TRANSPORT ASYNC XCUT NFR].freeze + ID = /\b(#{PREFIXES.join('|')})-(\d+)\b/ + # `SEAM-11`–`SEAM-15`, SEAM-11–SEAM-15, SEAM-11–15: backticks tolerated on either end, + # which is what hid 8a's `SEAM-13` from a range pattern written for bare text. + RANGE = /`?\b(#{PREFIXES.join('|')})-(\d+)`?\s*[–—]\s*`?(?:\1-)?(\d+)\b`?/ + CHAPTER = %r{docs/product-spec/(\d\d-[a-z0-9-]+\.md)} + BOUNDARY = ';' + # Prose whose subject is that the ID is NOT in the named chapter, over the line and the + # next one, because such a sentence routinely crosses a line break. + # A union of plain phrases, never an /x pattern: under /x the spaces inside a phrase are + # stripped and "appears nowhere" silently becomes "appearsnowhere" (found writing this). + NEGATION = Regexp.union( + 'appear nowhere', 'appears nowhere', 'harvested nowhere', 'appear in no', 'appears in no', + 'appendix C is their only', 'appendix C is its only', 'appendix C alone', + 'does not carry', 'do not carry', 'carries neither', 'carries none', 'carries no ', + 'carry none', 'no prose chapter', 'appendix-C row', 'unfollowable', 'from appendix C', + 'read out of appendix C', 'read out of **appendix C', 'neither ID appears', 'is not in', + 'are not in', 'not stated in', 'only normative statement', 'only prose home', + 'not appear in', 'does not appear', 'do not appear', 'never appear', "n't appear" + ).then { |union| Regexp.new(union.source, Regexp::IGNORECASE) } + # "X appears in ": the one verb that puts the chapter AFTER the IDs it is about. + # An ID run followed by it binds FORWARD, to the chapter the verb introduces, instead of to + # the nearest preceding one. Phase 10's review round 0 (R0-8) found the phrase listed as a + # negation, which silenced this -- the usual positive attribution -- to hide one line of the + # phase-5 segmentation design (:538-539) whose second run bound backward to the first + # run's chapter; binding forward is what that line needed. + FORWARD = /\bappears?\s+in\b/i + EXEMPT_TARGET = 'appendix-c-consolidated-normative-requirement-index.md' + # Documents that quote the pre-correction attributions ON PURPOSE, as the record of the + # defect this check exists for: phase 10's own design, plan and checklist. + EXEMPT_DOCUMENTS = %r{\Adocs/work/mvp/phase10/} + SCANNED = ['docs'].freeze + SKIPPED = %r{\Adocs/(?:product-spec|knowledge/harvested)/} + GAPS = %i[continued_clause dynamic_chapter_path].freeze + + def run(repo) + carried = chapter_ids(repo) + documents(repo).flat_map { |path| scan(path, repo.read(path).lines, carried) } + end + + # The pairs a single line asserts, [chapter, id], in clause order. Public so a test can + # read the clause scoping directly. + def self.pairs(line) + line.split(BOUNDARY).flat_map { |clause| clause_pairs(clause) } + end + + # A run of consecutive IDs binds to the nearest preceding chapter, unless the run is + # followed by FORWARD, when it binds to the chapter immediately after the verb (or to + # nothing, when the chapter is on the next line: the continued-clause gap). + def self.clause_pairs(clause) + list = tokens(clause) + chapter = nil + pairs = [] + index = 0 + while index < list.length + kind, value = list[index] + if kind == :chapter + chapter = value + index += 1 + elsif kind == :forward + index += 1 + else + run_end = index + run_end += 1 while run_end < list.length && list[run_end][0] == :id + target = run_target(list, run_end, chapter) + list[index...run_end].each { |_kind, id| pairs << [target, id] } if target + index = run_end + end + end + pairs + end + + def self.run_target(list, after, chapter) + return chapter unless list.dig(after, 0) == :forward + + list.dig(after + 1, 0) == :chapter ? list.dig(after + 1, 1) : nil + end + + # Chapters and IDs in textual order, every range expanded in place. + def self.tokens(clause) + found = [] + clause.scan(CHAPTER) { found << [Regexp.last_match.begin(0), :chapter, Regexp.last_match[1]] } + clause.scan(FORWARD) { found << [Regexp.last_match.begin(0), :forward, nil] } + covered = [] + clause.scan(RANGE) do + match = Regexp.last_match + covered << (match.begin(0)...match.end(0)) + (match[2].to_i..match[3].to_i).each { |n| found << [match.begin(0), :id, "#{match[1]}-#{n}"] } + end + clause.scan(ID) do + match = Regexp.last_match + next if covered.any? { |span| span.cover?(match.begin(0)) } + + found << [match.begin(0), :id, "#{match[1]}-#{match[2]}"] + end + found.sort_by.with_index { |(position, _kind, _value), index| [position, index] } + .map { |_position, kind, value| [kind, value] } + end + + private + + def chapter_ids(repo) + repo.tracked('docs/product-spec/*.md').to_h do |path| + [File.basename(path), repo.read(path).scan(ID).map { |prefix, n| "#{prefix}-#{n}" }.uniq] + end + end + + def documents(repo) + repo.tracked(*SCANNED).select { |path| path.end_with?('.md') } + .reject { |path| SKIPPED.match?(path) || EXEMPT_DOCUMENTS.match?(path) } + end + + def scan(path, lines, carried) + lines.each_with_index.flat_map do |line, index| + next [] if NEGATION.match?(line + lines.fetch(index + 1, '')) + + self.class.pairs(line).filter_map do |chapter, id| + next if chapter == EXEMPT_TARGET || !carried.key?(chapter) || carried[chapter].include?(id) + + act(path, index + 1, "attributes #{id} to docs/product-spec/#{chapter}, which does not " \ + "carry it (gaps: #{GAPS.join(', ')})") + end + end + end + end + end +end diff --git a/.claude/skills/housekeeping/probe.rb b/.claude/skills/housekeeping/probe.rb index a4ea042..e6a9e98 100644 --- a/.claude/skills/housekeeping/probe.rb +++ b/.claude/skills/housekeeping/probe.rb @@ -202,7 +202,8 @@ def strip_inline_code(text) end end - # The eight read-only checks. Each is a class with `name` and `run(repo) -> [Finding]`. + # The nine read-only checks. Each is a class with `name` and `run(repo) -> [Finding]`. The + # ninth, `chapters`, lives in chapters.rb (phase 10): one concern per file, as guard.rb. module Checks # Base class: supplies `name` from the subclass's `NAME` and a `Finding` factory. class Check @@ -732,11 +733,14 @@ def symlinks(repo) end end + require_relative 'chapters' + # Runs the checks and collects their findings. class Probe ALL = [ Checks::Inbox, Checks::Root, Checks::Claims, Checks::Readmes, - Checks::Links, Checks::Registers, Checks::Citations, Checks::GuardCheck + Checks::Links, Checks::Registers, Checks::Citations, Checks::GuardCheck, + Checks::Chapters ].freeze NAMES = ALL.map { |check| check::NAME }.freeze diff --git a/.claude/skills/housekeeping/test/probe_test.rb b/.claude/skills/housekeeping/test/probe_test.rb index 4b5f93c..86f366e 100644 --- a/.claude/skills/housekeeping/test/probe_test.rb +++ b/.claude/skills/housekeeping/test/probe_test.rb @@ -50,8 +50,8 @@ def test_a_clean_fixture_reports_nothing_on_every_check assert_empty on_fixture end - def test_check_names_are_the_eight_the_documentation_states - assert_equal %w[inbox root claims readmes links registers citations guard], Probe::NAMES + def test_check_names_are_the_nine_the_documentation_states + assert_equal %w[inbox root claims readmes links registers citations guard chapters], Probe::NAMES end # --- numerals -------------------------------------------------------------------------- diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 799f440..2281db2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,6 +32,9 @@ jobs: - run: bundle exec rake gates:cause_walk - run: bundle exec rake gates:bounded_map - run: bundle exec rake gates:seam_names + - run: bundle exec rake gates:ledger_audit + - run: bundle exec rake gates:spdx_rbs + - run: bundle exec rake gates:sole_parse - run: bundle exec rake gates:rbs_surface - run: bundle exec rake gates:sig_diff - run: bundle exec rake gates:surface_snapshot diff --git a/Rakefile b/Rakefile index 1de1c93..d335bc9 100644 --- a/Rakefile +++ b/Rakefile @@ -27,6 +27,9 @@ DEFAULT_GATES = %w[ gates:cause_walk gates:bounded_map gates:seam_names + gates:ledger_audit + gates:spdx_rbs + gates:sole_parse gates:clean_bundle gates:rbs_surface gates:sig_diff diff --git a/docs/deviations.md b/docs/deviations.md index 8e8913d..a35b782 100644 --- a/docs/deviations.md +++ b/docs/deviations.md @@ -6,34 +6,40 @@ the design document that promises it. Design §10 is frozen (see `docs/README.md frozen"); this file is where the routine, ongoing question "has entry N actually landed the way §10 says it would?" gets answered and kept current. -**Nothing has been built yet.** Every row below is therefore `design only — not yet built`: the -argument in §10 is recorded, but there is no shipped code to audit it against. A row moves to -`as-built: confirmed` (or is flagged otherwise) once a gem implementing it exists and has been -checked against source, the same way `docs/deviations.md` is used in the sibling Node SDK. +**Audited 2026-09-25 by phase 10, against the tree at the end of phase 10.** Every row was +re-derived from as-built source, never from another document: each Status cell names the gem path and +the constant that is its evidence, and `gates:ledger_audit` (phase 10's plan, Task 2) fails the build if +a row's IDs stop equalling its entry's, if two rows drift out of their entry's position, or if a verdict +cites no `gems/…` path that exists or names a `Dexpace::` constant that is not defined. The verdicts are +phase 10's design `R1`'s four: *confirmed*, *confirmed, narrower* (true, and narrower than the sentence -- +each names the amendment below that carries the narrowing), *contradicted* (none) and *unverifiable* +(none). The closing note's claim -- nothing unified, both retry stacks, both transport seams and both +bridges survive -- is confirmed too (`gems/dexpace-core/lib/dexpace/resilience/`, `Dexpace::Bridge::SyncOver` +and `AsyncOver`), and is phase 10's checklist row `RETRY-28`. ## The audit | # | Title | IDs touched | Status | |---|---|---|---| -| 1 | The byte-stream provider seam is retired; its behavioural contract is not | SEAM-3–SEAM-10, IO-1–IO-42, XCUT-23 | design only — not yet built | -| 2 | The canonical body is a duck type, not a nominal interface | SEAM-3, BODY-1, BODY-35 | design only — not yet built | -| 3 | The async pivot is a core-owned future rather than an ecosystem primitive | SEAM-1, SEAM-16, SEAM-17, ASYNC-1, ASYNC-2, NFR-11 | design only — not yet built | -| 4 | Cancellation is cooperative; the orphaned-response close moves to the producer | SEAM-13, SEAM-30, XCUT-1–XCUT-3, CFG-17, CFG-20, CFG-21, RETRY-23, TRANSPORT-3, ASYNC-5 | design only — not yet built | -| 5 | Two MUSTs are not satisfied, and a third holds vacuously | ASYNC-3, ASYNC-4, PIPE-33 | design only — not yet built | -| 6 | Suppressed exceptions are a core-owned trail, not a host facility | RECOV-12, PAGE-13, PAGE-15, SSE-29, SSE-30, SSE-36, RETRY-34, XCUT-9 | design only — not yet built | -| 7 | "Standard library" is narrowed to what is stable across the supported Ruby range | SEAM-1, NFR-1, AUTH-14, OBS-2 | design only — not yet built | -| 8 | Discovery's substrate is require-time self-registration, not classpath scanning | SEAM-5–SEAM-9, XCUT-23 | design only — not yet built | -| 9 | SEAM-10's multi-loader de-duplication is vacuous and is replaced by a version-skew guard | SEAM-10 | design only — not yet built | -| 10 | Runtime encapsulation of models is partially unachievable | HTTP-2/SEAM-29, HTTP-4, HTTP-7, HTTP-17, HTTP-18, IO-28/BODY-37, XCUT-18 | design only — not yet built | -| 11 | Read-only collection exposure is computed once, not wrapped per access | HTTP-5, XCUT-15 | design only — not yet built | -| 12 | One stream-ownership rule for bodies, resolving a reference inconsistency | BODY-8, SEAM-3, SEAM-20, SEAM-21 | design only — not yet built | -| 13 | The serde seam ships four encode profiles, two of which are one Ruby type | SEAM-20 | design only — not yet built | -| 14 | The serde witness is a class-object-and-combinator protocol, not a reflective type token | SEAM-22, SEAM-23, SERDE-5–SERDE-8, SERDE-16, SERDE-17 | design only — not yet built | -| 15 | The cross-origin redirect marker lives on the per-hop cursor, not on the request | REDIR-11, AUTH-29, PIPE-16 | design only — not yet built | -| 16 | The configuration chain keeps four tiers with a substituted third source | CFG-1, CFG-3, CFG-4, CFG-24, CFG-26, OBS-35 | design only — not yet built | -| 17 | The interruptible sleep is a cancellable queue wait, not `Kernel#sleep` | CFG-15, CFG-17, CFG-18, RETRY-26, XCUT-3, XCUT-13 | design only — not yet built | -| 18 | Platform-constant substitutions where Ruby has no constant | IO-9, BODY-32, SSE-11, RECOV-34 | design only — not yet built | -| 19 | The dead-code-survival gate is retargeted, not deleted | NFR-8, NFR-9 | design only — not yet built | +| 1 | The byte-stream provider seam is retired; its behavioural contract is not | SEAM-3–SEAM-10, IO-1–IO-42, XCUT-23 | as-built: confirmed, narrower -- the apparatus is absent (`gems/dexpace-core/lib/dexpace/io.rb` defines `Dexpace::IO::Buffer`, `BufferedSource`, `BufferedSink`, `TeeSink` and no registration entry point; the only registries are `Dexpace::Transport::REGISTRY`, `Dexpace::AsyncTransport::REGISTRY` and `Dexpace::Serde::REGISTRY`) and the contract is 3a's `gems/dexpace-core/lib/dexpace/io/`; narrower where XCUT-23's third surviving instance is the async transport seam, not an executor (C17) | +| 2 | The canonical body is a duck type, not a nominal interface | SEAM-3, BODY-1, BODY-35 | as-built: confirmed -- `Dexpace::IO::BufferedSource.over` (`gems/dexpace-core/lib/dexpace/io/buffered_source.rb:91`) takes no ownership, measured: closing the source leaves the upstream open, where `.wrapping` (`:48`) closes it; every body yields BINARY chunks from `#each` (`gems/dexpace-core/lib/dexpace/http/body.rb`) | +| 3 | The async pivot is a core-owned future rather than an ecosystem primitive | SEAM-1, SEAM-16, SEAM-17, ASYNC-1, ASYNC-2, NFR-11 | as-built: confirmed -- the pivot is `Dexpace::Async::Future`, `Dexpace::Async::Completer` and `Dexpace::Async::Settlement` (`gems/dexpace-core/lib/dexpace/async/future.rb`), core-owned; `gates:rbs_surface` finds no foreign async type in any shipped signature | +| 4 | Cancellation is cooperative; the orphaned-response close moves to the producer | SEAM-13, SEAM-30, XCUT-1–XCUT-3, CFG-17, CFG-20, CFG-21, RETRY-23, TRANSPORT-3, ASYNC-5 | as-built: confirmed, narrower -- `Dexpace::Async::Completer#fulfil` closes an undelivered result (`gems/dexpace-core/lib/dexpace/async/completer.rb:96`); the socket-closing hooks are the transports' (`gems/dexpace-transport-net_http/lib/dexpace/transport/net_http/response_pump.rb:120`, `gems/dexpace-transport-async_http/lib/dexpace/transport/async_http/exchange.rb:134`) and the pool owns no socket, so the mitigation names a transport, not an adapter (C12) | +| 5 | Two MUSTs are not satisfied, and a third holds vacuously | ASYNC-3, ASYNC-4, PIPE-33 | as-built: confirmed as admitted, not re-opened -- `ASYNC-3` waived by ID as would-fail in `gems/dexpace-async-thread/test/dexpace/async/thread/conformance_test.rb`, `PIPE-33`'s interrupt clause unmet, `ASYNC-4` vacuous; the gap is `docs/first-release.md` § Unsatisfied MUSTs | +| 6 | Suppressed exceptions are a core-owned trail, not a host facility | RECOV-12, PAGE-13, PAGE-15, SSE-29, SSE-30, SSE-36, RETRY-34, XCUT-9 | as-built: confirmed, narrower -- the trail is `Dexpace::Suppressible` (`gems/dexpace-core/lib/dexpace/suppressible.rb:46`), included by `Dexpace::Error` and extended onto any exception by `Dexpace.attach_suppressed`, not a method of `Dexpace::Error` itself (C14); one cycle-safe walk, `Dexpace.each_cause` over `compare_by_identity` (`gems/dexpace-core/lib/dexpace/each_cause.rb:48`), kept alone by `gates:cause_walk` | +| 7 | "Standard library" is narrowed to what is stable across the supported Ruby range | SEAM-1, NFR-1, AUTH-14, OBS-2 | as-built: confirmed -- `gems/dexpace-core/dexpace-core.gemspec` declares no dependency; Basic is `pack("m0")` (`gems/dexpace-core/lib/dexpace/auth/basic_handler.rb:45`); no `require "logger"` in core; `gates:require_allowlist` and `gates:clean_bundle` green on every row | +| 8 | Discovery's substrate is require-time self-registration, not classpath scanning | SEAM-5–SEAM-9, XCUT-23 | as-built: confirmed, narrower -- adapters self-register at require time (`gems/dexpace-transport-net_http/lib/dexpace/transport/net_http.rb`) into `Dexpace::Registry`'s five branches (`gems/dexpace-core/lib/dexpace/registry.rb`); the permitted instrumentation auto-activation exists nowhere (`gems/dexpace-core/test/dexpace/seam_surface_test.rb`), so the restriction holds by absence | +| 9 | SEAM-10's multi-loader de-duplication is vacuous and is replaced by a version-skew guard | SEAM-10 | as-built: confirmed -- vacuous with a replacement: `Dexpace::Registry#register(key, factory, core:)` (`gems/dexpace-core/lib/dexpace/registry.rb:123`) is the version-skew guard; `gates:single_instance` green | +| 10 | Runtime encapsulation of models is partially unachievable | HTTP-2/SEAM-29, HTTP-4, HTTP-7, HTTP-17, HTTP-18, IO-28/BODY-37, XCUT-18 | as-built: confirmed, narrower -- `Dexpace::Request.send(:new, ...)` bypasses `.build` but not validation: it runs the validating `#initialize` (`gems/dexpace-core/lib/dexpace/http/request.rb:51`), so a GET with a body raises HTTP-7 and a non-`Headers` headers value is refused (measured on 4.0.6 and 3.2.11); the residual holes are `Dexpace::Request.allocate`, which yields a Request whose four members are nil, and duck typing, and the mitigation for both is that both transports re-validate header names and outbound values at the wire (`gems/dexpace-transport-net_http/lib/dexpace/transport/net_http/request_mapper.rb:58`, `gems/dexpace-transport-async_http/lib/dexpace/transport/async_http/request_mapper.rb:50`) (C19) | +| 11 | Read-only collection exposure is computed once, not wrapped per access | HTTP-5, XCUT-15 | as-built: confirmed -- `Dexpace::Model.own` (`gems/dexpace-core/lib/dexpace/model.rb`) deep-freezes a copy it owns; a returned header value list refuses mutation (measured); phase 10 repaired its default-proc escape | +| 12 | One stream-ownership rule for bodies, resolving a reference inconsistency | BODY-8, SEAM-3, SEAM-20, SEAM-21 | as-built: confirmed, narrower -- a body closes only what it opened (`gems/dexpace-core/lib/dexpace/http/body/stream_body.rb`), ownership-on-wrap is `Dexpace::IO::BufferedSource.wrapping`'s, a codec closes nothing (`gems/dexpace-serde-json/lib/dexpace/serde/json/codec.rb`); §10.12 attributes the I/O rule to SEAM-3 where the live ID is IO-6 (C3) | +| 13 | The serde seam ships four encode profiles, two of which are one Ruby type | SEAM-20 | as-built: confirmed, narrower -- all four profiles ship (`gems/dexpace-serde-json/lib/dexpace/serde/json/codec.rb:145`), `dump_string` tagged UTF-8 and `dump_bytes` ASCII-8BIT (measured); each takes the value first, which §10.13 does not spell (C18) | +| 14 | The serde witness is a class-object-and-combinator protocol, not a reflective type token | SEAM-22, SEAM-23, SERDE-5–SERDE-8, SERDE-16, SERDE-17 | as-built: confirmed -- `Dexpace::Serde.witness!` over `Dexpace::Serde::WITNESS_METHOD` (`gems/dexpace-core/lib/dexpace/serde/witness.rb:17`); the hierarchy is `Dexpace::Serde::Error` with `Dexpace::Serde::SerializationError` and `Dexpace::Serde::DeserializationError` under it; no compile-time refusal, as admitted | +| 15 | The cross-origin redirect marker lives on the per-hop cursor, not on the request | REDIR-11, AUTH-29, PIPE-16 | as-built: confirmed -- `cursor.fork(state: { cross_origin: ... })` on every drive (`gems/dexpace-core/lib/dexpace/redirect/step.rb:166`), read only through `cursor.state` (`gems/dexpace-core/lib/dexpace/auth/step.rb:118`); `Dexpace::Pipeline::Cursor` has no state setter | +| 16 | The configuration chain keeps four tiers with a substituted third source | CFG-1, CFG-3, CFG-4, CFG-24, CFG-26, OBS-35 | as-built: confirmed -- `Dexpace::Configuration#string` (`gems/dexpace-core/lib/dexpace/configuration.rb:115`) reads override, environment, property, default; the property tier is `Dexpace.configure` (`gems/dexpace-core/lib/dexpace/config.rb:43`); `Dexpace::Proxy.resolve` reads the same chain; phase 10 bounded the chain a repeated configure grew | +| 17 | The interruptible sleep is a cancellable queue wait, not `Kernel#sleep` | CFG-15, CFG-17, CFG-18, RETRY-26, XCUT-3, XCUT-13 | as-built: confirmed -- `Dexpace::Clock#sleep` is a per-call queue pop woken by the token (`gems/dexpace-core/lib/dexpace/clock.rb:113`); phase 10 refused NaN and Complex in its guard and in `Dexpace::Async.delay` | +| 18 | Platform-constant substitutions where Ruby has no constant | IO-9, BODY-32, SSE-11, RECOV-34 | as-built: confirmed, narrower -- `Dexpace::IO::MAX_MATERIALIZED_BYTES` (`gems/dexpace-core/lib/dexpace/io.rb:27`), `Dexpace::SSE::MAX_RETRY_MS` (`gems/dexpace-core/lib/dexpace/sse.rb:58`), `Dexpace::Resilience::Policy::MAX_DURATION_NANOSECONDS` (`gems/dexpace-core/lib/dexpace/resilience/policy.rb:77`); 7b added `Dexpace::SSE::MAX_LINE_BYTES` and `Dexpace::SSE::MAX_EVENT_BYTES`, which the entry does not list (C15) | +| 19 | The dead-code-survival gate is retargeted, not deleted | NFR-8, NFR-9 | as-built: confirmed -- vacuous by its own text; retargeted at the require-allowlist audit and the clean-bundle run (`gems/dexpace-core/dexpace-core.gemspec`, `tasks/gates.rake`), both green on every row | Numbering follows §10's own list order and is not renumbered as entries are confirmed built; a row's number is a citation, the same as an item ID in `docs/first-release.md`, the one other register @@ -112,7 +118,7 @@ of `async-http`'s dependency closure, and appendix C's `SSE-19` row, the first e normative specification. Both are below, dated. **What this section adds up to, because the arithmetic was wrong once and is worth stating: thirteen -amendments in eleven notes.** Phase 10's design numbers them `C1`–`C13` and its plan, Task 17 writes each +amendments in eleven notes** (as of 2026-09-13; phase 10's table above completes the set at nineteen). Phase 10's design numbers them `C1`–`C13` and its plan, Task 17 writes each one's replacement text; the mapping is fixed here so neither document can drift from the other. Seven of the eleven notes below the retirement paragraph carry `C1`, `C3`, `C4`, `C5`, `C6`, `C7` and `C10`; the two dated 2026-09-13 carry `C8` and `C11`; and **the two notes above this paragraph, dated 2026-09-12, are @@ -126,6 +132,40 @@ condition is the `docs/first-release.md` blocker phase 10 files, and that blocker's enumeration names all thirteen — an amendment recorded here with no line in that blocker is exactly the outcome this section exists to prevent. +**The amendment set, written out -- phase 10, 2026-09-25. This is the section's closing condition.** +Phase 10 (its plan, Task 17) verified every code half below in as-built source before writing a +replacement, so applying each amendment is editorial: quote the sentence, paste the replacement. The set +is **`C1`-`C19`**: the thirteen of phase 10's design, `C14` (filed by phase 4b in `docs/first-release.md` +and missing here until now), and five the as-built audit added (`C15`-`C19`). The notes below this table +stand as each amendment's measured record; `C2`, `C9` and `C14`-`C19` had none and are written in the +table alone. Its closing condition is the `docs/first-release.md` blocker "The fourteen recorded +corrections ... applied", which now enumerates all nineteen and names the one further human act this set +cannot transcribe -- **the consolidation of every phase's as-built Deviation Ledger rows into §10** -- +and whose alternative form is that the release notes name the sentences, and the ledgers, a reader +should not trust. After that blocker is answered this section is a record, not a holding area. + +| # | File and sentence | Replacement | Code half, verified 2026-09-25 | +|---|---|---|---| +| C1 | §3.1 (`03-…md:78`): "applies the media type's charset via `String#encode(invalid: :replace, undef: :replace)`" | "retags the BINARY bytes to the media type's charset -- UTF-8 when it is absent or unknown -- and then transcodes with both encodings named, `#encode(target, invalid: :replace, undef: :replace)`" | `Dexpace::Response#body_string` (`gems/dexpace-core/lib/dexpace/http/response.rb`): `MediaType#charset`, `#read_string(encoding)`, then a named-target `#encode` | +| C2 | §4 (`04-…md:21`): "`Request`, `Response`, `Headers`, `Query`, `RequestOptions` and `Configuration` get real mutable `Builder` classes" | add "and the multipart body (`MultipartBody#new_builder`, `MultipartBody::Builder`), which **HTTP-3** names" | `Dexpace::MultipartBody::Builder` (`gems/dexpace-core/lib/dexpace/http/body/multipart_body.rb`) and its non-aliasing test | +| C3 | §3.1 (`03-…md:83`): "closes that `IO` (**SEAM-3**)"; §10.12 leans on the same attribution | cite **IO-6** (appendix C its only statement) beside or instead of SEAM-3 | `Dexpace::IO::BufferedSource.wrapping` closes the IO, measured | +| C4 | §8.1's code block (`08-…md:31`): "`#tag(key, value)`" | delete the method, or name the requirement it serves and its precedence against **OBS-5**'s three sources | `Dexpace::Instrumentation::Event` ships `#field`, `#event`, `#cause`, `#emit` and no `#tag` (5b's P5-18) | +| C5 | §3.2 (`03-…md:167-172`): the block-scoped `read_body` "satisfied literally" | "a per-response producer `Thread` over a `Thread::SizedQueue(1)`, drained through a `#readpartial`-shaped reader, keeps the body lazy and closable (**SEAM-11**, **TRANSPORT-25**); the block form buffers the body and kills the socket, measured" | `gems/dexpace-transport-net_http/lib/dexpace/transport/net_http/response_pump.rb` (8a's P8-1) | +| C6 | §3.2 (`03-…md:172-173`), §11.18 and §12's `TRANSPORT` row: `Net::HTTP` "retries nothing on its own", "has no resend hook", TRANSPORT-2 vacuous | "`Net::HTTP#max_retries` defaults to 1 and re-sends idempotent requests; the adapter sets it to 0, so TRANSPORT-2 is satisfied by construction and leaves §12's vacuous count" | `http.max_retries = 0` in `gems/dexpace-transport-net_http/lib/dexpace/transport/net_http/adapter.rb` | +| C7 | §12's `TRANSPORT` row, both directions | add TRANSPORT-14 to the adapter-scoped list (unreachable on `async-http`); record TRANSPORT-8 as satisfied on the async adapter and drop it from the vacuous count | the named waiver `WAIVED = %w[TRANSPORT-14 TRANSPORT-27]` and the "unreachable here" measurement in `gems/dexpace-transport-async_http/test/dexpace/transport/async_http/conformance_test.rb`; 8c's `Async::Cancel`/`Async::TimeoutError` discrimination | +| C8 | §8.3 (`08-…md:216`): "forbidden in every gem in this repository" | "forbidden in code this repository writes, which `Dexpace/NoThreadInterrupt` enforces over every gem's `lib/`. A dependency may use them: `net-http`'s connect phase is `Timeout.timeout(@open_timeout, Net::OpenTimeout)` on every supported Ruby (0.9.1 also carries `TCPSocket.open(open_timeout:)`), the first such call starts a process-wide thread a host that counts threads sees once, and the `async-http` closure has no `Timeout.timeout`, seven `Fiber#raise` sites at scheduler checkpoints, one `Thread.current.raise` and one `Thread#kill` on an unreachable helper" | none owed; the cop stands | +| C9 | §9.3 (`09-…md:71`): "it ships with the interpreter as a default gem" | "it ships with the interpreter as a **bundled** gem, needing an explicit bundle entry under Bundler -- `VERSIONS` carries it, pinned `~> 5.25` -- and the same argument follows" | `VERSIONS`' `tool minitest ~> 5.25` row and its reason beside it (`VERSIONS:22-28`), read by the root `Gemfile` | +| C10 | §9.3 (`09-…md:111`): the waiver "listing the requirement ID", the report's unit unstated | add "and the report's unit is the same requirement ID: one assertion per ID, an appendix-B item a many-to-one view whose status is the worst of its assertions" | `Dexpace::Conformance::Report`, one `Result` per assertion, and `gems/dexpace-conformance/APPENDIX_B.md` | +| C11 | appendix C's `SSE-19` and `OBS-29` rows, against their chapters | each row either carries the other's clause or says it is partial -- a recommendation to the specification author, in §11's idiom | 7b's configurable line cap (`Dexpace::SSE::MAX_LINE_BYTES`); 5c's documented `OBS-29` contract and 6a's emitted per-attempt group | +| C12 | §10.5 (`10-…md:52`): "`Completer#on_cancel` lets an adapter shorten that" | "lets a **transport** shorten that by closing its socket under the read; the thread-pool adapter owns no socket" | the transports' hooks at `net_http/response_pump.rb:120` and `async_http/exchange.rb:134` | +| C13 | §12's `PAGE` row (`12-…md:36`) | add "`PAGE-15`'s wrapping clause is vacuous by a false antecedent (`7c P7-1`)" beside PAGE-35 | 7c's `P7-1` | +| C14 | §10 item 6 (`10-…md:60`): "`Dexpace::Error#suppressed` supplies the list"; §5.2's placement | "`Dexpace::Suppressible` supplies the list -- `Dexpace::Error` includes it and `Dexpace.attach_suppressed` extends it onto any other exception" | `gems/dexpace-core/lib/dexpace/suppressible.rb` (4b's P4-12) | +| C15 | §10 item 18 (`10-…md:119-124`): the named substitutions | add "`SSE::MAX_LINE_BYTES` (1 MiB) and `SSE::MAX_EVENT_BYTES` (8 MiB), SSE-19's two rejecting caps, distinct from SSE-11's `MAX_RETRY_MS`" | `gems/dexpace-core/lib/dexpace/sse.rb:38`, `:48` (7b's P7-21) | +| C16 | §9's gate table (`09-…md:7-24`) | add the seven gates later phases built -- `gates:serde_boundary` (7b), `gates:cause_walk`, `gates:bounded_map`, `gates:seam_names` (phase 9, A4-A7), `gates:ledger_audit`, `gates:spdx_rbs`, `gates:sole_parse` (phase 10, A8, A9, A11) -- and the probe's `chapters` check (A10); the NFR-13 row gains "and `gates:spdx_rbs` over every shipped `.rbs`" | `DEFAULT_GATES` in the root `Rakefile`, twenty-four names | +| C17 | §11.8 (`11-…md:32`): XCUT-23's three instances "(transport, serde, executor)" | "(transport, async transport, serde)" -- there is no executor registry | `Dexpace::AsyncTransport::REGISTRY` (`gems/dexpace-core/lib/dexpace/async_transport.rb:22`) | +| C18 | §10 item 13 (`10-…md:93`): "`#dump_to(sink)` and `#dump_into(buffer, offset:)`" | "`#dump_to(value, sink)` and `#dump_into(value, buffer, offset: 0)`" (and `#dump_string(value)`, `#dump_bytes(value)`) | `gems/dexpace-serde-json/lib/dexpace/serde/json/codec.rb:145-200` | +| C19 | §4 (`04-…md:45`): "`Req.send(:new, ...)` reaches the generated constructor anyway"; §10 item 10 (`10-…md:78-79`): "`private_class_method :new` is bypassable by `send` by design" | "`send(:new, ...)` bypasses `.build` but not validation -- every model's `#initialize` override validates and calls `super`, so it re-runs HTTP-4, HTTP-7 and the member type checks; what remains open is `.allocate`, which yields an instance whose members are all nil, and duck typing" | `Dexpace::Request#initialize` (`gems/dexpace-core/lib/dexpace/http/request.rb:51`); `Request.send(:new, ...)` with a GET and a body raises HTTP-7, and `Request.allocate` returns all-nil members, measured on 4.0.6 and 3.2.11 (phase 10's review round 0, R0-6) | + **2026-09-08 — against design §3.1's decode recipe. Established by phase 3b's design.** The frozen sentence fixes one decode boundary as `Response#body_string`, "which applies the media type's charset via `String#encode(invalid: :replace, undef: :replace)` and falls back to UTF-8 when absent or unknown". The rule diff --git a/gems/dexpace-async-thread/sig/dexpace/async/thread.rbs b/gems/dexpace-async-thread/sig/dexpace/async/thread.rbs index 75d6cc2..f4e4538 100644 --- a/gems/dexpace-async-thread/sig/dexpace/async/thread.rbs +++ b/gems/dexpace-async-thread/sig/dexpace/async/thread.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Async module Thread diff --git a/gems/dexpace-async-thread/sig/dexpace/async/thread/pool.rbs b/gems/dexpace-async-thread/sig/dexpace/async/thread/pool.rbs index 5d6eab6..ef16696 100644 --- a/gems/dexpace-async-thread/sig/dexpace/async/thread/pool.rbs +++ b/gems/dexpace-async-thread/sig/dexpace/async/thread/pool.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Async module Thread diff --git a/gems/dexpace-async-thread/sig/dexpace/async/thread/rejected_error.rbs b/gems/dexpace-async-thread/sig/dexpace/async/thread/rejected_error.rbs index dbb81d8..efe39de 100644 --- a/gems/dexpace-async-thread/sig/dexpace/async/thread/rejected_error.rbs +++ b/gems/dexpace-async-thread/sig/dexpace/async/thread/rejected_error.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Async module Thread diff --git a/gems/dexpace-async-thread/sig/dexpace/async/thread/timer.rbs b/gems/dexpace-async-thread/sig/dexpace/async/thread/timer.rbs index 27435e6..eebb032 100644 --- a/gems/dexpace-async-thread/sig/dexpace/async/thread/timer.rbs +++ b/gems/dexpace-async-thread/sig/dexpace/async/thread/timer.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Async::Thread::Timer is a private_constant and not public API: this declaration exists # because the `async_thread` Steep target checks every file under lib/ and needs the class # declared to type Pool#delay's and #release's call sites. RBS has no visibility for a constant, diff --git a/gems/dexpace-async-thread/sig/dexpace/async/thread/version.rbs b/gems/dexpace-async-thread/sig/dexpace/async/thread/version.rbs index fbd9d20..d952774 100644 --- a/gems/dexpace-async-thread/sig/dexpace/async/thread/version.rbs +++ b/gems/dexpace-async-thread/sig/dexpace/async/thread/version.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Async module Thread diff --git a/gems/dexpace-conformance/lib/dexpace/conformance/packaging_case.rb b/gems/dexpace-conformance/lib/dexpace/conformance/packaging_case.rb index 5178ea9..3e1ffe2 100644 --- a/gems/dexpace-conformance/lib/dexpace/conformance/packaging_case.rb +++ b/gems/dexpace-conformance/lib/dexpace/conformance/packaging_case.rb @@ -25,6 +25,11 @@ module Conformance # available without one -- which is what keeps this gem's declared dependency set at # dexpace-core and nothing else. class PackagingCase + # Words a gem name spells lower-case that its constant path spells as an acronym, the two + # this port's own gems use (NetHTTP, AsyncHTTP, Serde::JSON). + ACRONYMS = %w[http json].freeze + private_constant :ACRONYMS + # Resolves a gem name to its installed specification, or nil when it is not installed. # # `Gem::LoadError` is named explicitly and is the WHOLE point of the rescue: RubyGems raises @@ -146,9 +151,19 @@ def resolve_constant(path) end end + # The gem-name -> constant-path rule, segment for segment, with the two exceptions this + # port's own six gems need (phase 10, phase 9's aggregate-run finding): a `-core` gem is its + # root namespace (`dexpace-core` -> `Dexpace`), and a word in ACRONYMS is upper-cased + # (`net_http` -> `NetHTTP`, `json` -> `JSON`). Before it, four of the six needed the + # `constants:` override; a gem this rule still misses keeps that override. def default_constant_path(name) - name.split("-").map { |segment| segment.split("_").map(&:capitalize).join }.join("::") + segments = name.split("-") + segments = segments.first(1) if segments.size == 2 && segments.last == "core" + segments.map { |segment| segment.split("_").map { |word| constant_word(word) }.join } + .join("::") end + + def constant_word(word) = ACRONYMS.include?(word) ? word.upcase : word.capitalize end end end diff --git a/gems/dexpace-conformance/lib/dexpace/conformance/runner.rb b/gems/dexpace-conformance/lib/dexpace/conformance/runner.rb index fe47817..36ef6c5 100644 --- a/gems/dexpace-conformance/lib/dexpace/conformance/runner.rb +++ b/gems/dexpace-conformance/lib/dexpace/conformance/runner.rb @@ -13,9 +13,9 @@ module Conformance # one place. Four suites would otherwise carry four copies of one status loop, which is # §11.12's "four reference sync/async drifts" reappearing inside the port's own suite. # - # TransportSuite (phase 8a) predates this and is deliberately NOT refactored onto it: phase 8 - # owns that file and phase 9 reports rather than refactors (design R6, P9-10). The residue is - # real -- two status-deciding paths in one gem -- and is stated rather than hidden. + # TransportSuite (phase 8a) predates this and phase 9 deliberately left it on its own loop + # (design R6, P9-10); phase 10, the first phase owning every gem, folded it onto this one, so + # every suite in the gem now decides the five statuses here. module Runner extend self diff --git a/gems/dexpace-conformance/lib/dexpace/conformance/transport_suite.rb b/gems/dexpace-conformance/lib/dexpace/conformance/transport_suite.rb index 7b600dd..c2593c6 100644 --- a/gems/dexpace-conformance/lib/dexpace/conformance/transport_suite.rb +++ b/gems/dexpace-conformance/lib/dexpace/conformance/transport_suite.rb @@ -6,6 +6,7 @@ require_relative "assertion" require_relative "result" require_relative "report" +require_relative "runner" require_relative "transport_case" require_relative "transport_suite/checks" require_relative "transport_suite/outbound" @@ -72,42 +73,36 @@ def assertions # the native client refuses the head before a response exists -- so the default is empty # and 8a's plain `waived:` rendering stands. # @return [Report] + # + # Phase 10 folded this onto Runner (P9-10's residue: "a future change to the five statuses + # must be made twice"), so the five statuses are decided in one place for every suite in + # the gem. What Runner had no seam for is the per-assertion teardown, and it is supplied + # here without widening Runner: the fresh TransportCase is built inside the invocation, and + # the `around` Runner is handed tears it down in an `ensure` after the driver's own wrapper + # returns -- clause 9 unchanged, the reactor an async driver opens still enclosing the whole + # assertion. One observable change, recorded: a :failed detail now carries Runner's + # "(expected …, got …)" suffix after the message. def run(build:, borrow: nil, waive: [], around: nil, settle: nil, wire: nil, assertions: self.assertions, accepted_vacuous: {}, would_fail: []) - results = assertions.map do |assertion| - if assertion.ids.intersect?(waive) - Result.build(assertion: assertion, status: :waived) - else - run_one(assertion, build: build, borrow: borrow, around: around, settle: settle, - wire: wire,) - end + cases = [] #: Array[TransportCase] + subject = lambda do + TransportCase.new(build: build, borrow: borrow, + settle: settle || TransportCase::DEFAULT_SETTLE, + wire: wire || TransportCase::DEFAULT_WIRE,).tap { |kase| cases << kase } end - Report.new(results, preamble: PREAMBLE, accepted_vacuous: accepted_vacuous, - would_fail: would_fail,) + Runner.run(assertions, waive: waive, around: tearing_down(around, cases), + preamble: PREAMBLE, accepted_vacuous: accepted_vacuous, + would_fail: would_fail, &subject) end private - def run_one(assertion, build:, borrow:, around:, settle:, wire:) - kase = TransportCase.new( - build: build, borrow: borrow, - settle: settle || TransportCase::DEFAULT_SETTLE, wire: wire || TransportCase::DEFAULT_WIRE, - ) - begin - # Clause 9: the runner INVOKES the assertion, so `around` can put a block around the - # whole of it -- the reactor an async driver opens here is the one the body reads a - # streamed response inside. - around ? around.call { assertion.call(kase) } : assertion.call(kase) - Result.build(assertion: assertion, status: :passed) - rescue Vacuous => error - Result.build(assertion: assertion, status: :vacuous, detail: error.reason) - rescue Failure => error - Result.build(assertion: assertion, status: :failed, detail: error.message) - rescue ::StandardError => error - Result.build(assertion: assertion, status: :error, - detail: "#{error.class}: #{error.message}",) + # Clause 9's wrapper, with every case built inside it torn down afterwards whatever happened. + def tearing_down(around, cases) + lambda do |&invocation| + around.nil? ? invocation.call : around.call(&invocation) ensure - kase.teardown + cases.pop&.teardown until cases.empty? end end diff --git a/gems/dexpace-conformance/sig/dexpace/conformance.rbs b/gems/dexpace-conformance/sig/dexpace/conformance.rbs index 37af212..f159e98 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Conformance end diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/aggregate.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/aggregate.rbs index 9b6530a..c716326 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/aggregate.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/aggregate.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Conformance # One report over every suite in a run; the caller 8a deferred Report#to_h for. diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/allocations.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/allocations.rbs index 87612f8..30f06af 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/allocations.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/allocations.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Conformance # OBS-25's per-call allocation measurement, two loops agreeing twice (5b's R8). diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/assertion.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/assertion.rbs index 85d9799..5ee3c3a 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/assertion.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/assertion.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Conformance # One conformance check as a value; the body is typed against no named interface because a diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/borrowed_pair.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/borrowed_pair.rbs index d052432..9681e30 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/borrowed_pair.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/borrowed_pair.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Conformance # Suite contract 4a: the borrowing transport and the adapter's own "still usable?" probe. diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/check.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/check.rbs index 8f9ef6c..0d744c2 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/check.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/check.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Conformance # The one assertion primitive every phase-9 suite body uses. diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/codec_case.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/codec_case.rbs index 643baad..f07eb2c 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/codec_case.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/codec_case.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Conformance # What a CodecSuite assertion receives: a codec factory, the driver's witness and its source diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/codec_suite.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/codec_suite.rbs index 117a185..a700d53 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/codec_suite.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/codec_suite.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Conformance # Appendix B.3's seam half: SEAM-20 with SERDE-3, and SERDE-9. diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/codec_suite/seam.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/codec_suite/seam.rbs index def9413..652485f 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/codec_suite/seam.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/codec_suite/seam.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Conformance::CodecSuite::Seam is a private_constant (see seam.rb); declared because the # conformance Steep target checks every file under lib/. module Dexpace diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/executor_case.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/executor_case.rbs index 3c8c74c..52e3256 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/executor_case.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/executor_case.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Conformance # What an ExecutorSuite assertion receives: an executor factory, an optional borrowing entry diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/executor_suite.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/executor_suite.rbs index 5e92f7f..5c5eddd 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/executor_suite.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/executor_suite.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Conformance # Appendix B.7's lifecycle half, and the harness half of SEAM-25's lifecycle event. diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/executor_suite/lifecycle.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/executor_suite/lifecycle.rbs index 9b9efbf..7e35e15 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/executor_suite/lifecycle.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/executor_suite/lifecycle.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Conformance::ExecutorSuite::Lifecycle is a private_constant (see lifecycle.rb); # declared because the conformance Steep target checks every file under lib/. module Dexpace diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/executor_suite/shutdown.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/executor_suite/shutdown.rbs index 7bc614c..7b9c7df 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/executor_suite/shutdown.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/executor_suite/shutdown.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Conformance::ExecutorSuite::Shutdown is a private_constant (see shutdown.rb); declared # because the conformance Steep target checks every file under lib/. module Dexpace diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/failure.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/failure.rbs index e45a0c4..efbfe4e 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/failure.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/failure.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Conformance # Design §9.3's assertion failure: a test result, never a Dexpace::Error (P8-8). diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/invariant_case.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/invariant_case.rbs index 2af60df..957a6fd 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/invariant_case.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/invariant_case.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Conformance # What an InvariantSuite assertion receives: the loaded core module and the driver's factories diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite.rbs index d2bcbfd..118b1a5 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Conformance # Appendix B.8: twenty-eight assertions over all twenty-four XCUT IDs (design P9-8). diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/classification.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/classification.rbs index af1ed1e..cce9c0e 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/classification.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/classification.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Conformance::InvariantSuite::Classification is a private_constant (see # classification.rb); declared because the conformance Steep target checks every file under lib/. module Dexpace diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/concurrency.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/concurrency.rbs index 961f83e..be81554 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/concurrency.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/concurrency.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Conformance::InvariantSuite::Concurrency is a private_constant (see concurrency.rb); # declared because the conformance Steep target checks every file under lib/. module Dexpace diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/credentials.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/credentials.rbs index e2c27cf..d087cb5 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/credentials.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/credentials.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Conformance::InvariantSuite::Credentials is a private_constant (see credentials.rb); # declared because the conformance Steep target checks every file under lib/. module Dexpace diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/header_syntax.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/header_syntax.rbs index 8d37808..8f7b389 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/header_syntax.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/header_syntax.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Conformance::InvariantSuite::HeaderSyntax is a private_constant (see header_syntax.rb); # declared because the conformance Steep target checks every file under lib/. module Dexpace diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/memory.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/memory.rbs index 571d954..170b3ed 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/memory.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/memory.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Conformance::InvariantSuite::Memory is a private_constant (see memory.rb); declared # because the conformance Steep target checks every file under lib/. module Dexpace diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/models.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/models.rbs index 0b033e9..3643dda 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/models.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/models.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Conformance::InvariantSuite::Models is a private_constant and not public API: this # declaration exists because the conformance Steep target checks every file under lib/. RBS has no # visibility for a constant, so the privacy lives in models.rb alone. diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/outcomes.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/outcomes.rbs index 88cd619..8f4d15c 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/outcomes.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/outcomes.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Conformance::InvariantSuite::Outcomes is a private_constant (see outcomes.rb); declared # because the conformance Steep target checks every file under lib/. module Dexpace diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/resolution.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/resolution.rbs index a7d698d..422c4a4 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/resolution.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/resolution.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Conformance::InvariantSuite::Resolution is a private_constant (see resolution.rb); # declared because the conformance Steep target checks every file under lib/. module Dexpace diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/security.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/security.rbs index 96a1eed..4a4f00b 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/security.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/security.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Conformance::InvariantSuite::Security is a private_constant (see security.rb); declared # because the conformance Steep target checks every file under lib/. module Dexpace diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/sharing.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/sharing.rbs index e32a7ac..602bf01 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/sharing.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/sharing.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Conformance::InvariantSuite::Sharing is a private_constant (see sharing.rb); declared # because the conformance Steep target checks every file under lib/. module Dexpace diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/taxonomy.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/taxonomy.rbs index 88eae9d..8d3d286 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/taxonomy.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/invariant_suite/taxonomy.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Conformance::InvariantSuite::Taxonomy is a private_constant (see taxonomy.rb); declared # because the conformance Steep target checks every file under lib/. module Dexpace diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/levels.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/levels.rbs index 4b77032..3c41c44 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/levels.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/levels.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Conformance # Generated from appendix C by tools/requirement_levels.rb; what lets a Report tell a diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/minitest_driver.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/minitest_driver.rbs index a36d3b9..37163b8 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/minitest_driver.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/minitest_driver.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Conformance # The thin Minitest driver: extended into a consumer's test class, whose `define_method`, diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/packaging_case.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/packaging_case.rbs index c9410e6..c6ed01e 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/packaging_case.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/packaging_case.rbs @@ -1,8 +1,11 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Conformance # What a PackagingSuite assertion receives: gem NAMES resolved through RubyGems, which is # what NFR-1's "published dependency metadata" names (design P9-2). class PackagingCase + ACRONYMS: Array[String] DEFAULT_RESOLVE: ^(String name) -> untyped @core_name: String @@ -30,6 +33,7 @@ module Dexpace def unheadered: (String? root) -> Array[String] def resolve_constant: (String path) -> Module def default_constant_path: (String name) -> String + def constant_word: (String word) -> String end end end diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/packaging_suite.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/packaging_suite.rbs index 1533a04..4f49e19 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/packaging_suite.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/packaging_suite.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Conformance # Appendix B.9's portable half: the eight NFRs whose subject is a UNIT (design R2). diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/packaging_suite/dependencies.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/packaging_suite/dependencies.rbs index 08dfed9..79956a1 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/packaging_suite/dependencies.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/packaging_suite/dependencies.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Conformance::PackagingSuite::Dependencies is a private_constant (see dependencies.rb); # declared because the conformance Steep target checks every file under lib/. module Dexpace diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/packaging_suite/surface.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/packaging_suite/surface.rbs index ce231c5..6452e55 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/packaging_suite/surface.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/packaging_suite/surface.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Conformance::PackagingSuite::Surface is a private_constant (see surface.rb); declared # because the conformance Steep target checks every file under lib/. module Dexpace diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/recording_span.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/recording_span.rbs index 12e2f0c..482e987 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/recording_span.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/recording_span.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Conformance # OBS-21's recording span double, over phase 5c's _Span protocol. diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/report.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/report.rbs index 428dbf2..496cd85 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/report.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/report.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Conformance # A frozen list of Results with the renderer 8a shipped and the structured form it deferred; diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/result.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/result.rbs index 2616464..10372cf 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/result.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/result.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Conformance # One assertion's outcome, in exactly five statuses (design §12). diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/rspec_driver.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/rspec_driver.rbs index 36b3480..88e42f6 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/rspec_driver.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/rspec_driver.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Conformance # The thin RSpec driver, opted into by requiring its file; `::RSpec` is resolved at call time diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/runner.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/runner.rbs index ab13167..b0a35d6 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/runner.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/runner.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Conformance # The one loop every phase-9 suite's `.run` delegates to; the five statuses decided in one diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/scripts.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/scripts.rbs index b7c6641..9881c1c 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/scripts.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/scripts.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Conformance # The named response scripts a WireServer runs; each returns a callable taking the accepted diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/shared_instance.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/shared_instance.rbs index d534029..362e0ce 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/shared_instance.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/shared_instance.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Conformance # XCUT-11's structural predicate (design R8, P9-9): frozen, or every ivar declared by the diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/transport_case.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/transport_case.rbs index e7c4c5a..60d4528 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/transport_case.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/transport_case.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Conformance # Suite contract 11: a fixture is whatever answers this -- WireServer's own surface -- so 8c's diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/transport_suite.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/transport_suite.rbs index 4a079df..81112c1 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/transport_suite.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/transport_suite.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Conformance # The transport suite's runner (8a's R7, R16). `build:` and `borrow:` are untyped callables: @@ -16,8 +18,7 @@ module Dexpace private - def run_one: (Assertion assertion, build: untyped, borrow: untyped, around: untyped, - settle: untyped, wire: untyped) -> Result + def tearing_down: (untyped around, Array[TransportCase] cases) -> untyped end end end diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/transport_suite/asynchronous.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/transport_suite/asynchronous.rbs index 6248e17..f6e0693 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/transport_suite/asynchronous.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/transport_suite/asynchronous.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Conformance::TransportSuite::Asynchronous is a private_constant and not public API: # this declaration exists because the conformance Steep target checks every file under lib/. RBS # has no visibility for a constant, so the privacy lives in asynchronous.rb alone. diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/transport_suite/checks.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/transport_suite/checks.rbs index cad8c7e..ea6708c 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/transport_suite/checks.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/transport_suite/checks.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Conformance::TransportSuite::Checks is a private_constant and not public API: this # declaration exists because the conformance Steep target checks every file under lib/ and needs # the helpers declared to type the five assertion groups. RBS has no visibility for a constant, so diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/transport_suite/header_drops.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/transport_suite/header_drops.rbs index 6208712..b682021 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/transport_suite/header_drops.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/transport_suite/header_drops.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Conformance::TransportSuite::HeaderDrops is a private_constant and not public API: # this declaration exists because the conformance Steep target checks every file under lib/. RBS # has no visibility for a constant, so the privacy lives in header_drops.rb alone. diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/transport_suite/inbound.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/transport_suite/inbound.rbs index 4598dc2..5e1fdd8 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/transport_suite/inbound.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/transport_suite/inbound.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Conformance::TransportSuite::Inbound is a private_constant and not public API: this # declaration exists because the conformance Steep target checks every file under lib/. RBS has no # visibility for a constant, so the privacy lives in inbound.rb alone. diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/transport_suite/lifecycle.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/transport_suite/lifecycle.rbs index 9350b45..84e7eb3 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/transport_suite/lifecycle.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/transport_suite/lifecycle.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Conformance::TransportSuite::Lifecycle is a private_constant and not public API: this # declaration exists because the conformance Steep target checks every file under lib/. RBS has no # visibility for a constant, so the privacy lives in lifecycle.rb alone. diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/transport_suite/outbound.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/transport_suite/outbound.rbs index 6060d68..fa58167 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/transport_suite/outbound.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/transport_suite/outbound.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Conformance::TransportSuite::Outbound is a private_constant and not public API: this # declaration exists because the conformance Steep target checks every file under lib/. RBS has no # visibility for a constant, so the privacy lives in outbound.rb alone. diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/transport_suite/resilience.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/transport_suite/resilience.rbs index ad22dc0..06c0f54 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/transport_suite/resilience.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/transport_suite/resilience.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Conformance::TransportSuite::Resilience is a private_constant and not public API: this # declaration exists because the conformance Steep target checks every file under lib/. RBS has no # visibility for a constant, so the privacy lives in resilience.rb alone. diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/transport_suite/streaming.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/transport_suite/streaming.rbs index 3490a2d..f87ebfc 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/transport_suite/streaming.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/transport_suite/streaming.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Conformance::TransportSuite::Streaming is a private_constant and not public API: this # declaration exists because the conformance Steep target checks every file under lib/. RBS has no # visibility for a constant, so the privacy lives in streaming.rb alone. diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/vacuous.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/vacuous.rbs index 253cfca..8ad59c3 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/vacuous.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/vacuous.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Conformance # Raised from inside an assertion whose antecedent is absent on the subject (design §12). diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/version.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/version.rbs index 6c9ab0d..c5ebe2b 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/version.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/version.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Conformance VERSION: String diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/wire_server.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/wire_server.rbs index 5a6f7d3..146a21a 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/wire_server.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/wire_server.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Conformance # Design §9.3's plaintext TCPServer fixture. The listener, the sockets and the accepted diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/wire_server/recorded_request.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/wire_server/recorded_request.rbs index d3aad5a..f2848b8 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/wire_server/recorded_request.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/wire_server/recorded_request.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Conformance class WireServer diff --git a/gems/dexpace-conformance/sig/dexpace/conformance/wire_server/request_reader.rbs b/gems/dexpace-conformance/sig/dexpace/conformance/wire_server/request_reader.rbs index 9c65486..588638f 100644 --- a/gems/dexpace-conformance/sig/dexpace/conformance/wire_server/request_reader.rbs +++ b/gems/dexpace-conformance/sig/dexpace/conformance/wire_server/request_reader.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Conformance::WireServer::RequestReader is a private_constant and not public API: this # declaration exists because the conformance Steep target checks every file under lib/. RBS has no # visibility for a constant, so the privacy lives in request_reader.rb alone. diff --git a/gems/dexpace-conformance/test/dexpace/conformance/packaging_suite_test.rb b/gems/dexpace-conformance/test/dexpace/conformance/packaging_suite_test.rb index d82b70a..bfd4b26 100644 --- a/gems/dexpace-conformance/test/dexpace/conformance/packaging_suite_test.rb +++ b/gems/dexpace-conformance/test/dexpace/conformance/packaging_suite_test.rb @@ -264,14 +264,33 @@ def shipped(lib:, sig:) assert_equal(:passed, statuses(report)["NFR-13"]) end - # The assertion must discriminate against the REAL tree too, not only against a fixture: 278 - # shipped .rbs files carry no header today, which is the gap phase 10's inbound list owns. - test "NFR-13 against this repository's own shipped signatures reports the real gap" do + # The assertion runs against the REAL tree too, not only against a fixture. Phase 9 pinned it + # :failed -- 307 of 307 shipped .rbs files carried no header on 2026-09-23 -- and phase 10's + # NFR-13 repair put the header on every one and flipped this pin, which is "the direction a gate + # should move under its own repair". The fixture cases above keep the :failed branch proven. + test "NFR-13 against this repository's own shipped signatures passes" do root = File.expand_path("../../../..", __dir__) report = run_suite(conforming, adapters: [], sig_roots: { CORE => File.join(root, "dexpace-core", "sig") },) - assert_equal(:failed, statuses(report)["NFR-13"], + assert_equal(:passed, statuses(report)["NFR-13"], "an unconditional vacuity here would check nothing for anyone",) end + + # NFR-15 (phase 10, phase 9's aggregate-run finding): the default name rule was segment for + # segment, so four of this port's six gems needed the `constants:` override -- `Dexpace::Core` + # is not loaded, and NetHttp, AsyncHttp and Json are not the constants. The rule now covers all + # six; the override stays for a name it still misses. + test "the default gem-name rule reaches all six of this port's namespaces" do + kase = Dexpace::Conformance::PackagingCase.new(core_name: CORE, adapter_names: []) + expected = { + "dexpace-core" => "Dexpace", "dexpace-transport-net_http" => "Dexpace::Transport::NetHTTP", + "dexpace-transport-async_http" => "Dexpace::Transport::AsyncHTTP", + "dexpace-serde-json" => "Dexpace::Serde::JSON", "dexpace-async-thread" => "Dexpace::Async::Thread", + "dexpace-conformance" => "Dexpace::Conformance", + } + + expected.each { |name, path| assert_equal(path, kase.send(:default_constant_path, name), name) } + assert_equal(Dexpace::VERSION, kase.runtime_version(CORE), "no override was supplied") + end end diff --git a/gems/dexpace-conformance/test/dexpace/conformance/transport_suite_test.rb b/gems/dexpace-conformance/test/dexpace/conformance/transport_suite_test.rb index d75b46b..3f0a2bc 100644 --- a/gems/dexpace-conformance/test/dexpace/conformance/transport_suite_test.rb +++ b/gems/dexpace-conformance/test/dexpace/conformance/transport_suite_test.rb @@ -53,7 +53,9 @@ class OutcomesTest < DexpaceTestCase report = run_suite(failing, vacuous, erroring) assert_equal(%i[failed vacuous error], report.results.map(&:status)) - assert_equal(["x", "no antecedent", "RuntimeError: boom"], report.results.map(&:detail)) + # Since phase 10 the statuses are Runner's, whose :failed detail carries the expectation. + assert_equal(["x (expected 1, got 2)", "no antecedent", "RuntimeError: boom"], + report.results.map(&:detail),) refute_predicate(report, :passed?) end @@ -96,7 +98,7 @@ class OutcomesTest < DexpaceTestCase }, assertions: [checks_close],) assert_equal(1, report.failures.size, "the suite must DETECT the defect, not merely run") - assert_equal("closed? did not flip", report.failures.first.detail) + assert_equal("closed? did not flip (expected true, got false)", report.failures.first.detail) end test "every assertion gets a FRESH case, torn down after it, whatever it raised" do @@ -181,4 +183,36 @@ class DriverKeywordsTest < DexpaceTestCase assert_raises(ArgumentError) { kase_seen.transport.call(:r, :o, :c) } end end + + # Phase 10's invariant for the fold onto Runner, in a class of its own under + # Metrics/ClassLength. + class RunnerFoldTest < DexpaceTestCase + include Running + + # Phase 10's invariant for folding TransportSuite.run onto Runner (P9-10's residue, inbound + # bullet 30): one run over five assertions yields exactly the five statuses, in order, and + # every case that was built is torn down -- the one behaviour Runner lacked a seam for. + test "one run yields all five statuses in order, and every built case is torn down" do + torn = [] + build = lambda do |**_| + transport = Object.new + transport.define_singleton_method(:close) { torn << :closed } + transport + end + assertions = [ + assertion("P", &:transport), + assertion("F") do |_| + raise Failure.new("f", expected: 1, actual: 2, requirement_ids: ["F"]) + end, + assertion("V") { |kase| kase.transport && raise(Vacuous, "v") }, + assertion("W") { |_| raise "never runs" }, + assertion("E") { |kase| kase.transport && raise("e") }, + ] + + report = TransportSuite.run(build: build, assertions: assertions, waive: ["W"]) + + assert_equal(%i[passed failed vacuous waived error], report.results.map(&:status)) + assert_equal(3, torn.size, "each case that built a transport closed it at teardown") + end + end end diff --git a/gems/dexpace-core/lib/dexpace/async/delay.rb b/gems/dexpace-core/lib/dexpace/async/delay.rb index 803d836..35bd0c3 100644 --- a/gems/dexpace-core/lib/dexpace/async/delay.rb +++ b/gems/dexpace-core/lib/dexpace/async/delay.rb @@ -73,11 +73,22 @@ def self.validate_delay(duration) unless duration.is_a?(::Numeric) raise InvalidArgumentError, "duration must be a number of seconds, got #{duration.class}" end + # Phase 10, P8-77's shape: `real?` first (a Complex has no `negative?`), and NaN refused + # (it answers false to both `negative?` and `zero?`, and the scheduler's wait it reached + # never ends). The same screen as Clock::Guard.duration. + unless duration.real? + raise InvalidArgumentError, "duration must be a real number, got #{duration.inspect}" + end if duration.negative? raise InvalidArgumentError, "duration must be non-negative, got #{duration}" end seconds = duration #: untyped + if seconds.respond_to?(:nan?) && seconds.nan? + raise InvalidArgumentError, + "duration must be a number, got NaN" + end + seconds.to_f end private_class_method :validate_delay diff --git a/gems/dexpace-core/lib/dexpace/cancellation/source.rb b/gems/dexpace-core/lib/dexpace/cancellation/source.rb index 0d6a768..7b8cb4e 100644 --- a/gems/dexpace-core/lib/dexpace/cancellation/source.rb +++ b/gems/dexpace-core/lib/dexpace/cancellation/source.rb @@ -85,8 +85,12 @@ def on_cancel(&block) # Withdraws a hook a token registered, so a bounded wait retains nothing on an unbounded # source. Called by Cancellation::Subscription#detach and by nothing else. Idempotent, and a - # no-op once #cancel has taken the list. Array#delete compares with ==, which for a Proc is - # object identity, so one registration is removed and an identical-looking one is not. + # no-op once #cancel has taken the list. Array#delete compares with ==, and Proc#== is NOT + # object identity: a Proc's #dup answers == to it on 3.2.11 through 4.0.6 (measured by phase + # 10, correcting this comment -- phase 2's review R3-3), while two Procs made separately from + # one literal do not. The hooks this list holds are made fresh per registration and never + # duplicated, so removing one never removes another; a caller that registered a Proc and its + # own #dup would see both go. # # @return [self] def off_cancel(hook) diff --git a/gems/dexpace-core/lib/dexpace/clock.rb b/gems/dexpace-core/lib/dexpace/clock.rb index 3185aa8..a5b56f9 100644 --- a/gems/dexpace-core/lib/dexpace/clock.rb +++ b/gems/dexpace-core/lib/dexpace/clock.rb @@ -34,7 +34,17 @@ def duration(value) unless value.is_a?(::Numeric) raise InvalidArgumentError, "duration must be a number of seconds, got #{value.class}" end + # Phase 10, P8-77's shape (8b's review R1-1): `real?` before `negative?`, because a Complex + # is a Numeric with no order and `negative?` was a bare NoMethodError; and a NaN answers + # false to `negative?` AND `zero?`, so it reached `Queue#pop(timeout: NaN)`, which parks + # forever on every supported row -- a sleep no elapsed time ends. Infinity stays admitted: + # an unbounded wait only the token ends is a caller's legitimate choice here. + unless value.real? + raise InvalidArgumentError, + "duration must be a real number, got #{value.inspect}" + end raise InvalidArgumentError, "duration must be non-negative, got #{value}" if value.negative? + raise InvalidArgumentError, "duration must be a number, got NaN" if Guard.nan?(value) # rbs's Numeric declares no #to_f (its subclasses do), so the conversion reads the value # untyped after the check above has done the typing. @@ -42,6 +52,9 @@ def duration(value) seconds.to_f end + # Numeric's own protocol: Float and BigDecimal answer #nan?, Integer and Rational do not. + def nan?(value) = value.respond_to?(:nan?) && value.nan? + # @return [Dexpace::Cancellation, nil] nil for no token or the never-cancelled one def token(cancellation) return nil if cancellation.nil? diff --git a/gems/dexpace-core/lib/dexpace/configuration/builder.rb b/gems/dexpace-core/lib/dexpace/configuration/builder.rb index ab14188..a32c405 100644 --- a/gems/dexpace-core/lib/dexpace/configuration/builder.rb +++ b/gems/dexpace-core/lib/dexpace/configuration/builder.rb @@ -121,17 +121,47 @@ def build property_source: composed_source,) end + # A property source this builder composed: its own entries shadowing one base source. + # Frozen, and never nested -- composing over a Layered merges into a new one over the same + # base (phase 10). + class Layered + attr_reader :entries, :base + + def initialize(entries, base) + @entries = entries.to_h { |key, value| [key.to_s, value.to_s] }.freeze + @base = base + freeze + end + + # This layer's own entry for the key, else the base source's answer (CFG-13's + # last-write-wins at the key level). + # + # @param key [String] + # @return [String, nil] + def call(key) = @entries.fetch(key.to_s) { @base.call(key) } + end + private_constant :Layered + private + # Phase 10, repairing 5a's review R1-2: each `Dexpace.configure` that added a property over + # an inherited source wrapped it in one more closure, so N configures built an N-deep chain + # every property lookup walked, and none was ever released. A layer this builder made is now + # FLATTENED -- its entries merged under the new ones, over the same base -- so the chain is + # at most one layer over the base whatever the configure count. Last-write-wins per key + # (CFG-13) and an untouched inherited seam passed through by reference (CFG-9) both hold. def composed_source - inherited = @property_source + inherited = @property_source #: untyped return inherited || Sources::NONE if @property_source_explicit || @properties.empty? added = Sources.from_hash(@properties) - return added unless inherited - return added if Sources::NONE.equal?(inherited) + return added if inherited.nil? || Sources::NONE.equal?(inherited) + if inherited.is_a?(Layered) + return Layered.new(inherited.entries.merge(@properties), + inherited.base,) + end - ->(key) { added.call(key) || inherited.call(key) }.freeze + Layered.new(@properties, inherited) end def key!(key) diff --git a/gems/dexpace-core/lib/dexpace/http/body/stream_body.rb b/gems/dexpace-core/lib/dexpace/http/body/stream_body.rb index f588260..3eeeb0d 100644 --- a/gems/dexpace-core/lib/dexpace/http/body/stream_body.rb +++ b/gems/dexpace-core/lib/dexpace/http/body/stream_body.rb @@ -25,6 +25,7 @@ def initialize(io, media_type: nil, content_length: -1, close: false) raise Dexpace::InvalidArgumentError, "a stream body's io must respond to #readpartial or #read, got #{io.class}" end + refuse_closed!(io) unless content_length.is_a?(::Integer) && content_length >= -1 raise Dexpace::InvalidArgumentError, "content_length must be an Integer of -1 or more, got #{content_length.inspect}" @@ -85,6 +86,14 @@ def write_to(sink) # Errno::ESPIPE is a SystemCallError and NOT an IOError, so a bare `rescue IOError` would not # catch it. Guarded by respond_to? on both methods first, because the factory accepts any # #read-shaped object and a bare probe would raise NoMethodError out of a question. + # A stream already closed can never be written, and its #pos raised a bare IOError out of the + # probe below, outside `rescue Dexpace::Error` (phase 10, phase 3b's review R2-1). + def refuse_closed!(io) + return unless io.respond_to?(:closed?) && io.closed? + + raise Dexpace::InvalidArgumentError, "a stream body's io is already closed" + end + def probe_rewindability(io) return [0, false] unless io.respond_to?(:pos) && io.respond_to?(:seek) diff --git a/gems/dexpace-core/lib/dexpace/http/header_syntax.rb b/gems/dexpace-core/lib/dexpace/http/header_syntax.rb index da72ee4..1f95753 100644 --- a/gems/dexpace-core/lib/dexpace/http/header_syntax.rb +++ b/gems/dexpace-core/lib/dexpace/http/header_syntax.rb @@ -61,7 +61,12 @@ def trim(name) # HTTP-17: no C0 control, no DEL, no byte >= 0x80, not blank. Every byte below 0x21 is # rejected, which covers the controls, SP and HTAB -- a header name has no interior space. + # + # Every predicate here is TOTAL: a non-String answers false rather than raising NoMethodError + # from `#b`, which escaped `rescue Dexpace::Error` (phase 10, phase 1's review R3-4). def valid_name?(name) + return false unless name.is_a?(::String) + trimmed = trim(name) return false if trimmed.empty? @@ -70,6 +75,8 @@ def valid_name?(name) # HTTP-18: HTAB plus printable ASCII 0x20-0x7E, and nothing else. def valid_outbound_value?(value) + return false unless value.is_a?(::String) + value.b.each_byte.all? { |byte| byte == HTAB || (byte >= SPACE && byte < DEL) } end @@ -77,6 +84,8 @@ def valid_outbound_value?(value) # in a field value and applying the outbound grammar to a response would silently drop a # legitimate Latin-1 Content-Disposition. def valid_inbound_value?(value) + return false unless value.is_a?(::String) + value.b.each_byte.all? { |byte| byte == HTAB || (byte >= SPACE && byte != DEL) } end @@ -132,6 +141,8 @@ def validate_inbound_value!(value, name:) # String carrying invalid UTF-8, and an ArgumentError raised from inside Ruby escapes # `rescue Dexpace::Error` -- a validator that crashes has not rejected its input. def token?(text) + return false unless text.is_a?(::String) + bytes = text.b !bytes.empty? && bytes.each_byte.all? { |byte| TCHAR.include?(byte) } end diff --git a/gems/dexpace-core/lib/dexpace/http/protocol.rb b/gems/dexpace-core/lib/dexpace/http/protocol.rb index 828bd01..8e14104 100644 --- a/gems/dexpace-core/lib/dexpace/http/protocol.rb +++ b/gems/dexpace-core/lib/dexpace/http/protocol.rb @@ -17,10 +17,16 @@ class Protocol < Data.define(:wire) private_class_method :new - # The canonical lower-case wire forms a Protocol can hold. - WIRE_FORMS = %w[http/1.1 http/2].freeze + # The canonical lower-case wire forms a Protocol can hold. HTTP-33 names http/1.1 and http/2 as + # EXAMPLES; http/1.0 joined in phase 10, because a real HTTP/1.0 status line is one any server + # may send and both transports feed their native version straight to .parse -- before it, an + # HTTP/1.0 response made both ResponseMappers raise. Additive, so NFR-4 permits it. + WIRE_FORMS = %w[http/1.0 http/1.1 http/2].freeze # Every accepted spelling, folded, mapped to its canonical form; HTTP/2.0 is HTTP-33's alias. - ALIASES = { "http/1.1" => "http/1.1", "http/2" => "http/2", "http/2.0" => "http/2" }.freeze + ALIASES = { + "http/1.0" => "http/1.0", "http/1.1" => "http/1.1", + "http/2" => "http/2", "http/2.0" => "http/2", + }.freeze # The validating factory: accepts a canonical wire form and nothing looser, because #with # routes through it and a derived protocol must be exactly as canonical as a parsed one. @@ -68,6 +74,8 @@ def to_s wire end + # The HTTP/1.0 protocol, canonical instance (phase 10). + HTTP_1_0 = build(wire: "http/1.0") # The HTTP/1.1 protocol, canonical instance. HTTP_1_1 = build(wire: "http/1.1") # The HTTP/2 protocol, canonical instance; `HTTP/2` and `HTTP/2.0` both parse to it. diff --git a/gems/dexpace-core/lib/dexpace/http/status.rb b/gems/dexpace-core/lib/dexpace/http/status.rb index e307c93..8bfb34e 100644 --- a/gems/dexpace-core/lib/dexpace/http/status.rb +++ b/gems/dexpace-core/lib/dexpace/http/status.rb @@ -4,7 +4,7 @@ require_relative "../model" module Dexpace - # An HTTP status code, total over the protocol's range (HTTP-10 - HTTP-12). + # An HTTP status code, total over every Integer code (HTTP-10 - HTTP-12). # # ONE member, and that is HTTP-12 rather than minimalism: two Status values must be equal iff # their codes are equal, with the name taking no part. A Data.define(:code, :name) would generate @@ -16,7 +16,7 @@ class Status < Data.define(:code) private_class_method :new - # The recognised codes and their reason phrases; every other code in range is a vendor code. + # The recognised codes and their reason phrases; every other code is a vendor code. CANONICAL_NAMES = { 100 => "Continue", 101 => "Switching Protocols", 200 => "OK", 201 => "Created", 202 => "Accepted", 204 => "No Content", @@ -54,19 +54,26 @@ def self.canonical_name(code) CANONICAL_NAMES[code] end - # HTTP-10 says construction is total over "any code"; the 100-599 guard is the port's reading - # and not a narrowing of it. A vendor code such as nginx's 499 or Cloudflare's 520-526 and 530 - # is inside the range and constructs cleanly with no name, which is the requirement's own - # rationale; an Integer outside the protocol's range is a caller mistake, not a vendor code. + # HTTP-10 says construction is total over "any code" -- "mapping any code MUST return a + # Status (never throw)" -- and TRANSPORT-24 that any code a server returns is surfaced rather + # than rejected. Phase 1's 100-599 guard refused a 600 or LinkedIn's 999, which Net::HTTP + # delivers as an HTTPUnknownResponse, and made both transports raise on a head the + # requirement says to surface (phase 10, 8a's R2-2); a first repair to 0..999 -- "every code a + # status line can carry" -- still threw on 1000 and -1, a port reading narrower than the + # MUST (phase 10's review round 0, R0-5). So every Integer is a code, and the protocol's own + # range is the #standard? predicate; only a non-Integer is refused, because it is not a code + # at all (HTTP-4's type check, not a range). def initialize(code:) Model.required!("code", code) - unless code.is_a?(Integer) && code.between?(100, 599) - raise InvalidArgumentError, "code must be an integer status code between 100 and 599" - end + raise InvalidArgumentError, "code must be an Integer status code" unless code.is_a?(Integer) super end + # 100-599: the classes HTTP-11 names. A vendor code outside them (a 999) is a Status all the + # same and answers false to every class predicate below. + def standard? = code.between?(100, 599) + # The reason phrase for a recognised code; nil for a vendor code (HTTP-10). def canonical_name CANONICAL_NAMES[code] diff --git a/gems/dexpace-core/lib/dexpace/http/url.rb b/gems/dexpace-core/lib/dexpace/http/url.rb index e0ceb91..e3e2ada 100644 --- a/gems/dexpace-core/lib/dexpace/http/url.rb +++ b/gems/dexpace-core/lib/dexpace/http/url.rb @@ -27,10 +27,7 @@ module URL # still holds is externally-mutable state XCUT-15 forbids a model from aliasing. Parsing # yields components this method owns, which it can then freeze without touching the caller's. def parse!(input) - Model.required!("url", input) - unless input.is_a?(String) || input.is_a?(::URI::Generic) - raise InvalidArgumentError, "url must be a String or a URI, got #{input.class}" - end + string_or_uri!(input) # rbs's stdlib signatures declare URI::RFC3986_Parser as an empty class, so its #parse is # invisible to Steep; the annotation is local to this one call rather than a reopened @@ -41,10 +38,15 @@ def parse!(input) raise InvalidArgumentError, "url #{input.to_s.inspect} is not an absolute URI (HTTP-47)" end - own(uri) - rescue ::URI::InvalidURIError => error + refuse_hostless!(uri, input) + + renderable!(own(uri), input) + rescue ::URI::Error => error # The Task 1 rule: a stdlib exception raised because of the argument is re-raised as the - # SDK's argument error, naming the input, with the original left as the cause. + # SDK's argument error, naming the input, with the original left as the cause. URI::Error, + # not only InvalidURIError: `mailto://host` raises URI::InvalidComponentError from the + # scheme's own constructor, which escaped `rescue Dexpace::Error` until phase 10 (phase 2's + # review R3-1). raise InvalidArgumentError, "url #{input.to_s.inspect} is malformed: #{error.message}" end @@ -93,6 +95,37 @@ def resolve(base, reference) private + def string_or_uri!(input) + Model.required!("url", input) + return if input.is_a?(String) || input.is_a?(::URI::Generic) + + raise InvalidArgumentError, "url must be a String or a URI, got #{input.class}" + end + + # HTTP-47's rationale is failing at construction "rather than surfacing a lower-level or + # transport-specific error later", and `URI::Generic#absolute?` is only "a scheme is present": + # `http:` and `http:///p` parse as absolute URI::HTTPs with no host, which surfaced as whatever + # the transport raised on a nil host. The http family is this SDK's own; which OTHER schemes a + # transport dispatches stays the transport's knowledge (phase 1's reading), so only the http + # family is screened for a host here (phase 10, phase 1's review round 3). + def refuse_hostless!(uri, input) + return unless uri.is_a?(::URI::HTTP) && (uri.host.nil? || uri.host.empty?) + + raise InvalidArgumentError, "url #{input.to_s.inspect} has no host (HTTP-47)" + end + + # A URI whose own #to_s mutates it cannot be held frozen: URI::FTP with a `;type=` code + # rewrites @path inside #to_s, so the frozen result raised FrozenError from every later + # rendering -- a lower-level error, later, which is what HTTP-47 exists to prevent (phase 2's + # review R3-2). Refused here, at construction, with the SDK's own error. + def renderable!(uri, input) + uri.to_s + uri + rescue ::FrozenError + raise InvalidArgumentError, + "url #{input.to_s.inspect} cannot be held as an immutable URI (HTTP-47)", cause: nil + end + # Freezes the URI's String components, then the URI. Only Strings: a URI also references # its parser, a process-global object this method leaves alone -- the uri gem freezes # URI::RFC3986_PARSER at definition on every supported Ruby, so nothing here needs to touch diff --git a/gems/dexpace-core/lib/dexpace/instrumentation/async_step.rb b/gems/dexpace-core/lib/dexpace/instrumentation/async_step.rb index 77e0081..da4d64a 100644 --- a/gems/dexpace-core/lib/dexpace/instrumentation/async_step.rb +++ b/gems/dexpace-core/lib/dexpace/instrumentation/async_step.rb @@ -103,10 +103,17 @@ def head(request, cursor) # response event and the derived future's fulfilment, then the finish; a meter that raises # there is collected by Hooks.notify, which still runs every later callback, and re-raised # into the producer once the list has run. + # + # The clock is read in a callback registered BEFORE the derivation (phase 10, 5b's review + # R3-2): the derived future's fulfilment runs every callback the CALLER put on it, and with + # the clock read in the finish that follows, the recorded duration included the caller's own + # continuation. The sync path records before it returns; this records at settlement. def attach(future, pending) if body? + ended = nil #: Float? + future.on_settle { |_settlement| ended = @clock.monotonic } derived = future.then { |response| settle_response(pending, wrap_response(response)) } - future.on_settle { |settlement| settle(pending, settlement) } + future.on_settle { |settlement| settle(pending, settlement, ended) } derived else future.on_settle do |settlement| @@ -123,12 +130,12 @@ def settle_response(pending, response) end # The failure event, bridged, then the tracer and meter work. - def settle(pending, settlement) + def settle(pending, settlement, ended = nil) error = settlement.error unless error.nil? bridged(pending.snapshot) { log_failure(pending.request, error, pending.started) } end - finish(pending.span, pending.started) + finish(pending.span, pending.started, ended) end # OBS-24's bridge around a log emission; nothing to bridge when nothing is logged. diff --git a/gems/dexpace-core/lib/dexpace/instrumentation/bundle.rb b/gems/dexpace-core/lib/dexpace/instrumentation/bundle.rb index c1620a6..dfd3ae1 100644 --- a/gems/dexpace-core/lib/dexpace/instrumentation/bundle.rb +++ b/gems/dexpace-core/lib/dexpace/instrumentation/bundle.rb @@ -25,6 +25,11 @@ module Instrumentation # made a bundle reachable by a pipeline step through Pipeline::Cursor#bundle, seeded per # call by the `bundle:` keyword on Pipeline#call and AsyncPipeline#call (its Task 8). # + # Two factories, not one (phase 10, R6): the member `tracer_factory` makes SPAN tracers + # (OBS-21 - OBS-25) and is legitimately shared and cached; OBS-29's HTTP-tracer factory makes + # one Instrumentation::HTTPTracer per operation and lives on the retry steps' + # `http_tracer_factory:`, never here. + # # trace_state is a list of pairs, not a Hash: OBS-26 says "a vendor trace-state list" and W3C # tracestate is ordered, most recent vendor first, which a Hash would lose. trace_flags stays # the two-hex-char wire form OBS-26 fixes; phase 5c added the #sampled? predicate over it, @@ -60,8 +65,13 @@ class Bundle < Data.define( # @param trace_state [Array] the vendor trace-state list, in order # @param remote [Boolean] whether the trace context arrived from another service # @param span [Object] the active span; NO_SPAN when tracing is off - # @param tracer_factory [#tracer] the per-operation tracer factory; NO_TRACER_FACTORY when - # tracing is off + # @param tracer_factory [#tracer] CTX-14's SPAN-tracer factory, keyed by instrumentation + # library name and version (opentelemetry-api's TracerProvider shape, 4a's P4-8) and shared + # across operations; NO_TRACER_FACTORY when tracing is off. It is NOT OBS-29's + # per-operation HTTP-tracer factory, which the retry steps take as `http_tracer_factory:` + # and call once per operation (6a's P6-7): OBS-25's no-op factory must answer the same + # object on every call, the opposite of one instance per operation, so the two are two + # objects (phase 10, R6) # @return [Bundle] def self.build(trace_id:, span_id:, flavour:, trace_flags: "00", trace_state: [], remote: false, span: NO_SPAN, tracer_factory: NO_TRACER_FACTORY) diff --git a/gems/dexpace-core/lib/dexpace/instrumentation/redactor.rb b/gems/dexpace-core/lib/dexpace/instrumentation/redactor.rb index 9b795ab..b70e14c 100644 --- a/gems/dexpace-core/lib/dexpace/instrumentation/redactor.rb +++ b/gems/dexpace-core/lib/dexpace/instrumentation/redactor.rb @@ -40,6 +40,13 @@ module Instrumentation # common case never reaches the rescue, because the name is folded through `#scrub` first; # the rescue is the totality backstop XCUT-20 asks for. # + # WHERE it is applied, which a caller wiring a sink needs to know (phase 10, 5b's review R3-4): + # on the way INTO the record, by the field's name, and never at the sink. Event#field routes + # every reserved URL and header key through here; Logger.build scrubs the global context once; + # Event#emit scrubs the folded diagnostic context -- OBS-5's three sources, one table + # (ReservedKeys). A sink therefore receives already-redacted values and is never trusted to + # redact; a value written under an unreserved key is not a URL or a header and passes as given. + # # Frozen, holding a frozen policy, with every intermediate a method local: XCUT-11's shared # instance by construction. Redactor::DEFAULT is the one over RedactionPolicy::DEFAULT. # diff --git a/gems/dexpace-core/lib/dexpace/instrumentation/step.rb b/gems/dexpace-core/lib/dexpace/instrumentation/step.rb index f27cce9..44cb033 100644 --- a/gems/dexpace-core/lib/dexpace/instrumentation/step.rb +++ b/gems/dexpace-core/lib/dexpace/instrumentation/step.rb @@ -164,13 +164,15 @@ def open_span(request, bundle) # two, OBS-32's conventions are post-v1, and an inline Hash literal here would allocate # per request to carry a vocabulary nothing has chosen. The histogram takes milliseconds, # the same figure the log field carries. - def finish(span, started) + # `ended` is the settlement's own reading when the async step took one (phase 10); nil reads + # the clock now, which is the sync path's moment. + def finish(span, started, ended = nil) span.finish @counter.add(1) - @histogram.record(elapsed_ms(started)) + @histogram.record(elapsed_ms(started, ended)) end - def elapsed_ms(started) = (@clock.monotonic - started) * 1000.0 + def elapsed_ms(started, ended = nil) = ((ended || @clock.monotonic) - started) * 1000.0 def logged? = @level.at_least?(HTTPLogging::HEADERS) def body? = @level.at_least?(HTTPLogging::BODY) diff --git a/gems/dexpace-core/lib/dexpace/io/buffered_source.rb b/gems/dexpace-core/lib/dexpace/io/buffered_source.rb index c8bee09..a31c321 100644 --- a/gems/dexpace-core/lib/dexpace/io/buffered_source.rb +++ b/gems/dexpace-core/lib/dexpace/io/buffered_source.rb @@ -12,8 +12,8 @@ module IO # representation, because it responds to #each yielding BINARY chunks. # # Ownership is two frozen construction-time facts, never a close-time judgement. Closeable's - # @owned is always true -- a source always owns its own buffer -- and a separate frozen - # @dexpace_owns_upstream decides whether #release also closes the upstream. The factory names + # `@owned` is always true -- a source always owns its own buffer -- and a separate frozen + # `@dexpace_owns_upstream` decides whether #release also closes the upstream. The factory names # carry it: .wrapping owns (IO-6), .of_bytes and .over own nothing. IO-6 is read out of # appendix C, and cited with design §10.12; SEAM-3, which the older statements of the rule # cite, is retired (docs/knowledge/notes/message-bodies.md). diff --git a/gems/dexpace-core/lib/dexpace/model.rb b/gems/dexpace-core/lib/dexpace/model.rb index 602cc80..d0365fd 100644 --- a/gems/dexpace-core/lib/dexpace/model.rb +++ b/gems/dexpace-core/lib/dexpace/model.rb @@ -25,8 +25,23 @@ def self.required!(name, value) # copy: true the caller's object graph is untouched and the returned copy is deep-frozen, # which is what lets every accessor return the same frozen reference with no per-access # wrapper (design §4, §10.11). + # + # Two things make_shareable cannot copy, both answered here rather than escaping as Ruby's own + # error outside `rescue Dexpace::Error` (phase 10, phase 1's review R3-1). A Hash's DEFAULT PROC + # is behaviour, not data -- `Hash.new { ... }` made make_shareable raise TypeError ("allocator + # undefined for Proc") on every supported Ruby -- so a model's copy drops it: what the model + # owns is the entries the caller handed it, and a lookup miss on the model answers nil, as on + # any other model collection. Anything else that cannot be made shareable (a Proc or a Mutex + # among the values) is the SDK's argument error naming what was refused. def self.own(collection) - Ractor.make_shareable(collection, copy: true) + owned = collection #: untyped + if owned.is_a?(::Hash) && owned.default_proc + owned = owned.dup + owned.default_proc = nil + end + Ractor.make_shareable(owned, copy: true) + rescue ::TypeError, ::Ractor::Error => error + raise InvalidArgumentError, "a model cannot own this collection: #{error.message}" end # A String a caller still holds a reference to is externally-mutable state XCUT-15 forbids a diff --git a/gems/dexpace-core/lib/dexpace/proxy/resolution.rb b/gems/dexpace-core/lib/dexpace/proxy/resolution.rb index ecbd931..10b2938 100644 --- a/gems/dexpace-core/lib/dexpace/proxy/resolution.rb +++ b/gems/dexpace-core/lib/dexpace/proxy/resolution.rb @@ -58,11 +58,27 @@ module ProxyResolution # rubocop:disable Metrics/ModuleLength # a value with no `//`, which RFC 3986 reads as an opaque URI with no userinfo and the # redactor therefore writes back as given (P5-100). Applied to the redactor's form of the # URL before it is shown, so `user:secret@proxy.corp:3128` -- the scheme forgotten -- shows - # as `***:***@proxy.corp:3128`. Anchored, one bounded class and one literal; the per-pattern - # timeout is the port's rule. - CREDENTIAL_BEFORE_AT = ::Regexp.new('\A[^/?#]*@', timeout: 1.0) + # as `***:***@proxy.corp:3128`. One bounded class and one literal; the per-pattern timeout is + # the port's rule. + # + # UNANCHORED, and applied with `gsub` to every `@` the shown form carries (phase 10, repairing + # phase 5b's R3-1). Anchored at `\A`, the belt reached only a credential in the value's FIRST + # slash-free run, and a spelling that puts a slash before the userinfo -- `http:/u:p@h:1`, + # `http:///u:p@h`, `/http://u:p@h:1`, `socks5:/u:p@h:1` -- is read by the parser as a path + # the redactor keeps verbatim (OBS-14), so the password reached Kernel#warn and the config sink + # on eight spellings of eight on 4.0.6 and 3.2.11. Over-redacting an `@` that sits in a path is + # the safe direction in a warning: OBS-11's userinfo rule is "unconditional". + CREDENTIAL_BEFORE_AT = ::Regexp.new("[^/?#]*@", timeout: 1.0) private_constant :CREDENTIAL_BEFORE_AT + # Where a proxy URL's authority starts: after a `scheme:` that is followed by at least one + # `/`, or after any leading run of `/` -- never after a bare `user:`, which is the forgotten- + # scheme spelling's username and part of the credential. Anchored, and no class in it admits + # the `:` or `/` that ends it, so it cannot backtrack; the per-pattern timeout is the port's + # rule all the same. + AUTHORITY_START = ::Regexp.new("\\A(?:[A-Za-z][A-Za-z0-9+.\\-]*:/+|/*)", timeout: 1.0) + private_constant :AUTHORITY_START + # The header name #shown renders the URL under: `location`, the first of # RedactionPolicy::DEFAULT's URL-valued names, which is what makes Redactor#header_value take # the URL route rather than pass the value through (OBS-17). @@ -174,8 +190,26 @@ def split_url(url, logger) # URL-valued header name, since that entry point is keyed by header name (OBS-16, OBS-17). # Then CFG-24's grammar rule, for the credential the redactor cannot know is one. def shown(url) - redacted = Instrumentation::Redactor::DEFAULT.header_value(SHOWN_AS, url) - redacted.sub(CREDENTIAL_BEFORE_AT, "#{Instrumentation::Redactor::REDACTED_USERINFO}@") + scrubbed = scrub_userinfo(url) + redacted = Instrumentation::Redactor::DEFAULT.header_value(SHOWN_AS, scrubbed) + redacted.gsub(CREDENTIAL_BEFORE_AT, "#{Instrumentation::Redactor::REDACTED_USERINFO}@") + end + + # CFG-24's grammar applied to the RAW value, before the parser or the redactor can split a + # password at a `/`, `?` or `#` it contains: in a proxy URL everything from the authority's + # start through the LAST `@` is userinfo, so it is replaced whole. The belt above runs on the + # redactor's output and stops at the first reserved character, which left the prefix of a + # password such as `pa/ss`, `p#ss` or `se#cret` in the warning (phase 10's review round 0, + # R0-4); this pass runs first and has no delimiter to stop at. A value with no `@` is + # returned unchanged. + def scrub_userinfo(url) + last = url.rindex("@") + return url if last.nil? + + start = AUTHORITY_START.match(url)&.end(0) || 0 + return url if start > last + + "#{url[0, start]}#{Instrumentation::Redactor::REDACTED_USERINFO}@#{url[(last + 1)..]}" end def url_problem(host, raw_port) diff --git a/gems/dexpace-core/lib/dexpace/registry.rb b/gems/dexpace-core/lib/dexpace/registry.rb index 2c33c14..c118bf2 100644 --- a/gems/dexpace-core/lib/dexpace/registry.rb +++ b/gems/dexpace-core/lib/dexpace/registry.rb @@ -177,6 +177,19 @@ def install(provider) # scenario as the motivation for replacing it, so trading a loud error for a silent wedge would # be a straight regression. The claim is released by #complete_resolution's ensure on this path # exactly as on any other, so the registry stays re-evaluable for everyone else. + # + # **Why the loop terminates** (phase 10, answering phase 9's finding that nothing stated it). + # Every pass ends in one of four ways, and only the last repeats. (1) A resolved slot returns + # at once. (2) A fresh claim runs #complete_resolution, which returns or raises -- it never + # loops. (3) The claim swap answers nil only when a provider was resolved between the two + # reads, so the next pass is (1). (4) A claim owned by another fiber is waited on, and its gate + # is closed in that owner's `ensure` whether the build succeeded or raised, and a pop on a + # closed queue returns at once; the owner's slot is cleared in the same `ensure`, so the next + # pass meets (1) when the owner succeeded or takes a fresh claim, (2), when it failed. So a + # caller repeats at most once per concurrent claim that FAILED ahead of it, each of which is a + # real exception raised to that claim's owner. The argument rests on the guard in (1) being + # unconditional: phase 9 made it conditional on anything else and the loop span forever with + # no progress. `registry_test.rb`'s Termination suite pins every state under a bounded join. def resolve loop do resolved = @state.resolved diff --git a/gems/dexpace-core/sig/dexpace.rbs b/gems/dexpace-core/sig/dexpace.rbs index b14f1e5..3cf24c6 100644 --- a/gems/dexpace-core/sig/dexpace.rbs +++ b/gems/dexpace-core/sig/dexpace.rbs @@ -1,2 +1,4 @@ +# SPDX-License-Identifier: MIT + module Dexpace end diff --git a/gems/dexpace-core/sig/dexpace/async/completer.rbs b/gems/dexpace-core/sig/dexpace/async/completer.rbs index 347e104..0b27349 100644 --- a/gems/dexpace-core/sig/dexpace/async/completer.rbs +++ b/gems/dexpace-core/sig/dexpace/async/completer.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Async # Async::Deadline is a private_constant and not public API: declared because the strict diff --git a/gems/dexpace-core/sig/dexpace/async/delay.rbs b/gems/dexpace-core/sig/dexpace/async/delay.rbs index db7ceb4..317a460 100644 --- a/gems/dexpace-core/sig/dexpace/async/delay.rbs +++ b/gems/dexpace-core/sig/dexpace/async/delay.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Async # Both private_constants; declared because the strict `core` Steep target types their uses. diff --git a/gems/dexpace-core/sig/dexpace/async/future.rbs b/gems/dexpace-core/sig/dexpace/async/future.rbs index 5767eff..855ab1f 100644 --- a/gems/dexpace-core/sig/dexpace/async/future.rbs +++ b/gems/dexpace-core/sig/dexpace/async/future.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Async class Future diff --git a/gems/dexpace-core/sig/dexpace/async/settlement.rbs b/gems/dexpace-core/sig/dexpace/async/settlement.rbs index 6e42724..9f25347 100644 --- a/gems/dexpace-core/sig/dexpace/async/settlement.rbs +++ b/gems/dexpace-core/sig/dexpace/async/settlement.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Async class Settlement < Data diff --git a/gems/dexpace-core/sig/dexpace/async_pipeline.rbs b/gems/dexpace-core/sig/dexpace/async_pipeline.rbs index a644c47..9a02534 100644 --- a/gems/dexpace-core/sig/dexpace/async_pipeline.rbs +++ b/gems/dexpace-core/sig/dexpace/async_pipeline.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class AsyncPipeline include Dexpace::Closeable diff --git a/gems/dexpace-core/sig/dexpace/async_transport.rbs b/gems/dexpace-core/sig/dexpace/async_transport.rbs index 0ab8626..54789b2 100644 --- a/gems/dexpace-core/sig/dexpace/async_transport.rbs +++ b/gems/dexpace-core/sig/dexpace/async_transport.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace # Differs from _Transport in exactly one position, the return type -- the whole of what # .conforms? cannot check and the reason both interfaces are declared at all. diff --git a/gems/dexpace-core/sig/dexpace/auth.rbs b/gems/dexpace-core/sig/dexpace/auth.rbs index bfa3cc8..840f2d3 100644 --- a/gems/dexpace-core/sig/dexpace/auth.rbs +++ b/gems/dexpace-core/sig/dexpace/auth.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace # Phase 6c: the authentication layer's namespace and its one shared redaction marker. module Auth diff --git a/gems/dexpace-core/sig/dexpace/auth/async_bearer_stamper.rbs b/gems/dexpace-core/sig/dexpace/auth/async_bearer_stamper.rbs index 1131a75..5af1220 100644 --- a/gems/dexpace-core/sig/dexpace/auth/async_bearer_stamper.rbs +++ b/gems/dexpace-core/sig/dexpace/auth/async_bearer_stamper.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Auth # AUTH-37, AUTH-36 (async half), AUTH-11: the three-zone async bearer stamper. diff --git a/gems/dexpace-core/sig/dexpace/auth/async_step.rbs b/gems/dexpace-core/sig/dexpace/auth/async_step.rbs index 4dfd529..2b001d9 100644 --- a/gems/dexpace-core/sig/dexpace/auth/async_step.rbs +++ b/gems/dexpace-core/sig/dexpace/auth/async_step.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Auth # AUTH-27 through AUTH-38 on the async runtime: Step over 4c's _AsyncStep. Exchange is a diff --git a/gems/dexpace-core/sig/dexpace/auth/basic_handler.rbs b/gems/dexpace-core/sig/dexpace/auth/basic_handler.rbs index 2bdf721..c7e9d13 100644 --- a/gems/dexpace-core/sig/dexpace/auth/basic_handler.rbs +++ b/gems/dexpace-core/sig/dexpace/auth/basic_handler.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Auth # AUTH-14: Basic, preemptive through #call and challenge-answered through diff --git a/gems/dexpace-core/sig/dexpace/auth/bearer_provider.rbs b/gems/dexpace-core/sig/dexpace/auth/bearer_provider.rbs index 6a8064e..dc81a46 100644 --- a/gems/dexpace-core/sig/dexpace/auth/bearer_provider.rbs +++ b/gems/dexpace-core/sig/dexpace/auth/bearer_provider.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Auth # AUTH-11: the provider duck type -- #fetch, and optionally #fetch_async. diff --git a/gems/dexpace-core/sig/dexpace/auth/bearer_stamper.rbs b/gems/dexpace-core/sig/dexpace/auth/bearer_stamper.rbs index 8ae8486..6358773 100644 --- a/gems/dexpace-core/sig/dexpace/auth/bearer_stamper.rbs +++ b/gems/dexpace-core/sig/dexpace/auth/bearer_stamper.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Auth # AUTH-11, AUTH-34, AUTH-35, AUTH-36: the sync bearer stamper. diff --git a/gems/dexpace-core/sig/dexpace/auth/bearer_token.rbs b/gems/dexpace-core/sig/dexpace/auth/bearer_token.rbs index 32a23f8..aa16c73 100644 --- a/gems/dexpace-core/sig/dexpace/auth/bearer_token.rbs +++ b/gems/dexpace-core/sig/dexpace/auth/bearer_token.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Auth # AUTH-8, AUTH-9, AUTH-10: the bearer token, redacted in every rendering. diff --git a/gems/dexpace-core/sig/dexpace/auth/challenge.rbs b/gems/dexpace-core/sig/dexpace/auth/challenge.rbs index c542ae0..cfd88cd 100644 --- a/gems/dexpace-core/sig/dexpace/auth/challenge.rbs +++ b/gems/dexpace-core/sig/dexpace/auth/challenge.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Auth # AUTH-12: one parsed challenge, folded once at construction. diff --git a/gems/dexpace-core/sig/dexpace/auth/challenge_handler_chain.rbs b/gems/dexpace-core/sig/dexpace/auth/challenge_handler_chain.rbs index 14297c5..4706cf2 100644 --- a/gems/dexpace-core/sig/dexpace/auth/challenge_handler_chain.rbs +++ b/gems/dexpace-core/sig/dexpace/auth/challenge_handler_chain.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Auth # AUTH-23's one-method handler protocol (6c's P6-2). diff --git a/gems/dexpace-core/sig/dexpace/auth/challenges.rbs b/gems/dexpace-core/sig/dexpace/auth/challenges.rbs index d7d958e..b624255 100644 --- a/gems/dexpace-core/sig/dexpace/auth/challenges.rbs +++ b/gems/dexpace-core/sig/dexpace/auth/challenges.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Auth # AUTH-12, AUTH-13: the RFC 7235 challenge-list parser. Its patterns and its Parser class are diff --git a/gems/dexpace-core/sig/dexpace/auth/descriptor.rbs b/gems/dexpace-core/sig/dexpace/auth/descriptor.rbs index 47d2537..6cd0945 100644 --- a/gems/dexpace-core/sig/dexpace/auth/descriptor.rbs +++ b/gems/dexpace-core/sig/dexpace/auth/descriptor.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Auth # AUTH-3: a non-empty ordered requirement list, immutable in and out. diff --git a/gems/dexpace-core/sig/dexpace/auth/digest_handler.rbs b/gems/dexpace-core/sig/dexpace/auth/digest_handler.rbs index 7f9224c..1f53703 100644 --- a/gems/dexpace-core/sig/dexpace/auth/digest_handler.rbs +++ b/gems/dexpace-core/sig/dexpace/auth/digest_handler.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Auth # A hash function the handler drives: what ::Digest::MD5 and ::Digest::SHA256 answer. diff --git a/gems/dexpace-core/sig/dexpace/auth/https_required_error.rbs b/gems/dexpace-core/sig/dexpace/auth/https_required_error.rbs index 0672b97..d0bd3f5 100644 --- a/gems/dexpace-core/sig/dexpace/auth/https_required_error.rbs +++ b/gems/dexpace-core/sig/dexpace/auth/https_required_error.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Auth # AUTH-28: the HTTPS guard's refusal, naming the step and the scheme. diff --git a/gems/dexpace-core/sig/dexpace/auth/key_credential.rbs b/gems/dexpace-core/sig/dexpace/auth/key_credential.rbs index 1e07c02..2610d7b 100644 --- a/gems/dexpace-core/sig/dexpace/auth/key_credential.rbs +++ b/gems/dexpace-core/sig/dexpace/auth/key_credential.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Auth # AUTH-8, AUTH-9, AUTH-26: an API key with reference identity. diff --git a/gems/dexpace-core/sig/dexpace/auth/key_stamper.rbs b/gems/dexpace-core/sig/dexpace/auth/key_stamper.rbs index d14cfa1..0747b06 100644 --- a/gems/dexpace-core/sig/dexpace/auth/key_stamper.rbs +++ b/gems/dexpace-core/sig/dexpace/auth/key_stamper.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Auth # What KeyStamper is written against: KeyCredential and NamedKeyCredential both answer it. diff --git a/gems/dexpace-core/sig/dexpace/auth/named_key_credential.rbs b/gems/dexpace-core/sig/dexpace/auth/named_key_credential.rbs index e65e0d9..8f41b04 100644 --- a/gems/dexpace-core/sig/dexpace/auth/named_key_credential.rbs +++ b/gems/dexpace-core/sig/dexpace/auth/named_key_credential.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Auth # AUTH-8, AUTH-9, AUTH-26: a named key with reference identity. diff --git a/gems/dexpace-core/sig/dexpace/auth/password_credential.rbs b/gems/dexpace-core/sig/dexpace/auth/password_credential.rbs index 5ac3872..c8d9dd1 100644 --- a/gems/dexpace-core/sig/dexpace/auth/password_credential.rbs +++ b/gems/dexpace-core/sig/dexpace/auth/password_credential.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Auth # AUTH-8, AUTH-14: the username/password pair, both fields redacted. diff --git a/gems/dexpace-core/sig/dexpace/auth/provider_error.rbs b/gems/dexpace-core/sig/dexpace/auth/provider_error.rbs index 5d1e4b5..9f25062 100644 --- a/gems/dexpace-core/sig/dexpace/auth/provider_error.rbs +++ b/gems/dexpace-core/sig/dexpace/auth/provider_error.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Auth # AUTH-35, AUTH-11: a misbehaving bearer provider result. diff --git a/gems/dexpace-core/sig/dexpace/auth/requirement.rbs b/gems/dexpace-core/sig/dexpace/auth/requirement.rbs index 11731b9..ff5b05a 100644 --- a/gems/dexpace-core/sig/dexpace/auth/requirement.rbs +++ b/gems/dexpace-core/sig/dexpace/auth/requirement.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Auth # AUTH-2: one scheme bound to its own scopes and params, deep-frozen once. diff --git a/gems/dexpace-core/sig/dexpace/auth/resolver.rbs b/gems/dexpace-core/sig/dexpace/auth/resolver.rbs index a6a5665..d7d458c 100644 --- a/gems/dexpace-core/sig/dexpace/auth/resolver.rbs +++ b/gems/dexpace-core/sig/dexpace/auth/resolver.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Auth # AUTH-4 through AUTH-7: tier resolution as a pure module function. diff --git a/gems/dexpace-core/sig/dexpace/auth/scheme.rbs b/gems/dexpace-core/sig/dexpace/auth/scheme.rbs index c7796ee..74b15b8 100644 --- a/gems/dexpace-core/sig/dexpace/auth/scheme.rbs +++ b/gems/dexpace-core/sig/dexpace/auth/scheme.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Auth # AUTH-1: the closed five-member set, in Pipeline::Stage's shape (P4-32, P4-56): both diff --git a/gems/dexpace-core/sig/dexpace/auth/step.rbs b/gems/dexpace-core/sig/dexpace/auth/step.rbs index 24bd214..9ed2549 100644 --- a/gems/dexpace-core/sig/dexpace/auth/step.rbs +++ b/gems/dexpace-core/sig/dexpace/auth/step.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Auth # A synchronous stamper: KeyStamper, BasicHandler, BearerStamper, or Step::NO_STAMP. diff --git a/gems/dexpace-core/sig/dexpace/auth/unencodable_credential_error.rbs b/gems/dexpace-core/sig/dexpace/auth/unencodable_credential_error.rbs index e255bdb..61cd2f6 100644 --- a/gems/dexpace-core/sig/dexpace/auth/unencodable_credential_error.rbs +++ b/gems/dexpace-core/sig/dexpace/auth/unencodable_credential_error.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Auth # AUTH-21 (R10): the typed failure of either encoding branch, naming its target and the diff --git a/gems/dexpace-core/sig/dexpace/auth/validation.rbs b/gems/dexpace-core/sig/dexpace/auth/validation.rbs index 8407a9d..8ce38d5 100644 --- a/gems/dexpace-core/sig/dexpace/auth/validation.rbs +++ b/gems/dexpace-core/sig/dexpace/auth/validation.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Auth::Validation is a private_constant and not public API: this declaration exists # because the strict `core` Steep target checks every file under lib/ and needs the module and # its one method declared to type the credential constructors, exactly as hooks.rbs does for diff --git a/gems/dexpace-core/sig/dexpace/bounded_map.rbs b/gems/dexpace-core/sig/dexpace/bounded_map.rbs index 4e67bab..c80b017 100644 --- a/gems/dexpace-core/sig/dexpace/bounded_map.rbs +++ b/gems/dexpace-core/sig/dexpace/bounded_map.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::BoundedMap is a private_constant and not public API (design P4-3): this declaration # exists because the strict `core` Steep target checks every file under lib/ and needs the class # declared to type ContextStore's call sites, exactly as hooks.rbs does for Dexpace::Hooks. RBS diff --git a/gems/dexpace-core/sig/dexpace/bridge/async_over.rbs b/gems/dexpace-core/sig/dexpace/bridge/async_over.rbs index 87470d7..2aa2103 100644 --- a/gems/dexpace-core/sig/dexpace/bridge/async_over.rbs +++ b/gems/dexpace-core/sig/dexpace/bridge/async_over.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Bridge class AsyncOver diff --git a/gems/dexpace-core/sig/dexpace/bridge/sync_over.rbs b/gems/dexpace-core/sig/dexpace/bridge/sync_over.rbs index 372ccd3..d959134 100644 --- a/gems/dexpace-core/sig/dexpace/bridge/sync_over.rbs +++ b/gems/dexpace-core/sig/dexpace/bridge/sync_over.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Bridge class SyncOver diff --git a/gems/dexpace-core/sig/dexpace/build_info.rbs b/gems/dexpace-core/sig/dexpace/build_info.rbs index 22edd56..76a3a47 100644 --- a/gems/dexpace-core/sig/dexpace/build_info.rbs +++ b/gems/dexpace-core/sig/dexpace/build_info.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module BuildInfo UNKNOWN: String diff --git a/gems/dexpace-core/sig/dexpace/builder.rbs b/gems/dexpace-core/sig/dexpace/builder.rbs index 949307e..9bb1541 100644 --- a/gems/dexpace-core/sig/dexpace/builder.rbs +++ b/gems/dexpace-core/sig/dexpace/builder.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace interface _Builder[T] def build: () -> T diff --git a/gems/dexpace-core/sig/dexpace/cancellation.rbs b/gems/dexpace-core/sig/dexpace/cancellation.rbs index f34865f..0201912 100644 --- a/gems/dexpace-core/sig/dexpace/cancellation.rbs +++ b/gems/dexpace-core/sig/dexpace/cancellation.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Cancellation # A private_constant with no visibility in RBS; declared so Steep can type .none. diff --git a/gems/dexpace-core/sig/dexpace/cancellation/source.rbs b/gems/dexpace-core/sig/dexpace/cancellation/source.rbs index 4f20a9b..93a1a46 100644 --- a/gems/dexpace-core/sig/dexpace/cancellation/source.rbs +++ b/gems/dexpace-core/sig/dexpace/cancellation/source.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Cancellation class Source diff --git a/gems/dexpace-core/sig/dexpace/clock.rbs b/gems/dexpace-core/sig/dexpace/clock.rbs index d16e9ae..23a08de 100644 --- a/gems/dexpace-core/sig/dexpace/clock.rbs +++ b/gems/dexpace-core/sig/dexpace/clock.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace # The time seam a fake satisfies and what Future#value(clock:) types against: exactly the three # operations CFG-15 declares. @@ -13,6 +15,7 @@ module Dexpace module Guard def self?.duration: (untyped value) -> Float def self?.token: (untyped cancellation) -> Dexpace::Cancellation? + def self?.nan?: (untyped value) -> bool end SYSTEM: Clock diff --git a/gems/dexpace-core/sig/dexpace/closeable.rbs b/gems/dexpace-core/sig/dexpace/closeable.rbs index 0a8229a..dd12cd4 100644 --- a/gems/dexpace-core/sig/dexpace/closeable.rbs +++ b/gems/dexpace-core/sig/dexpace/closeable.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Closeable def initialize_closeable: (?owned: bool) -> nil diff --git a/gems/dexpace-core/sig/dexpace/config.rbs b/gems/dexpace-core/sig/dexpace/config.rbs index 5109242..eb4d984 100644 --- a/gems/dexpace-core/sig/dexpace/config.rbs +++ b/gems/dexpace-core/sig/dexpace/config.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace self.@config_mutex: Thread::Mutex self.@configuration: Configuration diff --git a/gems/dexpace-core/sig/dexpace/configuration.rbs b/gems/dexpace-core/sig/dexpace/configuration.rbs index 2ed40d1..557c651 100644 --- a/gems/dexpace-core/sig/dexpace/configuration.rbs +++ b/gems/dexpace-core/sig/dexpace/configuration.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Configuration < Data include Model diff --git a/gems/dexpace-core/sig/dexpace/configuration/builder.rbs b/gems/dexpace-core/sig/dexpace/configuration/builder.rbs index 4dd230d..051e5b2 100644 --- a/gems/dexpace-core/sig/dexpace/configuration/builder.rbs +++ b/gems/dexpace-core/sig/dexpace/configuration/builder.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Configuration class Builder @@ -12,6 +14,16 @@ module Dexpace def env_source=: (_ConfigSource source) -> void def build: () -> Configuration + # Builder::Layered is a private_constant and not public API: declared because the strict + # `core` Steep target types #composed_source. + class Layered + attr_reader entries: Hash[String, String] + attr_reader base: _ConfigSource + + def initialize: (Hash[untyped, untyped] entries, _ConfigSource base) -> void + def call: (String key) -> String? + end + private @overrides: Hash[String, String] diff --git a/gems/dexpace-core/sig/dexpace/configuration/keys.rbs b/gems/dexpace-core/sig/dexpace/configuration/keys.rbs index 4f7c7e3..b38667e 100644 --- a/gems/dexpace-core/sig/dexpace/configuration/keys.rbs +++ b/gems/dexpace-core/sig/dexpace/configuration/keys.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Configuration module Keys diff --git a/gems/dexpace-core/sig/dexpace/configuration/parsers.rbs b/gems/dexpace-core/sig/dexpace/configuration/parsers.rbs index f63e83d..78650ba 100644 --- a/gems/dexpace-core/sig/dexpace/configuration/parsers.rbs +++ b/gems/dexpace-core/sig/dexpace/configuration/parsers.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::ConfigParsers is a private_constant and not public API: this declaration exists # because the strict `core` Steep target checks every file under lib/ and needs the module and # its three functions declared to type their call sites in configuration.rb. RBS has no diff --git a/gems/dexpace-core/sig/dexpace/configuration/sources.rbs b/gems/dexpace-core/sig/dexpace/configuration/sources.rbs index c18e325..c7959cd 100644 --- a/gems/dexpace-core/sig/dexpace/configuration/sources.rbs +++ b/gems/dexpace-core/sig/dexpace/configuration/sources.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace # CFG-11's seam: a callable from key name to String?. What a fake satisfies and what every # Configuration member and builder writer types against. diff --git a/gems/dexpace-core/sig/dexpace/context.rbs b/gems/dexpace-core/sig/dexpace/context.rbs index 315910f..9b21ef9 100644 --- a/gems/dexpace-core/sig/dexpace/context.rbs +++ b/gems/dexpace-core/sig/dexpace/context.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace # What an includer of Context must be beyond a Model: a Data whose members include the store # #close releases through and the key it is registered under. The store is `untyped` because diff --git a/gems/dexpace-core/sig/dexpace/context/call_key.rbs b/gems/dexpace-core/sig/dexpace/context/call_key.rbs index e2f66c2..6061cee 100644 --- a/gems/dexpace-core/sig/dexpace/context/call_key.rbs +++ b/gems/dexpace-core/sig/dexpace/context/call_key.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::CallKey is a private_constant and not public API (design P4-3): this declaration # exists because the strict `core` Steep target checks every file under lib/ and needs the module # and its one method declared to type the three .build call sites, exactly as hooks.rbs does for diff --git a/gems/dexpace-core/sig/dexpace/context/dispatch_context.rbs b/gems/dexpace-core/sig/dexpace/context/dispatch_context.rbs index dc62215..9454eb8 100644 --- a/gems/dexpace-core/sig/dexpace/context/dispatch_context.rbs +++ b/gems/dexpace-core/sig/dexpace/context/dispatch_context.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class DispatchContext < Data include Context diff --git a/gems/dexpace-core/sig/dexpace/context/exchange_context.rbs b/gems/dexpace-core/sig/dexpace/context/exchange_context.rbs index c905d81..9388118 100644 --- a/gems/dexpace-core/sig/dexpace/context/exchange_context.rbs +++ b/gems/dexpace-core/sig/dexpace/context/exchange_context.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class ExchangeContext < Data include Context diff --git a/gems/dexpace-core/sig/dexpace/context/request_context.rbs b/gems/dexpace-core/sig/dexpace/context/request_context.rbs index 73a175e..2f64844 100644 --- a/gems/dexpace-core/sig/dexpace/context/request_context.rbs +++ b/gems/dexpace-core/sig/dexpace/context/request_context.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class RequestContext < Data include Context diff --git a/gems/dexpace-core/sig/dexpace/context_store.rbs b/gems/dexpace-core/sig/dexpace/context_store.rbs index 71cfbf9..e538d3f 100644 --- a/gems/dexpace-core/sig/dexpace/context_store.rbs +++ b/gems/dexpace-core/sig/dexpace/context_store.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class ContextStore MAX_TRACKED_CONTEXTS: Integer diff --git a/gems/dexpace-core/sig/dexpace/deep_value.rbs b/gems/dexpace-core/sig/dexpace/deep_value.rbs index c999c96..e143d0d 100644 --- a/gems/dexpace-core/sig/dexpace/deep_value.rbs +++ b/gems/dexpace-core/sig/dexpace/deep_value.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::DeepValue is a private_constant and not public API (R4): this declaration exists # because the strict `core` Steep target checks every file under lib/, and a later # dexpace-conformance suite reaches it by bare name from inside `module Dexpace`. RBS has no diff --git a/gems/dexpace-core/sig/dexpace/each_cause.rbs b/gems/dexpace-core/sig/dexpace/each_cause.rbs index 1dace63..4034068 100644 --- a/gems/dexpace-core/sig/dexpace/each_cause.rbs +++ b/gems/dexpace-core/sig/dexpace/each_cause.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace def self.each_cause: (::Exception error) -> Enumerator[::Exception, nil] | (::Exception error) { (::Exception) -> void } -> nil diff --git a/gems/dexpace-core/sig/dexpace/error.rbs b/gems/dexpace-core/sig/dexpace/error.rbs index 97cf37f..8a9abc4 100644 --- a/gems/dexpace-core/sig/dexpace/error.rbs +++ b/gems/dexpace-core/sig/dexpace/error.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace # The same self-type as Suppressible's: a module including a self-typed module must satisfy # that self-type, and every including class is an Exception by phase 2's shape. diff --git a/gems/dexpace-core/sig/dexpace/error/auth_resolution_error.rbs b/gems/dexpace-core/sig/dexpace/error/auth_resolution_error.rbs index 470076f..fa6939f 100644 --- a/gems/dexpace-core/sig/dexpace/error/auth_resolution_error.rbs +++ b/gems/dexpace-core/sig/dexpace/error/auth_resolution_error.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace # AUTH-6's resolution failure, flat under Dexpace in phase 2's error shape. class AuthResolutionError < ::StandardError diff --git a/gems/dexpace-core/sig/dexpace/error/cancelled_error.rbs b/gems/dexpace-core/sig/dexpace/error/cancelled_error.rbs index 7fd1484..e7f57b8 100644 --- a/gems/dexpace-core/sig/dexpace/error/cancelled_error.rbs +++ b/gems/dexpace-core/sig/dexpace/error/cancelled_error.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class CancelledError < ::StandardError include Dexpace::Error diff --git a/gems/dexpace-core/sig/dexpace/error/closed_error.rbs b/gems/dexpace-core/sig/dexpace/error/closed_error.rbs index 817502d..e4a4c95 100644 --- a/gems/dexpace-core/sig/dexpace/error/closed_error.rbs +++ b/gems/dexpace-core/sig/dexpace/error/closed_error.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class ClosedError < ::StandardError include Dexpace::Error diff --git a/gems/dexpace-core/sig/dexpace/error/context_conflict_error.rbs b/gems/dexpace-core/sig/dexpace/error/context_conflict_error.rbs index 0d61dc8..301dc24 100644 --- a/gems/dexpace-core/sig/dexpace/error/context_conflict_error.rbs +++ b/gems/dexpace-core/sig/dexpace/error/context_conflict_error.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class ContextConflictError < ::StandardError include Dexpace::Error diff --git a/gems/dexpace-core/sig/dexpace/error/end_of_stream_error.rbs b/gems/dexpace-core/sig/dexpace/error/end_of_stream_error.rbs index b0daf2c..32ac40d 100644 --- a/gems/dexpace-core/sig/dexpace/error/end_of_stream_error.rbs +++ b/gems/dexpace-core/sig/dexpace/error/end_of_stream_error.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class EndOfStreamError < ::EOFError include Dexpace::Error diff --git a/gems/dexpace-core/sig/dexpace/error/invalid_argument_error.rbs b/gems/dexpace-core/sig/dexpace/error/invalid_argument_error.rbs index c1e062b..b6389f5 100644 --- a/gems/dexpace-core/sig/dexpace/error/invalid_argument_error.rbs +++ b/gems/dexpace-core/sig/dexpace/error/invalid_argument_error.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class InvalidArgumentError < ::ArgumentError include Dexpace::Error diff --git a/gems/dexpace-core/sig/dexpace/error/not_replayable_error.rbs b/gems/dexpace-core/sig/dexpace/error/not_replayable_error.rbs index 6d56027..98afbfa 100644 --- a/gems/dexpace-core/sig/dexpace/error/not_replayable_error.rbs +++ b/gems/dexpace-core/sig/dexpace/error/not_replayable_error.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace # REDIR-6: the clear error for a present, non-replayable body at a re-send site. class NotReplayableError < ::StandardError diff --git a/gems/dexpace-core/sig/dexpace/error/outcome_error.rbs b/gems/dexpace-core/sig/dexpace/error/outcome_error.rbs index 4b3ad52..0a67207 100644 --- a/gems/dexpace-core/sig/dexpace/error/outcome_error.rbs +++ b/gems/dexpace-core/sig/dexpace/error/outcome_error.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class OutcomeError < ::StandardError include Dexpace::Error diff --git a/gems/dexpace-core/sig/dexpace/error/pipeline_error.rbs b/gems/dexpace-core/sig/dexpace/error/pipeline_error.rbs index cb60560..5173cc2 100644 --- a/gems/dexpace-core/sig/dexpace/error/pipeline_error.rbs +++ b/gems/dexpace-core/sig/dexpace/error/pipeline_error.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class PipelineError < ::StandardError include Dexpace::Error diff --git a/gems/dexpace-core/sig/dexpace/error/protocol_error.rbs b/gems/dexpace-core/sig/dexpace/error/protocol_error.rbs index 33b512f..e64a1d0 100644 --- a/gems/dexpace-core/sig/dexpace/error/protocol_error.rbs +++ b/gems/dexpace-core/sig/dexpace/error/protocol_error.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class ProtocolError < ::StandardError include Dexpace::Error diff --git a/gems/dexpace-core/sig/dexpace/error/retry_predicate_error.rbs b/gems/dexpace-core/sig/dexpace/error/retry_predicate_error.rbs index 1e33088..daceec0 100644 --- a/gems/dexpace-core/sig/dexpace/error/retry_predicate_error.rbs +++ b/gems/dexpace-core/sig/dexpace/error/retry_predicate_error.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class RetryPredicateError < ::StandardError include Dexpace::Error diff --git a/gems/dexpace-core/sig/dexpace/error/seam_error.rbs b/gems/dexpace-core/sig/dexpace/error/seam_error.rbs index fe6c0ab..3a8b2a5 100644 --- a/gems/dexpace-core/sig/dexpace/error/seam_error.rbs +++ b/gems/dexpace-core/sig/dexpace/error/seam_error.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class SeamError < ::StandardError include Dexpace::Error diff --git a/gems/dexpace-core/sig/dexpace/error/stream_error.rbs b/gems/dexpace-core/sig/dexpace/error/stream_error.rbs index 1d726ea..127829c 100644 --- a/gems/dexpace-core/sig/dexpace/error/stream_error.rbs +++ b/gems/dexpace-core/sig/dexpace/error/stream_error.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class StreamError < ::IOError include Dexpace::Error diff --git a/gems/dexpace-core/sig/dexpace/error/transport_error.rbs b/gems/dexpace-core/sig/dexpace/error/transport_error.rbs index f7036fa..fe85864 100644 --- a/gems/dexpace-core/sig/dexpace/error/transport_error.rbs +++ b/gems/dexpace-core/sig/dexpace/error/transport_error.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace # TRANSPORT-20 and XCUT-4 branch (b): the always-retryable transport failure, a sibling of # StreamError inside ::IOError (P3-3). Phase 8's phase-level task, landed by 8a. diff --git a/gems/dexpace-core/sig/dexpace/hooks.rbs b/gems/dexpace-core/sig/dexpace/hooks.rbs index 7c930c7..40ead7c 100644 --- a/gems/dexpace-core/sig/dexpace/hooks.rbs +++ b/gems/dexpace-core/sig/dexpace/hooks.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Hooks is a private_constant and not public API (design P2-15): this declaration exists # because the strict `core` Steep target checks every file under lib/ and needs the module and # its one method declared to type the three call sites. RBS has no visibility for a constant, so diff --git a/gems/dexpace-core/sig/dexpace/http/body.rbs b/gems/dexpace-core/sig/dexpace/http/body.rbs index 7073456..4133403 100644 --- a/gems/dexpace-core/sig/dexpace/http/body.rbs +++ b/gems/dexpace-core/sig/dexpace/http/body.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace # HTTP-44/HTTP-45's handler contract (R7): any object responding to #call(response). It names # Dexpace::Response and nothing of phase 7's, so NFR-11's scan stays clean and phase 7 supplies diff --git a/gems/dexpace-core/sig/dexpace/http/body/buffer_body.rbs b/gems/dexpace-core/sig/dexpace/http/body/buffer_body.rbs index c91fa3e..d9b4c1d 100644 --- a/gems/dexpace-core/sig/dexpace/http/body/buffer_body.rbs +++ b/gems/dexpace-core/sig/dexpace/http/body/buffer_body.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class BufferBody include Dexpace::Body diff --git a/gems/dexpace-core/sig/dexpace/http/body/bytes_body.rbs b/gems/dexpace-core/sig/dexpace/http/body/bytes_body.rbs index 3e1dfca..37a91f7 100644 --- a/gems/dexpace-core/sig/dexpace/http/body/bytes_body.rbs +++ b/gems/dexpace-core/sig/dexpace/http/body/bytes_body.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class BytesBody include Dexpace::Body diff --git a/gems/dexpace-core/sig/dexpace/http/body/chunked_body.rbs b/gems/dexpace-core/sig/dexpace/http/body/chunked_body.rbs index a4bdb1c..6ca6b9c 100644 --- a/gems/dexpace-core/sig/dexpace/http/body/chunked_body.rbs +++ b/gems/dexpace-core/sig/dexpace/http/body/chunked_body.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class ChunkedBody include Dexpace::Body diff --git a/gems/dexpace-core/sig/dexpace/http/body/file_body.rbs b/gems/dexpace-core/sig/dexpace/http/body/file_body.rbs index 06135f8..79546af 100644 --- a/gems/dexpace-core/sig/dexpace/http/body/file_body.rbs +++ b/gems/dexpace-core/sig/dexpace/http/body/file_body.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class FileBody include Dexpace::Body diff --git a/gems/dexpace-core/sig/dexpace/http/body/form_body.rbs b/gems/dexpace-core/sig/dexpace/http/body/form_body.rbs index a726a32..9d86b5e 100644 --- a/gems/dexpace-core/sig/dexpace/http/body/form_body.rbs +++ b/gems/dexpace-core/sig/dexpace/http/body/form_body.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class FormBody include Dexpace::Body diff --git a/gems/dexpace-core/sig/dexpace/http/body/multipart_body.rbs b/gems/dexpace-core/sig/dexpace/http/body/multipart_body.rbs index 14cbd37..297c249 100644 --- a/gems/dexpace-core/sig/dexpace/http/body/multipart_body.rbs +++ b/gems/dexpace-core/sig/dexpace/http/body/multipart_body.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class MultipartBody include Dexpace::Body diff --git a/gems/dexpace-core/sig/dexpace/http/body/request_logging_body.rbs b/gems/dexpace-core/sig/dexpace/http/body/request_logging_body.rbs index ab0839c..0629cc1 100644 --- a/gems/dexpace-core/sig/dexpace/http/body/request_logging_body.rbs +++ b/gems/dexpace-core/sig/dexpace/http/body/request_logging_body.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class RequestLoggingBody include Dexpace::Body diff --git a/gems/dexpace-core/sig/dexpace/http/body/response_body.rbs b/gems/dexpace-core/sig/dexpace/http/body/response_body.rbs index f3e361f..2d0eb76 100644 --- a/gems/dexpace-core/sig/dexpace/http/body/response_body.rbs +++ b/gems/dexpace-core/sig/dexpace/http/body/response_body.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class ResponseBody include Dexpace::Body diff --git a/gems/dexpace-core/sig/dexpace/http/body/response_logging_body.rbs b/gems/dexpace-core/sig/dexpace/http/body/response_logging_body.rbs index 09679ee..86693d0 100644 --- a/gems/dexpace-core/sig/dexpace/http/body/response_logging_body.rbs +++ b/gems/dexpace-core/sig/dexpace/http/body/response_logging_body.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class ResponseLoggingBody include Dexpace::Body diff --git a/gems/dexpace-core/sig/dexpace/http/body/stream_body.rbs b/gems/dexpace-core/sig/dexpace/http/body/stream_body.rbs index ef29cd0..acf3959 100644 --- a/gems/dexpace-core/sig/dexpace/http/body/stream_body.rbs +++ b/gems/dexpace-core/sig/dexpace/http/body/stream_body.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class StreamBody include Dexpace::Body @@ -21,6 +23,7 @@ module Dexpace @close: bool @rewindable: bool + def refuse_closed!: (untyped io) -> void def probe_rewindability: (untyped io) -> [Integer, bool] def write_once: (Dexpace::IO::_Sink sink) -> Integer def write_replayable: (Dexpace::IO::_Sink sink) -> Integer diff --git a/gems/dexpace-core/sig/dexpace/http/header_name.rbs b/gems/dexpace-core/sig/dexpace/http/header_name.rbs index 5a0a729..d2acd68 100644 --- a/gems/dexpace-core/sig/dexpace/http/header_name.rbs +++ b/gems/dexpace-core/sig/dexpace/http/header_name.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class HeaderName < Data include Model diff --git a/gems/dexpace-core/sig/dexpace/http/header_syntax.rbs b/gems/dexpace-core/sig/dexpace/http/header_syntax.rbs index 670e0c8..6247891 100644 --- a/gems/dexpace-core/sig/dexpace/http/header_syntax.rbs +++ b/gems/dexpace-core/sig/dexpace/http/header_syntax.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module HeaderSyntax HTAB: Integer diff --git a/gems/dexpace-core/sig/dexpace/http/headers.rbs b/gems/dexpace-core/sig/dexpace/http/headers.rbs index 9296fab..5d3e7f3 100644 --- a/gems/dexpace-core/sig/dexpace/http/headers.rbs +++ b/gems/dexpace-core/sig/dexpace/http/headers.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Headers < Data include Model diff --git a/gems/dexpace-core/sig/dexpace/http/headers/builder.rbs b/gems/dexpace-core/sig/dexpace/http/headers/builder.rbs index 40b2b50..3701396 100644 --- a/gems/dexpace-core/sig/dexpace/http/headers/builder.rbs +++ b/gems/dexpace-core/sig/dexpace/http/headers/builder.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Headers class Builder diff --git a/gems/dexpace-core/sig/dexpace/http/media_type.rbs b/gems/dexpace-core/sig/dexpace/http/media_type.rbs index 5ade8fb..91020fc 100644 --- a/gems/dexpace-core/sig/dexpace/http/media_type.rbs +++ b/gems/dexpace-core/sig/dexpace/http/media_type.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class MediaType < Data include Model diff --git a/gems/dexpace-core/sig/dexpace/http/method.rbs b/gems/dexpace-core/sig/dexpace/http/method.rbs index 533f859..ef5a00b 100644 --- a/gems/dexpace-core/sig/dexpace/http/method.rbs +++ b/gems/dexpace-core/sig/dexpace/http/method.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Method < Data include Model diff --git a/gems/dexpace-core/sig/dexpace/http/percent_encoding.rbs b/gems/dexpace-core/sig/dexpace/http/percent_encoding.rbs index 32ff317..17b6053 100644 --- a/gems/dexpace-core/sig/dexpace/http/percent_encoding.rbs +++ b/gems/dexpace-core/sig/dexpace/http/percent_encoding.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module PercentEncoding UNRESERVED: Array[Integer] diff --git a/gems/dexpace-core/sig/dexpace/http/protocol.rbs b/gems/dexpace-core/sig/dexpace/http/protocol.rbs index e4f6bda..881cbd3 100644 --- a/gems/dexpace-core/sig/dexpace/http/protocol.rbs +++ b/gems/dexpace-core/sig/dexpace/http/protocol.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Protocol < Data include Model @@ -5,6 +7,7 @@ module Dexpace WIRE_FORMS: Array[String] ALIASES: Hash[String, String] + HTTP_1_0: Protocol HTTP_1_1: Protocol HTTP_2: Protocol diff --git a/gems/dexpace-core/sig/dexpace/http/query.rbs b/gems/dexpace-core/sig/dexpace/http/query.rbs index fe373f3..75fec8f 100644 --- a/gems/dexpace-core/sig/dexpace/http/query.rbs +++ b/gems/dexpace-core/sig/dexpace/http/query.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Query < Data include Model diff --git a/gems/dexpace-core/sig/dexpace/http/query/builder.rbs b/gems/dexpace-core/sig/dexpace/http/query/builder.rbs index cd49dfb..af67fde 100644 --- a/gems/dexpace-core/sig/dexpace/http/query/builder.rbs +++ b/gems/dexpace-core/sig/dexpace/http/query/builder.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Query class Builder diff --git a/gems/dexpace-core/sig/dexpace/http/request.rbs b/gems/dexpace-core/sig/dexpace/http/request.rbs index 04dada4..7c15898 100644 --- a/gems/dexpace-core/sig/dexpace/http/request.rbs +++ b/gems/dexpace-core/sig/dexpace/http/request.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Request < Data include Model diff --git a/gems/dexpace-core/sig/dexpace/http/request/builder.rbs b/gems/dexpace-core/sig/dexpace/http/request/builder.rbs index 3fbffa8..9654573 100644 --- a/gems/dexpace-core/sig/dexpace/http/request/builder.rbs +++ b/gems/dexpace-core/sig/dexpace/http/request/builder.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Request class Builder diff --git a/gems/dexpace-core/sig/dexpace/http/request_options.rbs b/gems/dexpace-core/sig/dexpace/http/request_options.rbs index d979f2c..8c9b4e6 100644 --- a/gems/dexpace-core/sig/dexpace/http/request_options.rbs +++ b/gems/dexpace-core/sig/dexpace/http/request_options.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class RequestOptions < Data include Model diff --git a/gems/dexpace-core/sig/dexpace/http/request_options/builder.rbs b/gems/dexpace-core/sig/dexpace/http/request_options/builder.rbs index c098d8f..442212f 100644 --- a/gems/dexpace-core/sig/dexpace/http/request_options/builder.rbs +++ b/gems/dexpace-core/sig/dexpace/http/request_options/builder.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class RequestOptions class Builder diff --git a/gems/dexpace-core/sig/dexpace/http/response.rbs b/gems/dexpace-core/sig/dexpace/http/response.rbs index ccd18e8..fb9d6de 100644 --- a/gems/dexpace-core/sig/dexpace/http/response.rbs +++ b/gems/dexpace-core/sig/dexpace/http/response.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Response < Data include Model diff --git a/gems/dexpace-core/sig/dexpace/http/response/builder.rbs b/gems/dexpace-core/sig/dexpace/http/response/builder.rbs index 7c329d4..17f60b7 100644 --- a/gems/dexpace-core/sig/dexpace/http/response/builder.rbs +++ b/gems/dexpace-core/sig/dexpace/http/response/builder.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Response class Builder diff --git a/gems/dexpace-core/sig/dexpace/http/status.rbs b/gems/dexpace-core/sig/dexpace/http/status.rbs index f1a2ae5..dbe8919 100644 --- a/gems/dexpace-core/sig/dexpace/http/status.rbs +++ b/gems/dexpace-core/sig/dexpace/http/status.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Status < Data include Model @@ -36,5 +38,6 @@ module Dexpace def client_error?: () -> bool def server_error?: () -> bool def error?: () -> bool + def standard?: () -> bool end end diff --git a/gems/dexpace-core/sig/dexpace/http/typed_response.rbs b/gems/dexpace-core/sig/dexpace/http/typed_response.rbs index b48a7a2..55f6ca7 100644 --- a/gems/dexpace-core/sig/dexpace/http/typed_response.rbs +++ b/gems/dexpace-core/sig/dexpace/http/typed_response.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class TypedResponse attr_reader response: Dexpace::Response diff --git a/gems/dexpace-core/sig/dexpace/http/url.rbs b/gems/dexpace-core/sig/dexpace/http/url.rbs index 11d80e3..1edd4e2 100644 --- a/gems/dexpace-core/sig/dexpace/http/url.rbs +++ b/gems/dexpace-core/sig/dexpace/http/url.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module URL def self?.parse!: (String | URI::Generic input) -> URI::Generic @@ -7,5 +9,8 @@ module Dexpace private def own: (URI::Generic uri) -> URI::Generic + def string_or_uri!: (untyped input) -> void + def refuse_hostless!: (URI::Generic uri, String | URI::Generic input) -> void + def renderable!: (URI::Generic uri, String | URI::Generic input) -> URI::Generic end end diff --git a/gems/dexpace-core/sig/dexpace/http_date.rbs b/gems/dexpace-core/sig/dexpace/http_date.rbs index 9712ae7..2bfeda9 100644 --- a/gems/dexpace-core/sig/dexpace/http_date.rbs +++ b/gems/dexpace-core/sig/dexpace/http_date.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module HTTPDate # Both private_constant; declared because the strict `core` Steep target types their uses. diff --git a/gems/dexpace-core/sig/dexpace/instrumentation/async_step.rbs b/gems/dexpace-core/sig/dexpace/instrumentation/async_step.rbs index 499de25..911a98d 100644 --- a/gems/dexpace-core/sig/dexpace/instrumentation/async_step.rbs +++ b/gems/dexpace-core/sig/dexpace/instrumentation/async_step.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Instrumentation # The async runtime's step over 4c's _AsyncStep: the same .build, the same Emitter (P5-34). @@ -24,7 +26,7 @@ module Dexpace def head: (Dexpace::Request request, Pipeline::Cursor cursor) -> [Dexpace::Async::Future, Pending] def attach: (Dexpace::Async::Future future, Pending pending) -> Dexpace::Async::Future def settle_response: (Pending pending, Dexpace::Response response) -> Dexpace::Response - def settle: (Pending pending, Dexpace::Async::Settlement settlement) -> void + def settle: (Pending pending, Dexpace::Async::Settlement settlement, ?Float? ended) -> void def bridged: [T] (Hash[Symbol, untyped]? snapshot) { () -> T } -> T end end diff --git a/gems/dexpace-core/sig/dexpace/instrumentation/bundle.rbs b/gems/dexpace-core/sig/dexpace/instrumentation/bundle.rbs index 9df22aa..6374057 100644 --- a/gems/dexpace-core/sig/dexpace/instrumentation/bundle.rbs +++ b/gems/dexpace-core/sig/dexpace/instrumentation/bundle.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Instrumentation class Bundle < Data diff --git a/gems/dexpace-core/sig/dexpace/instrumentation/callable_adapter.rbs b/gems/dexpace-core/sig/dexpace/instrumentation/callable_adapter.rbs index 9206eb1..0631bcc 100644 --- a/gems/dexpace-core/sig/dexpace/instrumentation/callable_adapter.rbs +++ b/gems/dexpace-core/sig/dexpace/instrumentation/callable_adapter.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Instrumentation # Design §8.1's bus adapter over one `#call(name, payload)` object; the eleven overrides diff --git a/gems/dexpace-core/sig/dexpace/instrumentation/contain.rbs b/gems/dexpace-core/sig/dexpace/instrumentation/contain.rbs index 5fa6150..9646876 100644 --- a/gems/dexpace-core/sig/dexpace/instrumentation/contain.rbs +++ b/gems/dexpace-core/sig/dexpace/instrumentation/contain.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Instrumentation # OBS-20, XCUT-20: the one containment primitive, a module function (P5-37). Returns nil diff --git a/gems/dexpace-core/sig/dexpace/instrumentation/diagnostics.rbs b/gems/dexpace-core/sig/dexpace/instrumentation/diagnostics.rbs index 9e025f3..14ef544 100644 --- a/gems/dexpace-core/sig/dexpace/instrumentation/diagnostics.rbs +++ b/gems/dexpace-core/sig/dexpace/instrumentation/diagnostics.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace # OBS-24's "immutable snapshot": what Diagnostics.with installs -- a frozen Hash from # Diagnostics.capture, or anything else that yields Symbol-keyed pairs and names its keys. diff --git a/gems/dexpace-core/sig/dexpace/instrumentation/emitter.rbs b/gems/dexpace-core/sig/dexpace/instrumentation/emitter.rbs index e7cbda8..1139583 100644 --- a/gems/dexpace-core/sig/dexpace/instrumentation/emitter.rbs +++ b/gems/dexpace-core/sig/dexpace/instrumentation/emitter.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Instrumentation::Emitter is a private_constant and not public API (P2-15, P4-3): this # declaration exists because the strict `core` Steep target checks every file under lib/ and # needs the class and its three writers declared to type Step and AsyncStep. RBS has no diff --git a/gems/dexpace-core/sig/dexpace/instrumentation/event.rbs b/gems/dexpace-core/sig/dexpace/instrumentation/event.rbs index 5cc27ff..5953458 100644 --- a/gems/dexpace-core/sig/dexpace/instrumentation/event.rbs +++ b/gems/dexpace-core/sig/dexpace/instrumentation/event.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Instrumentation # A private_constant with no visibility in RBS; declared because the strict `core` Steep diff --git a/gems/dexpace-core/sig/dexpace/instrumentation/http_logging.rbs b/gems/dexpace-core/sig/dexpace/instrumentation/http_logging.rbs index 52c4eb1..6de0658 100644 --- a/gems/dexpace-core/sig/dexpace/instrumentation/http_logging.rbs +++ b/gems/dexpace-core/sig/dexpace/instrumentation/http_logging.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Instrumentation # OBS-34, OBS-35: a closed set of three. .new and .[] are both private and #with always diff --git a/gems/dexpace-core/sig/dexpace/instrumentation/http_tracer.rbs b/gems/dexpace-core/sig/dexpace/instrumentation/http_tracer.rbs index 7d41b43..bb78051 100644 --- a/gems/dexpace-core/sig/dexpace/instrumentation/http_tracer.rbs +++ b/gems/dexpace-core/sig/dexpace/instrumentation/http_tracer.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Instrumentation # OBS-28's eleven-method vocabulary: the module an implementer includes, and -- since phase 6a diff --git a/gems/dexpace-core/sig/dexpace/instrumentation/keys.rbs b/gems/dexpace-core/sig/dexpace/instrumentation/keys.rbs index e175f59..5285dc1 100644 --- a/gems/dexpace-core/sig/dexpace/instrumentation/keys.rbs +++ b/gems/dexpace-core/sig/dexpace/instrumentation/keys.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Instrumentation # OBS-39: the emitted field-key vocabulary, sixteen frozen Strings. diff --git a/gems/dexpace-core/sig/dexpace/instrumentation/logger.rbs b/gems/dexpace-core/sig/dexpace/instrumentation/logger.rbs index bdbedc1..63c6bdc 100644 --- a/gems/dexpace-core/sig/dexpace/instrumentation/logger.rbs +++ b/gems/dexpace-core/sig/dexpace/instrumentation/logger.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Instrumentation # OBS-1, OBS-2, OBS-9, OBS-10, OBS-40: the facade. Frozen after .build; NULL is the one over diff --git a/gems/dexpace-core/sig/dexpace/instrumentation/meter.rbs b/gems/dexpace-core/sig/dexpace/instrumentation/meter.rbs index 105b48b..d6402a0 100644 --- a/gems/dexpace-core/sig/dexpace/instrumentation/meter.rbs +++ b/gems/dexpace-core/sig/dexpace/instrumentation/meter.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Instrumentation # OBS-31's monotonic counter. OBS-33: only non-negative increments are valid; a negative diff --git a/gems/dexpace-core/sig/dexpace/instrumentation/no_span.rbs b/gems/dexpace-core/sig/dexpace/instrumentation/no_span.rbs index 8abe01c..f21dbab 100644 --- a/gems/dexpace-core/sig/dexpace/instrumentation/no_span.rbs +++ b/gems/dexpace-core/sig/dexpace/instrumentation/no_span.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Instrumentation # OBS-21's span protocol, as phase 5c fixed it (phase 4a declared this empty on purpose and diff --git a/gems/dexpace-core/sig/dexpace/instrumentation/no_tracer.rbs b/gems/dexpace-core/sig/dexpace/instrumentation/no_tracer.rbs index 651347c..c571a80 100644 --- a/gems/dexpace-core/sig/dexpace/instrumentation/no_tracer.rbs +++ b/gems/dexpace-core/sig/dexpace/instrumentation/no_tracer.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Instrumentation # OBS-25's tracer protocol, as phase 5c fixed it (phase 4a declared this empty on purpose and diff --git a/gems/dexpace-core/sig/dexpace/instrumentation/null_sink.rbs b/gems/dexpace-core/sig/dexpace/instrumentation/null_sink.rbs index b9dc571..58049c4 100644 --- a/gems/dexpace-core/sig/dexpace/instrumentation/null_sink.rbs +++ b/gems/dexpace-core/sig/dexpace/instrumentation/null_sink.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace # OBS-1, OBS-2, design §8.1: the sink duck type -- the stdlib Logger surface as a structural # subset, so a consumer's Logger drops in and core never requires `logger`. A signature typed diff --git a/gems/dexpace-core/sig/dexpace/instrumentation/preview.rbs b/gems/dexpace-core/sig/dexpace/instrumentation/preview.rbs index c334f22..fe5dbe2 100644 --- a/gems/dexpace-core/sig/dexpace/instrumentation/preview.rbs +++ b/gems/dexpace-core/sig/dexpace/instrumentation/preview.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Instrumentation # OBS-38: charset-aware text and binary-safe preview rendering, total. diff --git a/gems/dexpace-core/sig/dexpace/instrumentation/redaction_policy.rbs b/gems/dexpace-core/sig/dexpace/instrumentation/redaction_policy.rbs index 30026a8..67f7e6e 100644 --- a/gems/dexpace-core/sig/dexpace/instrumentation/redaction_policy.rbs +++ b/gems/dexpace-core/sig/dexpace/instrumentation/redaction_policy.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Instrumentation # OBS-12, OBS-17, OBS-18: three folded Sets and one boolean, no member for userinfo (OBS-11 is diff --git a/gems/dexpace-core/sig/dexpace/instrumentation/redactor.rbs b/gems/dexpace-core/sig/dexpace/instrumentation/redactor.rbs index 6da361f..784613b 100644 --- a/gems/dexpace-core/sig/dexpace/instrumentation/redactor.rbs +++ b/gems/dexpace-core/sig/dexpace/instrumentation/redactor.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Instrumentation # OBS-11..OBS-18, XCUT-19, XCUT-20: two total entry points over one frozen policy (P5-25). diff --git a/gems/dexpace-core/sig/dexpace/instrumentation/render.rbs b/gems/dexpace-core/sig/dexpace/instrumentation/render.rbs index aadbacf..dea158f 100644 --- a/gems/dexpace-core/sig/dexpace/instrumentation/render.rbs +++ b/gems/dexpace-core/sig/dexpace/instrumentation/render.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Instrumentation::Render is a private_constant and not public API (P2-15, P4-3): this # declaration exists because the strict `core` Steep target checks every file under lib/ and # needs the module and its one entry point declared to type Event#emit. RBS has no visibility diff --git a/gems/dexpace-core/sig/dexpace/instrumentation/scope.rbs b/gems/dexpace-core/sig/dexpace/instrumentation/scope.rbs index 61ae3aa..b28f653 100644 --- a/gems/dexpace-core/sig/dexpace/instrumentation/scope.rbs +++ b/gems/dexpace-core/sig/dexpace/instrumentation/scope.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Instrumentation # The current-span slot key: a private_constant shared by Scope and Tracing, declared here diff --git a/gems/dexpace-core/sig/dexpace/instrumentation/severity.rbs b/gems/dexpace-core/sig/dexpace/instrumentation/severity.rbs index 3929d8e..613b092 100644 --- a/gems/dexpace-core/sig/dexpace/instrumentation/severity.rbs +++ b/gems/dexpace-core/sig/dexpace/instrumentation/severity.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Instrumentation # OBS-2: a closed set of four. .new and .[] are both private, and #with always raises, so the diff --git a/gems/dexpace-core/sig/dexpace/instrumentation/step.rbs b/gems/dexpace-core/sig/dexpace/instrumentation/step.rbs index 35bbda3..3329af8 100644 --- a/gems/dexpace-core/sig/dexpace/instrumentation/step.rbs +++ b/gems/dexpace-core/sig/dexpace/instrumentation/step.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Instrumentation # OBS-34, OBS-36, OBS-39, OBS-20: the instrumentation step at Stages::LOGGING. The two slots @@ -34,8 +36,8 @@ module Dexpace # untyped: a _Span is what the tracer returns and `_Span & Object` is what Tracing takes # (5c's identity device); the step passes one straight to the other. def open_span: (Dexpace::Request request, Bundle bundle) -> untyped - def finish: (untyped span, Float started) -> void - def elapsed_ms: (Float started) -> Float + def finish: (untyped span, Float started, ?Float? ended) -> void + def elapsed_ms: (Float started, ?Float? ended) -> Float def logged?: () -> bool def body?: () -> bool def prepare: (Dexpace::Request request) -> Dexpace::Request diff --git a/gems/dexpace-core/sig/dexpace/instrumentation/trace_id_flavour.rbs b/gems/dexpace-core/sig/dexpace/instrumentation/trace_id_flavour.rbs index 1b073b9..fb23485 100644 --- a/gems/dexpace-core/sig/dexpace/instrumentation/trace_id_flavour.rbs +++ b/gems/dexpace-core/sig/dexpace/instrumentation/trace_id_flavour.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Instrumentation class TraceIdFlavour < Data diff --git a/gems/dexpace-core/sig/dexpace/instrumentation/tracing.rbs b/gems/dexpace-core/sig/dexpace/instrumentation/tracing.rbs index 74277d9..3b2725e 100644 --- a/gems/dexpace-core/sig/dexpace/instrumentation/tracing.rbs +++ b/gems/dexpace-core/sig/dexpace/instrumentation/tracing.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Instrumentation # OBS-22, OBS-23: module functions over one Fiber[] slot. The block forms are primary and diff --git a/gems/dexpace-core/sig/dexpace/io.rbs b/gems/dexpace-core/sig/dexpace/io.rbs index 9a43fb7..f16babe 100644 --- a/gems/dexpace-core/sig/dexpace/io.rbs +++ b/gems/dexpace-core/sig/dexpace/io.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module IO MAX_MATERIALIZED_BYTES: Integer diff --git a/gems/dexpace-core/sig/dexpace/io/buffer.rbs b/gems/dexpace-core/sig/dexpace/io/buffer.rbs index f048f07..2905940 100644 --- a/gems/dexpace-core/sig/dexpace/io/buffer.rbs +++ b/gems/dexpace-core/sig/dexpace/io/buffer.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module IO class Buffer diff --git a/gems/dexpace-core/sig/dexpace/io/buffered_sink.rbs b/gems/dexpace-core/sig/dexpace/io/buffered_sink.rbs index a480a75..e13673b 100644 --- a/gems/dexpace-core/sig/dexpace/io/buffered_sink.rbs +++ b/gems/dexpace-core/sig/dexpace/io/buffered_sink.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module IO class BufferedSink diff --git a/gems/dexpace-core/sig/dexpace/io/buffered_source.rbs b/gems/dexpace-core/sig/dexpace/io/buffered_source.rbs index 2032dfa..b7233d8 100644 --- a/gems/dexpace-core/sig/dexpace/io/buffered_source.rbs +++ b/gems/dexpace-core/sig/dexpace/io/buffered_source.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module IO class BufferedSource diff --git a/gems/dexpace-core/sig/dexpace/io/tee_sink.rbs b/gems/dexpace-core/sig/dexpace/io/tee_sink.rbs index 2461333..d4dcecd 100644 --- a/gems/dexpace-core/sig/dexpace/io/tee_sink.rbs +++ b/gems/dexpace-core/sig/dexpace/io/tee_sink.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module IO class TeeSink diff --git a/gems/dexpace-core/sig/dexpace/io/typed_reads.rbs b/gems/dexpace-core/sig/dexpace/io/typed_reads.rbs index b20e13a..e836712 100644 --- a/gems/dexpace-core/sig/dexpace/io/typed_reads.rbs +++ b/gems/dexpace-core/sig/dexpace/io/typed_reads.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module IO module TypedReads diff --git a/gems/dexpace-core/sig/dexpace/io/typed_writes.rbs b/gems/dexpace-core/sig/dexpace/io/typed_writes.rbs index e868335..97e9ff3 100644 --- a/gems/dexpace-core/sig/dexpace/io/typed_writes.rbs +++ b/gems/dexpace-core/sig/dexpace/io/typed_writes.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module IO module TypedWrites diff --git a/gems/dexpace-core/sig/dexpace/model.rbs b/gems/dexpace-core/sig/dexpace/model.rbs index c1ff827..4e56459 100644 --- a/gems/dexpace-core/sig/dexpace/model.rbs +++ b/gems/dexpace-core/sig/dexpace/model.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace # What an object that includes Model must be: a Data, so `to_h` yields its members, whose class # carries a validating `.build` taking those members as keywords. That factory's keyword list diff --git a/gems/dexpace-core/sig/dexpace/operation.rbs b/gems/dexpace-core/sig/dexpace/operation.rbs index 2b73c84..62dd39f 100644 --- a/gems/dexpace-core/sig/dexpace/operation.rbs +++ b/gems/dexpace-core/sig/dexpace/operation.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Operation < Data include Dexpace::Model diff --git a/gems/dexpace-core/sig/dexpace/outcome.rbs b/gems/dexpace-core/sig/dexpace/outcome.rbs index 282ecdf..9c1efea 100644 --- a/gems/dexpace-core/sig/dexpace/outcome.rbs +++ b/gems/dexpace-core/sig/dexpace/outcome.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace # The closed sum type both variants include; `t` is the union every fold site is checked # against. diff --git a/gems/dexpace-core/sig/dexpace/outcome/failure.rbs b/gems/dexpace-core/sig/dexpace/outcome/failure.rbs index 78261f0..79614f0 100644 --- a/gems/dexpace-core/sig/dexpace/outcome/failure.rbs +++ b/gems/dexpace-core/sig/dexpace/outcome/failure.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Outcome class Failure < Data diff --git a/gems/dexpace-core/sig/dexpace/outcome/success.rbs b/gems/dexpace-core/sig/dexpace/outcome/success.rbs index cf8384b..cddc1c5 100644 --- a/gems/dexpace-core/sig/dexpace/outcome/success.rbs +++ b/gems/dexpace-core/sig/dexpace/outcome/success.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Outcome class Success < Data diff --git a/gems/dexpace-core/sig/dexpace/page.rbs b/gems/dexpace-core/sig/dexpace/page.rbs index 0508a2d..9126e7e 100644 --- a/gems/dexpace-core/sig/dexpace/page.rbs +++ b/gems/dexpace-core/sig/dexpace/page.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace # PAGE-2 / PAGE-3: one page of a paginated walk, owning exactly one live Response behind # Dexpace::Closeable's latch; also the namespace of the pagination subsystem (P7-2). The three diff --git a/gems/dexpace-core/sig/dexpace/page/async_paginator.rbs b/gems/dexpace-core/sig/dexpace/page/async_paginator.rbs index 640e8d9..178d232 100644 --- a/gems/dexpace-core/sig/dexpace/page/async_paginator.rbs +++ b/gems/dexpace-core/sig/dexpace/page/async_paginator.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Page # PAGE-25..PAGE-33: the non-blocking engine, a frozen Data of immutable configuration plus an diff --git a/gems/dexpace-core/sig/dexpace/page/closing.rbs b/gems/dexpace-core/sig/dexpace/page/closing.rbs index 2d6083e..036b1a7 100644 --- a/gems/dexpace-core/sig/dexpace/page/closing.rbs +++ b/gems/dexpace-core/sig/dexpace/page/closing.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Page::Closing is a private_constant and not public API: this declaration exists # because the strict `core` Steep target checks every file under lib/ and needs the module and # its two functions declared to type the views', the engines' and the async pump's call sites. diff --git a/gems/dexpace-core/sig/dexpace/page/cursor_strategy.rbs b/gems/dexpace-core/sig/dexpace/page/cursor_strategy.rbs index 1acbc02..56ef093 100644 --- a/gems/dexpace-core/sig/dexpace/page/cursor_strategy.rbs +++ b/gems/dexpace-core/sig/dexpace/page/cursor_strategy.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Page # PAGE-16: the cursor strategy over a caller-supplied `#call(response) -> [items, cursor]` diff --git a/gems/dexpace-core/sig/dexpace/page/fetchers.rbs b/gems/dexpace-core/sig/dexpace/page/fetchers.rbs index 48387e9..13f1d4e 100644 --- a/gems/dexpace-core/sig/dexpace/page/fetchers.rbs +++ b/gems/dexpace-core/sig/dexpace/page/fetchers.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Page # PAGE-34 / PAGE-35: the fetcher-based front-end, a frozen Data over two caller callables and diff --git a/gems/dexpace-core/sig/dexpace/page/info.rbs b/gems/dexpace-core/sig/dexpace/page/info.rbs index a4f19ca..3daa59e 100644 --- a/gems/dexpace-core/sig/dexpace/page/info.rbs +++ b/gems/dexpace-core/sig/dexpace/page/info.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Page # PAGE-4's PageInfo: items plus the next request, whose nil is the one end-of-stream signal. diff --git a/gems/dexpace-core/sig/dexpace/page/items.rbs b/gems/dexpace-core/sig/dexpace/page/items.rbs index de68a92..c5aebb5 100644 --- a/gems/dexpace-core/sig/dexpace/page/items.rbs +++ b/gems/dexpace-core/sig/dexpace/page/items.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Page # PAGE-1 / PAGE-8 / PAGE-11: the re-iterable item-level view, one fresh walk per #each, every diff --git a/gems/dexpace-core/sig/dexpace/page/link_header.rbs b/gems/dexpace-core/sig/dexpace/page/link_header.rbs index c1c1397..d11e1da 100644 --- a/gems/dexpace-core/sig/dexpace/page/link_header.rbs +++ b/gems/dexpace-core/sig/dexpace/page/link_header.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Page::LinkHeader is a private_constant and not public API: this declaration exists # because the strict `core` Steep target checks every file under lib/ and needs the module and # its one function declared to type LinkStrategy's call site. RBS has no visibility for a diff --git a/gems/dexpace-core/sig/dexpace/page/link_strategy.rbs b/gems/dexpace-core/sig/dexpace/page/link_strategy.rbs index 28df566..536eab8 100644 --- a/gems/dexpace-core/sig/dexpace/page/link_strategy.rbs +++ b/gems/dexpace-core/sig/dexpace/page/link_strategy.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Page # PAGE-18..PAGE-20: the Link-header strategy over a caller-supplied `#call(response) -> items` diff --git a/gems/dexpace-core/sig/dexpace/page/page_number_strategy.rbs b/gems/dexpace-core/sig/dexpace/page/page_number_strategy.rbs index 8cd76dc..88e062a 100644 --- a/gems/dexpace-core/sig/dexpace/page/page_number_strategy.rbs +++ b/gems/dexpace-core/sig/dexpace/page/page_number_strategy.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Page # PAGE-17: the page-number strategy over a caller-supplied `#call(response) -> items` extractor. diff --git a/gems/dexpace-core/sig/dexpace/page/page_state_error.rbs b/gems/dexpace-core/sig/dexpace/page/page_state_error.rbs index ace0b8e..3f3c796 100644 --- a/gems/dexpace-core/sig/dexpace/page/page_state_error.rbs +++ b/gems/dexpace-core/sig/dexpace/page/page_state_error.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Page # PAGE-14: a second #each on a single-use page view; a state error, never an ArgumentError. diff --git a/gems/dexpace-core/sig/dexpace/page/pages.rbs b/gems/dexpace-core/sig/dexpace/page/pages.rbs index ffb97aa..d0e73f8 100644 --- a/gems/dexpace-core/sig/dexpace/page/pages.rbs +++ b/gems/dexpace-core/sig/dexpace/page/pages.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Page # PAGE-1 / PAGE-12 / PAGE-14 / PAGE-15: the single-use, auto-closing page-level view over one diff --git a/gems/dexpace-core/sig/dexpace/page/paginator.rbs b/gems/dexpace-core/sig/dexpace/page/paginator.rbs index bdb9776..6785fd5 100644 --- a/gems/dexpace-core/sig/dexpace/page/paginator.rbs +++ b/gems/dexpace-core/sig/dexpace/page/paginator.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Page # PAGE-6..PAGE-10, PAGE-36: the blocking engine, a frozen Data of immutable configuration. diff --git a/gems/dexpace-core/sig/dexpace/page/query_rewriter.rbs b/gems/dexpace-core/sig/dexpace/page/query_rewriter.rbs index 279fe27..2db3f1e 100644 --- a/gems/dexpace-core/sig/dexpace/page/query_rewriter.rbs +++ b/gems/dexpace-core/sig/dexpace/page/query_rewriter.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Page # PAGE-21..PAGE-24: the verbatim query splice over the raw query substring. diff --git a/gems/dexpace-core/sig/dexpace/page/walk.rbs b/gems/dexpace-core/sig/dexpace/page/walk.rbs index 485bdb1..4deac04 100644 --- a/gems/dexpace-core/sig/dexpace/page/walk.rbs +++ b/gems/dexpace-core/sig/dexpace/page/walk.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Page::Walk is a private_constant and not public API: this declaration exists because # the strict `core` Steep target checks every file under lib/ and needs the class, its ivars and # its readers declared to type the two views and the two engines. RBS has no visibility for a diff --git a/gems/dexpace-core/sig/dexpace/pipeline.rbs b/gems/dexpace-core/sig/dexpace/pipeline.rbs index 6abb2a1..f6f78da 100644 --- a/gems/dexpace-core/sig/dexpace/pipeline.rbs +++ b/gems/dexpace-core/sig/dexpace/pipeline.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Pipeline include Dexpace::Closeable diff --git a/gems/dexpace-core/sig/dexpace/pipeline/async_driver.rbs b/gems/dexpace-core/sig/dexpace/pipeline/async_driver.rbs index 0c224f6..d54ec83 100644 --- a/gems/dexpace-core/sig/dexpace/pipeline/async_driver.rbs +++ b/gems/dexpace-core/sig/dexpace/pipeline/async_driver.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Pipeline::AsyncDriver is a private_constant and not public API (design P2-15): this # declaration exists because the strict `core` Steep target checks every file under lib/ and # needs the class and its methods declared to type its call sites in Cursor, Builder and diff --git a/gems/dexpace-core/sig/dexpace/pipeline/builder.rbs b/gems/dexpace-core/sig/dexpace/pipeline/builder.rbs index 2f7f91c..b6797fd 100644 --- a/gems/dexpace-core/sig/dexpace/pipeline/builder.rbs +++ b/gems/dexpace-core/sig/dexpace/pipeline/builder.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Pipeline class Builder diff --git a/gems/dexpace-core/sig/dexpace/pipeline/cursor.rbs b/gems/dexpace-core/sig/dexpace/pipeline/cursor.rbs index ece37dd..16548d5 100644 --- a/gems/dexpace-core/sig/dexpace/pipeline/cursor.rbs +++ b/gems/dexpace-core/sig/dexpace/pipeline/cursor.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Pipeline class Cursor diff --git a/gems/dexpace-core/sig/dexpace/pipeline/entry.rbs b/gems/dexpace-core/sig/dexpace/pipeline/entry.rbs index f3f6027..6d7663e 100644 --- a/gems/dexpace-core/sig/dexpace/pipeline/entry.rbs +++ b/gems/dexpace-core/sig/dexpace/pipeline/entry.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Pipeline class Entry < Data diff --git a/gems/dexpace-core/sig/dexpace/pipeline/stage.rbs b/gems/dexpace-core/sig/dexpace/pipeline/stage.rbs index 1b3de45..8108ef3 100644 --- a/gems/dexpace-core/sig/dexpace/pipeline/stage.rbs +++ b/gems/dexpace-core/sig/dexpace/pipeline/stage.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Pipeline class Stage < Data diff --git a/gems/dexpace-core/sig/dexpace/pipeline/stages.rbs b/gems/dexpace-core/sig/dexpace/pipeline/stages.rbs index 7da5a2f..7c3288c 100644 --- a/gems/dexpace-core/sig/dexpace/pipeline/stages.rbs +++ b/gems/dexpace-core/sig/dexpace/pipeline/stages.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Pipeline module Stages diff --git a/gems/dexpace-core/sig/dexpace/pipeline/step.rbs b/gems/dexpace-core/sig/dexpace/pipeline/step.rbs index 2949514..48bd218 100644 --- a/gems/dexpace-core/sig/dexpace/pipeline/step.rbs +++ b/gems/dexpace-core/sig/dexpace/pipeline/step.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Pipeline module Step diff --git a/gems/dexpace-core/sig/dexpace/pipeline/sync_driver.rbs b/gems/dexpace-core/sig/dexpace/pipeline/sync_driver.rbs index 56e44de..6a73541 100644 --- a/gems/dexpace-core/sig/dexpace/pipeline/sync_driver.rbs +++ b/gems/dexpace-core/sig/dexpace/pipeline/sync_driver.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Pipeline::SyncDriver is a private_constant and not public API (design P2-15): this # declaration exists because the strict `core` Steep target checks every file under lib/ and # needs the class and its two methods declared to type its call sites in Cursor, Builder and diff --git a/gems/dexpace-core/sig/dexpace/pipeline/transform_step.rbs b/gems/dexpace-core/sig/dexpace/pipeline/transform_step.rbs index 8322d0e..e00237d 100644 --- a/gems/dexpace-core/sig/dexpace/pipeline/transform_step.rbs +++ b/gems/dexpace-core/sig/dexpace/pipeline/transform_step.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Pipeline class TransformStep diff --git a/gems/dexpace-core/sig/dexpace/proxy.rbs b/gems/dexpace-core/sig/dexpace/proxy.rbs index d881d05..116a435 100644 --- a/gems/dexpace-core/sig/dexpace/proxy.rbs +++ b/gems/dexpace-core/sig/dexpace/proxy.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Proxy < Data include Model diff --git a/gems/dexpace-core/sig/dexpace/proxy/host_pattern.rbs b/gems/dexpace-core/sig/dexpace/proxy/host_pattern.rbs index 33bb6f5..825cc0d 100644 --- a/gems/dexpace-core/sig/dexpace/proxy/host_pattern.rbs +++ b/gems/dexpace-core/sig/dexpace/proxy/host_pattern.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Proxy class HostPattern < Data diff --git a/gems/dexpace-core/sig/dexpace/proxy/resolution.rbs b/gems/dexpace-core/sig/dexpace/proxy/resolution.rbs index 9a888d6..49e7abf 100644 --- a/gems/dexpace-core/sig/dexpace/proxy/resolution.rbs +++ b/gems/dexpace-core/sig/dexpace/proxy/resolution.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::ProxyResolution is a private_constant and not public API (P2-15): this declaration # exists because the strict `core` Steep target checks every file under lib/ and needs the module # and its one entry point declared to type Proxy.resolve. RBS has no visibility for a constant, so @@ -9,6 +11,7 @@ module Dexpace CRED_PASS: String NON_PROXY_PROP: String CREDENTIAL_BEFORE_AT: Regexp + AUTHORITY_START: Regexp SHOWN_AS: String def self?.resolve: (Configuration configuration, Instrumentation::Logger logger) -> Proxy? @@ -24,6 +27,7 @@ module Dexpace username: String?, password: String?) -> Proxy? def split_url: (String url, Instrumentation::Logger logger) -> Array[untyped] def shown: (String url) -> String + def scrub_userinfo: (String url) -> String def url_problem: (String? host, String? raw_port) -> String? def scheme_type: (String? scheme) -> Proxy::Type def credentials: (String? userinfo) -> Array[String?] diff --git a/gems/dexpace-core/sig/dexpace/proxy/type.rbs b/gems/dexpace-core/sig/dexpace/proxy/type.rbs index 6a0aff2..482cd20 100644 --- a/gems/dexpace-core/sig/dexpace/proxy/type.rbs +++ b/gems/dexpace-core/sig/dexpace/proxy/type.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Proxy class Type < Data diff --git a/gems/dexpace-core/sig/dexpace/recovery.rbs b/gems/dexpace-core/sig/dexpace/recovery.rbs index 8ba5cdc..31526cd 100644 --- a/gems/dexpace-core/sig/dexpace/recovery.rbs +++ b/gems/dexpace-core/sig/dexpace/recovery.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Recovery def self.buffer_error_body: (Dexpace::Response response) -> Dexpace::Response diff --git a/gems/dexpace-core/sig/dexpace/recovery/client_identity_step.rbs b/gems/dexpace-core/sig/dexpace/recovery/client_identity_step.rbs index 602e0e2..de92cf0 100644 --- a/gems/dexpace-core/sig/dexpace/recovery/client_identity_step.rbs +++ b/gems/dexpace-core/sig/dexpace/recovery/client_identity_step.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Recovery class ClientIdentityStep diff --git a/gems/dexpace-core/sig/dexpace/recovery/error_mapping_step.rbs b/gems/dexpace-core/sig/dexpace/recovery/error_mapping_step.rbs index 3c25a4a..f0e741f 100644 --- a/gems/dexpace-core/sig/dexpace/recovery/error_mapping_step.rbs +++ b/gems/dexpace-core/sig/dexpace/recovery/error_mapping_step.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Recovery class ErrorMappingStep diff --git a/gems/dexpace-core/sig/dexpace/recovery/idempotency_key_step.rbs b/gems/dexpace-core/sig/dexpace/recovery/idempotency_key_step.rbs index 2a8d80c..09a57bd 100644 --- a/gems/dexpace-core/sig/dexpace/recovery/idempotency_key_step.rbs +++ b/gems/dexpace-core/sig/dexpace/recovery/idempotency_key_step.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Recovery class IdempotencyKeyStep diff --git a/gems/dexpace-core/sig/dexpace/recovery/orchestrator.rbs b/gems/dexpace-core/sig/dexpace/recovery/orchestrator.rbs index cbe4404..f462bba 100644 --- a/gems/dexpace-core/sig/dexpace/recovery/orchestrator.rbs +++ b/gems/dexpace-core/sig/dexpace/recovery/orchestrator.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Recovery class Orchestrator diff --git a/gems/dexpace-core/sig/dexpace/recovery/ownership.rbs b/gems/dexpace-core/sig/dexpace/recovery/ownership.rbs index 621cb85..3230260 100644 --- a/gems/dexpace-core/sig/dexpace/recovery/ownership.rbs +++ b/gems/dexpace-core/sig/dexpace/recovery/ownership.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Recovery::Ownership is a private_constant and not public API (design P2-15): this # declaration exists because the strict `core` Steep target checks every file under lib/ and # needs the module and its one method declared to type the two call sites in ResponseChain. RBS diff --git a/gems/dexpace-core/sig/dexpace/recovery/request_chain.rbs b/gems/dexpace-core/sig/dexpace/recovery/request_chain.rbs index 282d6ac..dc3a58f 100644 --- a/gems/dexpace-core/sig/dexpace/recovery/request_chain.rbs +++ b/gems/dexpace-core/sig/dexpace/recovery/request_chain.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Recovery class RequestChain diff --git a/gems/dexpace-core/sig/dexpace/recovery/response_chain.rbs b/gems/dexpace-core/sig/dexpace/recovery/response_chain.rbs index e489855..b70f136 100644 --- a/gems/dexpace-core/sig/dexpace/recovery/response_chain.rbs +++ b/gems/dexpace-core/sig/dexpace/recovery/response_chain.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Recovery class ResponseChain diff --git a/gems/dexpace-core/sig/dexpace/recovery/transform.rbs b/gems/dexpace-core/sig/dexpace/recovery/transform.rbs index b62db08..deac068 100644 --- a/gems/dexpace-core/sig/dexpace/recovery/transform.rbs +++ b/gems/dexpace-core/sig/dexpace/recovery/transform.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Recovery # The static half of R8's contract -- what phase 4c's generic adapter names at its parameter. diff --git a/gems/dexpace-core/sig/dexpace/redirect/chain.rbs b/gems/dexpace-core/sig/dexpace/redirect/chain.rbs index aa57115..7cbfbef 100644 --- a/gems/dexpace-core/sig/dexpace/redirect/chain.rbs +++ b/gems/dexpace-core/sig/dexpace/redirect/chain.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Redirect::Chain is a private_constant and not public API: this declaration exists # because the strict `core` Steep target checks every file under lib/ and needs the class and # its readers declared to type Step. RBS has no visibility for a constant, so the privacy lives diff --git a/gems/dexpace-core/sig/dexpace/redirect/condition_snapshot.rbs b/gems/dexpace-core/sig/dexpace/redirect/condition_snapshot.rbs index a05749d..5c39a65 100644 --- a/gems/dexpace-core/sig/dexpace/redirect/condition_snapshot.rbs +++ b/gems/dexpace-core/sig/dexpace/redirect/condition_snapshot.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Redirect # REDIR-20: the read-only, defensively-copied condition snapshot a redirect predicate is diff --git a/gems/dexpace-core/sig/dexpace/redirect/emitter.rbs b/gems/dexpace-core/sig/dexpace/redirect/emitter.rbs index 983d59b..6380582 100644 --- a/gems/dexpace-core/sig/dexpace/redirect/emitter.rbs +++ b/gems/dexpace-core/sig/dexpace/redirect/emitter.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Redirect::Emitter is a private_constant and not public API: this declaration exists # because the strict `core` Steep target checks every file under lib/ and needs the class and # its four writers declared to type Step. RBS has no visibility for a constant, so the privacy diff --git a/gems/dexpace-core/sig/dexpace/redirect/events.rbs b/gems/dexpace-core/sig/dexpace/redirect/events.rbs index 0cf6260..2e99b78 100644 --- a/gems/dexpace-core/sig/dexpace/redirect/events.rbs +++ b/gems/dexpace-core/sig/dexpace/redirect/events.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Redirect # REDIR-28's event names, the redirect layer's own vocabulary. diff --git a/gems/dexpace-core/sig/dexpace/redirect/location.rbs b/gems/dexpace-core/sig/dexpace/redirect/location.rbs index 1b47270..47abb63 100644 --- a/gems/dexpace-core/sig/dexpace/redirect/location.rbs +++ b/gems/dexpace-core/sig/dexpace/redirect/location.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Redirect::Location is a private_constant and not public API: this declaration exists # because the strict `core` Steep target checks every file under lib/ and needs the module and # its one function declared to type Step's call site. RBS has no visibility for a constant, so diff --git a/gems/dexpace-core/sig/dexpace/redirect/origin.rbs b/gems/dexpace-core/sig/dexpace/redirect/origin.rbs index 285deba..c7e6b2d 100644 --- a/gems/dexpace-core/sig/dexpace/redirect/origin.rbs +++ b/gems/dexpace-core/sig/dexpace/redirect/origin.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Redirect::Origin is a private_constant and not public API: this declaration exists # because the strict `core` Steep target checks every file under lib/ and needs the module and # its two functions declared to type Step's call sites. RBS has no visibility for a constant, so diff --git a/gems/dexpace-core/sig/dexpace/redirect/reissue.rbs b/gems/dexpace-core/sig/dexpace/redirect/reissue.rbs index 1651a8b..70eb911 100644 --- a/gems/dexpace-core/sig/dexpace/redirect/reissue.rbs +++ b/gems/dexpace-core/sig/dexpace/redirect/reissue.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Redirect::Reissue is a private_constant and not public API: this declaration exists # because the strict `core` Steep target checks every file under lib/ and needs the module and # its one function declared to type Step's call site. RBS has no visibility for a constant, so diff --git a/gems/dexpace-core/sig/dexpace/redirect/scheme_downgrade_error.rbs b/gems/dexpace-core/sig/dexpace/redirect/scheme_downgrade_error.rbs index 7f5b015..145f927 100644 --- a/gems/dexpace-core/sig/dexpace/redirect/scheme_downgrade_error.rbs +++ b/gems/dexpace-core/sig/dexpace/redirect/scheme_downgrade_error.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Redirect # REDIR-15: an HTTPS -> HTTP hop refused because the caller did not opt in. diff --git a/gems/dexpace-core/sig/dexpace/redirect/step.rbs b/gems/dexpace-core/sig/dexpace/redirect/step.rbs index 49cda7d..f6ee19c 100644 --- a/gems/dexpace-core/sig/dexpace/redirect/step.rbs +++ b/gems/dexpace-core/sig/dexpace/redirect/step.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace # The redirect layer: chapter 10's synchronous pillar step and its vocabulary. module Redirect diff --git a/gems/dexpace-core/sig/dexpace/registry.rbs b/gems/dexpace-core/sig/dexpace/registry.rbs index a57cafe..44b88d4 100644 --- a/gems/dexpace-core/sig/dexpace/registry.rbs +++ b/gems/dexpace-core/sig/dexpace/registry.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace class Registry # Two private_constants with no visibility in RBS, declared so Steep can type the snapshot diff --git a/gems/dexpace-core/sig/dexpace/resilience/async_retry_step.rbs b/gems/dexpace-core/sig/dexpace/resilience/async_retry_step.rbs index 99b6577..937f501 100644 --- a/gems/dexpace-core/sig/dexpace/resilience/async_retry_step.rbs +++ b/gems/dexpace-core/sig/dexpace/resilience/async_retry_step.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Resilience # The asynchronous stage-based retry step at Stages::RETRY, over 4c's _AsyncStep. The keyword diff --git a/gems/dexpace-core/sig/dexpace/resilience/pacing_parsers.rbs b/gems/dexpace-core/sig/dexpace/resilience/pacing_parsers.rbs index 4c4cfe3..7b78cdc 100644 --- a/gems/dexpace-core/sig/dexpace/resilience/pacing_parsers.rbs +++ b/gems/dexpace-core/sig/dexpace/resilience/pacing_parsers.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Resilience::PacingParsers is a private_constant and not public API (P6-11): this # declaration exists because the strict `core` Steep target types Policy.pacing_delay's calls into # it. RBS has no visibility for a constant, so the privacy lives in diff --git a/gems/dexpace-core/sig/dexpace/resilience/policy.rbs b/gems/dexpace-core/sig/dexpace/resilience/policy.rbs index ee04eb9..6f65d6f 100644 --- a/gems/dexpace-core/sig/dexpace/resilience/policy.rbs +++ b/gems/dexpace-core/sig/dexpace/resilience/policy.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Resilience # The one shared retry policy core (RETRY-13, RECOV-30, R5). `random` is untyped on the two diff --git a/gems/dexpace-core/sig/dexpace/resilience/recovery_retry.rbs b/gems/dexpace-core/sig/dexpace/resilience/recovery_retry.rbs index 706b830..6ab3ed0 100644 --- a/gems/dexpace-core/sig/dexpace/resilience/recovery_retry.rbs +++ b/gems/dexpace-core/sig/dexpace/resilience/recovery_retry.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Resilience # The recovery-chain retry engine (RECOV-17..30, RECOV-34), a Dexpace::Transport decorator diff --git a/gems/dexpace-core/sig/dexpace/resilience/resend.rbs b/gems/dexpace-core/sig/dexpace/resilience/resend.rbs index f1e897e..7b0a490 100644 --- a/gems/dexpace-core/sig/dexpace/resilience/resend.rbs +++ b/gems/dexpace-core/sig/dexpace/resilience/resend.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Resilience # RETRY-5 through RETRY-8, RECOV-18: the re-sendability gate (R5), and REDIR-6's body-only diff --git a/gems/dexpace-core/sig/dexpace/resilience/retry_settings.rbs b/gems/dexpace-core/sig/dexpace/resilience/retry_settings.rbs index a571920..1846f79 100644 --- a/gems/dexpace-core/sig/dexpace/resilience/retry_settings.rbs +++ b/gems/dexpace-core/sig/dexpace/resilience/retry_settings.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Resilience # RECOV-34, RETRY-12: the one retry configuration both stacks build from. `random` is untyped diff --git a/gems/dexpace-core/sig/dexpace/resilience/retry_step.rbs b/gems/dexpace-core/sig/dexpace/resilience/retry_step.rbs index 454fbe1..f674d32 100644 --- a/gems/dexpace-core/sig/dexpace/resilience/retry_step.rbs +++ b/gems/dexpace-core/sig/dexpace/resilience/retry_step.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Resilience # The synchronous stage-based retry step at Stages::RETRY (RETRY-1..45's stage half). The diff --git a/gems/dexpace-core/sig/dexpace/resilience/retry_step_helpers.rbs b/gems/dexpace-core/sig/dexpace/resilience/retry_step_helpers.rbs index 1796647..3c54e9f 100644 --- a/gems/dexpace-core/sig/dexpace/resilience/retry_step_helpers.rbs +++ b/gems/dexpace-core/sig/dexpace/resilience/retry_step_helpers.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Resilience::RetryStepHelpers is a private_constant and not public API (P6-11): this # declaration exists because the strict `core` Steep target types its two includers, RetryStep # and AsyncRetryStep. RBS has no visibility for a constant, so the privacy lives in diff --git a/gems/dexpace-core/sig/dexpace/retryability.rbs b/gems/dexpace-core/sig/dexpace/retryability.rbs index 8a4ef2d..816b3cb 100644 --- a/gems/dexpace-core/sig/dexpace/retryability.rbs +++ b/gems/dexpace-core/sig/dexpace/retryability.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace # What .retryable_status? accepts beside an Integer: a Dexpace::Status, or anything answering # #code with one. diff --git a/gems/dexpace-core/sig/dexpace/serde.rbs b/gems/dexpace-core/sig/dexpace/serde.rbs index 0b97b93..a28243b 100644 --- a/gems/dexpace-core/sig/dexpace/serde.rbs +++ b/gems/dexpace-core/sig/dexpace/serde.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace # The static half of the codec seam: the six methods, typed. `value` is untyped because a # codec's value space is its own. Phase 7a settled the three clauses phase 2 left loose: diff --git a/gems/dexpace-core/sig/dexpace/serde/decode_context.rbs b/gems/dexpace-core/sig/dexpace/serde/decode_context.rbs index b5199aa..f66bee8 100644 --- a/gems/dexpace-core/sig/dexpace/serde/decode_context.rbs +++ b/gems/dexpace-core/sig/dexpace/serde/decode_context.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Serde # The `ctx` a witness receives: the path from the document root, the decode's target type diff --git a/gems/dexpace-core/sig/dexpace/serde/decoding_handler.rbs b/gems/dexpace-core/sig/dexpace/serde/decoding_handler.rbs index 5b67fae..23c4da3 100644 --- a/gems/dexpace-core/sig/dexpace/serde/decoding_handler.rbs +++ b/gems/dexpace-core/sig/dexpace/serde/decoding_handler.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Serde # SERDE-27's response-decoding handler, a Dexpace::_ResponseHandler supplied into diff --git a/gems/dexpace-core/sig/dexpace/serde/deserialization_error.rbs b/gems/dexpace-core/sig/dexpace/serde/deserialization_error.rbs index 0060f36..9742c8d 100644 --- a/gems/dexpace-core/sig/dexpace/serde/deserialization_error.rbs +++ b/gems/dexpace-core/sig/dexpace/serde/deserialization_error.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Serde class DeserializationError < Dexpace::Serde::Error diff --git a/gems/dexpace-core/sig/dexpace/serde/error.rbs b/gems/dexpace-core/sig/dexpace/serde/error.rbs index c6e81f4..0ce1a56 100644 --- a/gems/dexpace-core/sig/dexpace/serde/error.rbs +++ b/gems/dexpace-core/sig/dexpace/serde/error.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Serde class Error < ::StandardError diff --git a/gems/dexpace-core/sig/dexpace/serde/instant.rbs b/gems/dexpace-core/sig/dexpace/serde/instant.rbs index 0178937..30da4d8 100644 --- a/gems/dexpace-core/sig/dexpace/serde/instant.rbs +++ b/gems/dexpace-core/sig/dexpace/serde/instant.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Serde # The ISO-8601 instant witness (SERDE-24): the decode half parses, the encode half renders at diff --git a/gems/dexpace-core/sig/dexpace/serde/list.rbs b/gems/dexpace-core/sig/dexpace/serde/list.rbs index 9e7eda0..e088427 100644 --- a/gems/dexpace-core/sig/dexpace/serde/list.rbs +++ b/gems/dexpace-core/sig/dexpace/serde/list.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Serde # The Array combinator (SERDE-6): a witness for Array[element], built by value. A frozen Data; diff --git a/gems/dexpace-core/sig/dexpace/serde/map.rbs b/gems/dexpace-core/sig/dexpace/serde/map.rbs index 44246f5..4d7e84f 100644 --- a/gems/dexpace-core/sig/dexpace/serde/map.rbs +++ b/gems/dexpace-core/sig/dexpace/serde/map.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Serde # The Hash combinator (SERDE-6): a witness for Hash[key, value], built by value from a key diff --git a/gems/dexpace-core/sig/dexpace/serde/native.rbs b/gems/dexpace-core/sig/dexpace/serde/native.rbs index 870fd1a..eb7fa99 100644 --- a/gems/dexpace-core/sig/dexpace/serde/native.rbs +++ b/gems/dexpace-core/sig/dexpace/serde/native.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Serde # A private_constant with no visibility in RBS; declared because the strict `core` Steep diff --git a/gems/dexpace-core/sig/dexpace/serde/nullable.rbs b/gems/dexpace-core/sig/dexpace/serde/nullable.rbs index 9045a88..1679247 100644 --- a/gems/dexpace-core/sig/dexpace/serde/nullable.rbs +++ b/gems/dexpace-core/sig/dexpace/serde/nullable.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Serde # The nullable combinator (SERDE-6): a witness accepting a wire null where its element would diff --git a/gems/dexpace-core/sig/dexpace/serde/scalars.rbs b/gems/dexpace-core/sig/dexpace/serde/scalars.rbs index 3fe19aa..adf35ee 100644 --- a/gems/dexpace-core/sig/dexpace/serde/scalars.rbs +++ b/gems/dexpace-core/sig/dexpace/serde/scalars.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Serde::Scalars is a private_constant and not public API: this declaration exists # because the strict `core` Steep target checks every file under lib/ and needs the module, its # table and its witness class declared to type the combinators' constructors. RBS has no diff --git a/gems/dexpace-core/sig/dexpace/serde/serialization_error.rbs b/gems/dexpace-core/sig/dexpace/serde/serialization_error.rbs index f6b4fd9..889ddf9 100644 --- a/gems/dexpace-core/sig/dexpace/serde/serialization_error.rbs +++ b/gems/dexpace-core/sig/dexpace/serde/serialization_error.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Serde class SerializationError < Dexpace::Serde::Error diff --git a/gems/dexpace-core/sig/dexpace/serde/status_aware_handler.rbs b/gems/dexpace-core/sig/dexpace/serde/status_aware_handler.rbs index 45b41b3..9440874 100644 --- a/gems/dexpace-core/sig/dexpace/serde/status_aware_handler.rbs +++ b/gems/dexpace-core/sig/dexpace/serde/status_aware_handler.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Serde # SERDE-28's status-aware response handler, a Dexpace::_ResponseHandler supplied into diff --git a/gems/dexpace-core/sig/dexpace/serde/tristate.rbs b/gems/dexpace-core/sig/dexpace/serde/tristate.rbs index 0fb11fa..85db267 100644 --- a/gems/dexpace-core/sig/dexpace/serde/tristate.rbs +++ b/gems/dexpace-core/sig/dexpace/serde/tristate.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Serde # The three-state PATCH type (SERDE-14–SERDE-20, SERDE-30): a module included by ABSENT, NULL diff --git a/gems/dexpace-core/sig/dexpace/serde/witness.rbs b/gems/dexpace-core/sig/dexpace/serde/witness.rbs index b1013e4..3dbf624 100644 --- a/gems/dexpace-core/sig/dexpace/serde/witness.rbs +++ b/gems/dexpace-core/sig/dexpace/serde/witness.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Serde # The witness protocol (design §7.3, §10.14): any object answering .dexpace_load(parsed, ctx). diff --git a/gems/dexpace-core/sig/dexpace/sse.rbs b/gems/dexpace-core/sig/dexpace/sse.rbs index 2920d13..8920fd3 100644 --- a/gems/dexpace-core/sig/dexpace/sse.rbs +++ b/gems/dexpace-core/sig/dexpace/sse.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module SSE MAX_LINE_BYTES: Integer diff --git a/gems/dexpace-core/sig/dexpace/sse/event.rbs b/gems/dexpace-core/sig/dexpace/sse/event.rbs index 45a5c44..75e452e 100644 --- a/gems/dexpace-core/sig/dexpace/sse/event.rbs +++ b/gems/dexpace-core/sig/dexpace/sse/event.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module SSE # The immutable five-field event value (SSE-20, SSE-21, SSE-22). diff --git a/gems/dexpace-core/sig/dexpace/sse/limit_exceeded_error.rbs b/gems/dexpace-core/sig/dexpace/sse/limit_exceeded_error.rbs index c717a56..3b11f09 100644 --- a/gems/dexpace-core/sig/dexpace/sse/limit_exceeded_error.rbs +++ b/gems/dexpace-core/sig/dexpace/sse/limit_exceeded_error.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module SSE # SSE-19: a line or an event block crossed its documented bound and was rejected. diff --git a/gems/dexpace-core/sig/dexpace/sse/line_reader.rbs b/gems/dexpace-core/sig/dexpace/sse/line_reader.rbs index bbbb649..f97f8b3 100644 --- a/gems/dexpace-core/sig/dexpace/sse/line_reader.rbs +++ b/gems/dexpace-core/sig/dexpace/sse/line_reader.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module SSE # SSE-2's line machine over a _ByteSource#getbyte (P7-20), with SSE-19's line cap. diff --git a/gems/dexpace-core/sig/dexpace/sse/reader.rbs b/gems/dexpace-core/sig/dexpace/sse/reader.rbs index 3787ef7..74ccd5d 100644 --- a/gems/dexpace-core/sig/dexpace/sse/reader.rbs +++ b/gems/dexpace-core/sig/dexpace/sse/reader.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module SSE # SSE-1's field state machine over LineReader: SSE-3 through SSE-18, SSE-19's event cap. diff --git a/gems/dexpace-core/sig/dexpace/sse/sentinel.rbs b/gems/dexpace-core/sig/dexpace/sse/sentinel.rbs index 2eecefb..d2389e5 100644 --- a/gems/dexpace-core/sig/dexpace/sse/sentinel.rbs +++ b/gems/dexpace-core/sig/dexpace/sse/sentinel.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module SSE # SSE-34's two non-value outcomes, as a closed two-instance type (P7-23). diff --git a/gems/dexpace-core/sig/dexpace/sse/stream.rbs b/gems/dexpace-core/sig/dexpace/sse/stream.rbs index f40dcd3..daba788 100644 --- a/gems/dexpace-core/sig/dexpace/sse/stream.rbs +++ b/gems/dexpace-core/sig/dexpace/sse/stream.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module SSE # The resource-owning single-pass streaming facade (SSE-23 through SSE-32, SSE-39, SSE-40). diff --git a/gems/dexpace-core/sig/dexpace/sse/stream_state_error.rbs b/gems/dexpace-core/sig/dexpace/sse/stream_state_error.rbs index 2b12410..9e97ba1 100644 --- a/gems/dexpace-core/sig/dexpace/sse/stream_state_error.rbs +++ b/gems/dexpace-core/sig/dexpace/sse/stream_state_error.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module SSE # SSE-26's second view and SSE-27's post-close request: an illegal-state error. diff --git a/gems/dexpace-core/sig/dexpace/sse/typed_stream.rbs b/gems/dexpace-core/sig/dexpace/sse/typed_stream.rbs index 6c288fc..6748cbc 100644 --- a/gems/dexpace-core/sig/dexpace/sse/typed_stream.rbs +++ b/gems/dexpace-core/sig/dexpace/sse/typed_stream.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module SSE # The typed adapter over a Stream and a caller-supplied mapper (SSE-33 through SSE-36). diff --git a/gems/dexpace-core/sig/dexpace/suppressible.rbs b/gems/dexpace-core/sig/dexpace/suppressible.rbs index 55294fb..6ae717a 100644 --- a/gems/dexpace-core/sig/dexpace/suppressible.rbs +++ b/gems/dexpace-core/sig/dexpace/suppressible.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace # The self-type is ::Exception because #detailed_message calls `super`, and every includer and # every object Dexpace.attach_suppressed extends is one. The keywords Ruby's printer passes diff --git a/gems/dexpace-core/sig/dexpace/transport.rbs b/gems/dexpace-core/sig/dexpace/transport.rbs index 7cb3d02..cca3a86 100644 --- a/gems/dexpace-core/sig/dexpace/transport.rbs +++ b/gems/dexpace-core/sig/dexpace/transport.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace # The static half of the seam -- what a consumer's own `steep check` sees at a parameter -- and # the artifact that replaces the styleguide's Sorbet abstract module diff --git a/gems/dexpace-core/sig/dexpace/uuid.rbs b/gems/dexpace-core/sig/dexpace/uuid.rbs index 2dba9d2..ac2dc36 100644 --- a/gems/dexpace-core/sig/dexpace/uuid.rbs +++ b/gems/dexpace-core/sig/dexpace/uuid.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module UUID # A private_constant; declared because the strict `core` Steep target types its use. diff --git a/gems/dexpace-core/sig/dexpace/version.rbs b/gems/dexpace-core/sig/dexpace/version.rbs index 35dc193..73401b7 100644 --- a/gems/dexpace-core/sig/dexpace/version.rbs +++ b/gems/dexpace-core/sig/dexpace/version.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace VERSION: String end diff --git a/gems/dexpace-core/test/dexpace/http/protocol_test.rb b/gems/dexpace-core/test/dexpace/http/protocol_test.rb index 6b73f03..c57a5e5 100644 --- a/gems/dexpace-core/test/dexpace/http/protocol_test.rb +++ b/gems/dexpace-core/test/dexpace/http/protocol_test.rb @@ -23,7 +23,22 @@ class DexpaceProtocolTest < DexpaceTestCase error = assert_raises(Dexpace::InvalidArgumentError) { Dexpace::Protocol.parse("spdy/3") } assert_includes(error.message, "spdy/3") - assert_raises(Dexpace::InvalidArgumentError) { Dexpace::Protocol.parse("http/1.0") } + # The negative half of phase 10's repair: widening the table by one form must not turn a + # loud failure into a silent one for every other version. + ["http/0.9", "HTTP/3", "http/1", "http/1.10"].each do |text| + assert_raises(Dexpace::InvalidArgumentError, text) { Dexpace::Protocol.parse(text) } + end + end + + # HTTP-33 names http/1.1 and http/2 as EXAMPLES of the canonical form; a real HTTP/1.0 status + # line is one any server may send, and both adapters feed their native version straight here, so + # before phase 10 an HTTP/1.0 response made both ResponseMappers raise (8a's hand-forward). + test "parses HTTP/1.0 to its own canonical form, and build accepts exactly that form" do + ["HTTP/1.0", "http/1.0"].each do |text| + assert_equal("http/1.0", Dexpace::Protocol.parse(text).wire) + end + assert_equal(Dexpace::Protocol::HTTP_1_0, Dexpace::Protocol.parse("HTTP/1.0")) + assert_equal(Dexpace::Protocol::HTTP_1_0, Dexpace::Protocol.build(wire: "http/1.0")) end test "requires an identifier, naming the field" do diff --git a/gems/dexpace-core/test/dexpace/http/response_test.rb b/gems/dexpace-core/test/dexpace/http/response_test.rb index 45515ec..838030a 100644 --- a/gems/dexpace-core/test/dexpace/http/response_test.rb +++ b/gems/dexpace-core/test/dexpace/http/response_test.rb @@ -103,8 +103,9 @@ def request assert_equal(["1"], derived.build.headers["X-A"]) end - test "with re-validates, so a derived response cannot carry a status outside the range" do - assert_raises(Dexpace::InvalidArgumentError) { response.with(status: 600) } + test "with re-validates, so a derived response cannot carry a status that is not a code" do + # A String and not 600 or 1000: phase 10 made Status total over every Integer (HTTP-10). + assert_raises(Dexpace::InvalidArgumentError) { response.with(status: "404") } assert_raises(Dexpace::InvalidArgumentError) { response.with(protocol: nil) } assert_equal(Dexpace::Status::NOT_FOUND, response.with(status: 404).status) end @@ -159,7 +160,7 @@ def response(status: Dexpace::Status::OK) end test "build rejects a protocol, a status, a request and headers it cannot coerce" do - assert_raises(Dexpace::InvalidArgumentError) { response(status: 99) } + assert_raises(Dexpace::InvalidArgumentError) { response(status: "200") } # every Integer maps builder = Dexpace::Response.builder builder.request = request builder.protocol = "spdy/3" diff --git a/gems/dexpace-core/test/dexpace/http/status_test.rb b/gems/dexpace-core/test/dexpace/http/status_test.rb index 17bb30c..9d6e782 100644 --- a/gems/dexpace-core/test/dexpace/http/status_test.rb +++ b/gems/dexpace-core/test/dexpace/http/status_test.rb @@ -48,10 +48,30 @@ class DexpaceStatusTest < DexpaceTestCase refute_predicate(Dexpace::Status.of(399), :error?) end - test "rejects a code outside the protocol's range as a caller mistake" do - assert_raises(Dexpace::InvalidArgumentError) { Dexpace::Status.of(99) } - assert_raises(Dexpace::InvalidArgumentError) { Dexpace::Status.of(600) } + # HTTP-10 (MUST): "mapping ANY code MUST return a Status (never throw)"; TRANSPORT-24 (MUST): any + # code the server returns, vendor codes included, is surfaced rather than rejected. Net::HTTP + # delivers a 600 or LinkedIn's 999 as an HTTPUnknownResponse, and before phase 10 this guard + # refused both, which made both transports raise on a head the requirement says to surface. A + # first repair stopped at 0..999 and still threw on 1000 and -1 (review round 0, R0-5), so the + # whole Integer line maps and only a non-Integer -- not a code at all -- is refused. + test "maps every Integer code, vendor codes past 599 included, and refuses only a non-code" do + [-1, 0, 99, 600, 999, 1000, 2**70].each do |code| + assert_equal(code, Dexpace::Status.of(code).code) + assert_nil(Dexpace::Status.of(code).canonical_name) + refute_predicate(Dexpace::Status.of(code), :standard?) + end assert_raises(Dexpace::InvalidArgumentError) { Dexpace::Status.of("200") } + assert_raises(Dexpace::InvalidArgumentError) { Dexpace::Status.of(200.0) } + end + + # The 100-599 guard moved to its own predicate (phase 10), so a caller who wants the protocol's + # classes still asks one question; a code outside them belongs to none of HTTP-11's classes. + test "standard? is the protocol's own range, and a code outside it has no class" do + assert_predicate(Dexpace::Status.of(100), :standard?) + assert_predicate(Dexpace::Status.of(599), :standard?) + refute_predicate(Dexpace::Status.of(999), :standard?) + refute_predicate(Dexpace::Status.of(999), :error?) + refute_predicate(Dexpace::Status.of(99), :informational?) end test "requires a code, naming the field" do @@ -66,9 +86,9 @@ class DexpaceStatusTest < DexpaceTestCase end # HTTP-10's totality as a property rather than five examples. - test "construction is total over the protocol's range" do + test "construction is total over the Integer line" do sample(count: 256) do |rng| - code = rng.rand(100..599) + code = rng.rand(-100_000..100_000) assert_equal(code, Dexpace::Status.of(code).code) end diff --git a/gems/dexpace-core/test/dexpace/page/matrix_facts_test.rb b/gems/dexpace-core/test/dexpace/page/matrix_facts_test.rb index 1e557ba..cfac068 100644 --- a/gems/dexpace-core/test/dexpace/page/matrix_facts_test.rb +++ b/gems/dexpace-core/test/dexpace/page/matrix_facts_test.rb @@ -186,7 +186,8 @@ class SpliceAndFiberTest < DexpaceTestCase refute_same(element, owned.first) assert_predicate(owned.first, :frozen?) - assert_raises(TypeError) { Dexpace::Model.own([proc {}]) } + # Phase 10: the SDK's own error now, never Ruby's TypeError (phase 1's review R3-1). + assert_raises(Dexpace::InvalidArgumentError) { Dexpace::Model.own([proc {}]) } assert_empty(Data.define(:a).new(a: 1).instance_variables) end diff --git a/gems/dexpace-serde-json/sig/dexpace/serde/json.rbs b/gems/dexpace-serde-json/sig/dexpace/serde/json.rbs index 6bad8ff..cb12253 100644 --- a/gems/dexpace-serde-json/sig/dexpace/serde/json.rbs +++ b/gems/dexpace-serde-json/sig/dexpace/serde/json.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Serde # The reference wire codec over Ruby's json: the floor it asserts (P7-7), the core constraint diff --git a/gems/dexpace-serde-json/sig/dexpace/serde/json/codec.rbs b/gems/dexpace-serde-json/sig/dexpace/serde/json/codec.rbs index 2e71e9e..929278e 100644 --- a/gems/dexpace-serde-json/sig/dexpace/serde/json/codec.rbs +++ b/gems/dexpace-serde-json/sig/dexpace/serde/json/codec.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Serde module JSON diff --git a/gems/dexpace-serde-json/sig/dexpace/serde/json/version.rbs b/gems/dexpace-serde-json/sig/dexpace/serde/json/version.rbs index cce65f5..ec503da 100644 --- a/gems/dexpace-serde-json/sig/dexpace/serde/json/version.rbs +++ b/gems/dexpace-serde-json/sig/dexpace/serde/json/version.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Serde module JSON diff --git a/gems/dexpace-transport-async_http/lib/dexpace/transport/async_http/response_mapper.rb b/gems/dexpace-transport-async_http/lib/dexpace/transport/async_http/response_mapper.rb index 402f679..6514a63 100644 --- a/gems/dexpace-transport-async_http/lib/dexpace/transport/async_http/response_mapper.rb +++ b/gems/dexpace-transport-async_http/lib/dexpace/transport/async_http/response_mapper.rb @@ -12,11 +12,11 @@ module AsyncHTTP # response object exists: a malformed inbound header NAME (`Protocol::HTTP1::BadHeader`, # P8-38, the TRANSPORT-14 waiver) and a non-numeric Content-Length # (`Protocol::HTTP1::BadRequest`, the TRANSPORT-27 waiver) -- both wrap as retryable - # transport failures. Dexpace::Protocol admits HTTP/1.1 and HTTP/2 only (HTTP-33) and - # Dexpace::Status 100-599 (HTTP-10's reading), so an `HTTP/1.0` head and a `999` status each - # raise Dexpace::InvalidArgumentError here, after the head, with the native body closed by - # the exchange's own release -- the disposition dexpace-transport-net_http records, and the - # two phase-1 questions on phase 10's inbound list. A private_constant of AsyncHTTP. + # transport failures. Since phase 10 Dexpace::Protocol admits HTTP/1.0 beside HTTP/1.1 and + # HTTP/2 (HTTP-33) and Dexpace::Status every Integer code (HTTP-10, TRANSPORT-24), so an + # `HTTP/1.0` head and a vendor `999` both map; a version the model does not know still + # raises Dexpace::InvalidArgumentError here, after the head, with the native body closed by + # the exchange's own release. A private_constant of AsyncHTTP. module ResponseMapper extend self diff --git a/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http.rbs b/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http.rbs index 2b52a4a..0d5d584 100644 --- a/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http.rbs +++ b/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Transport # The reference asynchronous transport over async-http (phase 8c). Nothing from async, diff --git a/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/adapter.rbs b/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/adapter.rbs index 7f6997c..cd331fb 100644 --- a/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/adapter.rbs +++ b/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/adapter.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Transport module AsyncHTTP diff --git a/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/clients.rbs b/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/clients.rbs index 7bf3aed..8ec5bbb 100644 --- a/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/clients.rbs +++ b/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/clients.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Transport::AsyncHTTP::Clients is a private_constant and not public API: this # declaration exists because the `async_http` Steep target checks every file under lib/ and needs # the class declared to type Adapter's call sites. RBS has no visibility for a constant, so the diff --git a/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/drop_policy.rbs b/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/drop_policy.rbs index cf86ae4..461823f 100644 --- a/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/drop_policy.rbs +++ b/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/drop_policy.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Transport module AsyncHTTP diff --git a/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/endpoints.rbs b/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/endpoints.rbs index b30599e..84fab2a 100644 --- a/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/endpoints.rbs +++ b/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/endpoints.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Transport::AsyncHTTP::Endpoints is a private_constant and not public API: this # declaration exists because the `async_http` Steep target checks every file under lib/ and needs # the module declared to type Clients' and RequestMapper's call sites. RBS has no visibility for a diff --git a/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/errors.rbs b/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/errors.rbs index 0e2ed08..28e8d7e 100644 --- a/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/errors.rbs +++ b/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/errors.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Transport::AsyncHTTP::Errors is a private_constant and not public API: this # declaration exists because the `async_http` Steep target checks every file under lib/ and needs # the module and its three functions declared to type Adapter's, Exchange's and ResponseBody's diff --git a/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/exchange.rbs b/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/exchange.rbs index ea6fc01..8e5c243 100644 --- a/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/exchange.rbs +++ b/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/exchange.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Transport::AsyncHTTP::Exchange is a private_constant and not public API: this # declaration exists because the `async_http` Steep target checks every file under lib/ and needs # the class declared to type Adapter's call site. RBS has no visibility for a constant, so the diff --git a/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/request_body.rbs b/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/request_body.rbs index a8fd436..7a99264 100644 --- a/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/request_body.rbs +++ b/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/request_body.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Transport::AsyncHTTP::RequestBody is a private_constant and not public API: this # declaration exists because the `async_http` Steep target checks every file under lib/ and needs # the class declared to type RequestMapper's call site. RBS has no visibility for a constant, so diff --git a/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/request_mapper.rbs b/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/request_mapper.rbs index b268bc3..922d090 100644 --- a/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/request_mapper.rbs +++ b/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/request_mapper.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Transport::AsyncHTTP::RequestMapper is a private_constant and not public API: this # declaration exists because the `async_http` Steep target checks every file under lib/ and needs # the module and its one function declared to type Adapter's call site. RBS has no visibility for diff --git a/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/response_body.rbs b/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/response_body.rbs index c042eed..b39c7ce 100644 --- a/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/response_body.rbs +++ b/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/response_body.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Transport::AsyncHTTP::ResponseBody is a private_constant and not public API: this # declaration exists because the `async_http` Steep target checks every file under lib/ and needs # the class declared to type ResponseMapper's call site. RBS has no visibility for a constant, so diff --git a/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/response_mapper.rbs b/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/response_mapper.rbs index c1cab97..02c1861 100644 --- a/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/response_mapper.rbs +++ b/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/response_mapper.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Transport::AsyncHTTP::ResponseMapper is a private_constant and not public API: this # declaration exists because the `async_http` Steep target checks every file under lib/ and needs # the module and its one function declared to type Exchange's call site. RBS has no visibility for diff --git a/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/version.rbs b/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/version.rbs index ef1b356..3c049db 100644 --- a/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/version.rbs +++ b/gems/dexpace-transport-async_http/sig/dexpace/transport/async_http/version.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Transport module AsyncHTTP diff --git a/gems/dexpace-transport-async_http/test/dexpace/transport/async_http/parent_cancellation_test.rb b/gems/dexpace-transport-async_http/test/dexpace/transport/async_http/parent_cancellation_test.rb index aa13ea1..6d4aa46 100644 --- a/gems/dexpace-transport-async_http/test/dexpace/transport/async_http/parent_cancellation_test.rb +++ b/gems/dexpace-transport-async_http/test/dexpace/transport/async_http/parent_cancellation_test.rb @@ -148,7 +148,8 @@ class CloseDisciplineTest < DexpaceTestCase end end - # TRANSPORT-22's adaptation-failure half: a head phase 1's model refuses -- a 999 status -- + # TRANSPORT-22's adaptation-failure half: a head phase 1's model refuses -- an HTTP/1.2 version, + # a 999 status until phase 10 widened Status -- # raises InvalidArgumentError through the future, and the native body the exchange was holding # is closed exactly once before the future settles. test "TRANSPORT-22: an adaptation failure after the head closes the native body exactly once " \ @@ -158,7 +159,7 @@ class CloseDisciplineTest < DexpaceTestCase client.define_singleton_method(:retries) { 0 } client.define_singleton_method(:pool) { Object.new.tap { |pool| def pool.close = nil } } client.define_singleton_method(:call) do |_native| - ::Protocol::HTTP::Response.new("HTTP/1.1", 999, ::Protocol::HTTP::Headers.new, native) + ::Protocol::HTTP::Response.new("HTTP/1.2", 200, ::Protocol::HTTP::Headers.new, native) end adapter = AsyncHTTP.using(client) @@ -187,7 +188,7 @@ class CloseDisciplineTest < DexpaceTestCase client.define_singleton_method(:retries) { 0 } client.define_singleton_method(:pool) { Object.new.tap { |pool| def pool.close = nil } } client.define_singleton_method(:call) do |_native| - ::Protocol::HTTP::Response.new("HTTP/1.1", 999, ::Protocol::HTTP::Headers.new, native) + ::Protocol::HTTP::Response.new("HTTP/1.2", 200, ::Protocol::HTTP::Headers.new, native) end adapter = AsyncHTTP.using(client) diff --git a/gems/dexpace-transport-async_http/test/dexpace/transport/async_http/response_mapper_test.rb b/gems/dexpace-transport-async_http/test/dexpace/transport/async_http/response_mapper_test.rb index 5aee477..8d1ebf2 100644 --- a/gems/dexpace-transport-async_http/test/dexpace/transport/async_http/response_mapper_test.rb +++ b/gems/dexpace-transport-async_http/test/dexpace/transport/async_http/response_mapper_test.rb @@ -60,12 +60,16 @@ class HeadTest < DexpaceTestCase assert_equal("http/2", response.protocol.wire) end - # Phase 1's Status guards 100-599 and Protocol admits HTTP/1.1 and HTTP/2: a head outside either - # is InvalidArgumentError after the head, the disposition 8a records and phase 10 decides. - test "a status outside 100-599 or an HTTP/1.0 head raises InvalidArgumentError from the " \ - "model" do - assert_raises(Dexpace::InvalidArgumentError) { map(native_response(status: 999)) } - assert_raises(Dexpace::InvalidArgumentError) { map(native_response(version: "HTTP/1.0")) } + # HTTP-10, HTTP-33, TRANSPORT-24: phase 10 widened Status to every code a status line can carry + # and Protocol by HTTP/1.0, the two phase-1 questions 8a routed. Until then this test asserted + # both heads raised; it is that pin, inverted. + test "a vendor 999 status and an HTTP/1.0 head both map" do + assert_equal(999, map(native_response(status: 999)).status.code) + assert_equal("http/1.0", map(native_response(version: "HTTP/1.0")).protocol.wire) + end + + test "a version the model does not know is InvalidArgumentError after the head (HTTP-33)" do + assert_raises(Dexpace::InvalidArgumentError) { map(native_response(version: "HTTP/1.2")) } end test "TRANSPORT-14: a control byte in an inbound value is dropped, that header only, and " \ diff --git a/gems/dexpace-transport-net_http/lib/dexpace/transport/net_http/request_mapper.rb b/gems/dexpace-transport-net_http/lib/dexpace/transport/net_http/request_mapper.rb index 22cedcb..fb859d4 100644 --- a/gems/dexpace-transport-net_http/lib/dexpace/transport/net_http/request_mapper.rb +++ b/gems/dexpace-transport-net_http/lib/dexpace/transport/net_http/request_mapper.rb @@ -74,7 +74,7 @@ def copy_headers(request, native, logger) end # P8-3: the switch is `#[]=`, never `#add_field`, because only `#[]=` flips - # @decode_content off as a side effect. Re-assigning the caller's own value preserves it + # `@decode_content` off as a side effect. Re-assigning the caller's own value preserves it # while still tripping the flip; assigning then deleting leaves the flag off and the # header absent when the caller set none. def suppress_decode_content(native) diff --git a/gems/dexpace-transport-net_http/lib/dexpace/transport/net_http/response_mapper.rb b/gems/dexpace-transport-net_http/lib/dexpace/transport/net_http/response_mapper.rb index eda21cd..9c073ea 100644 --- a/gems/dexpace-transport-net_http/lib/dexpace/transport/net_http/response_mapper.rb +++ b/gems/dexpace-transport-net_http/lib/dexpace/transport/net_http/response_mapper.rb @@ -11,13 +11,12 @@ module NetHTTP # connection-close framing takes over instead of Net::HTTPResponse#content_length raising # Net::HTTPHeaderSyntaxError. Headers come from `#to_hash` and nothing else: `#[]` joins # with ", " and `#each_capitalized` re-cases, both of which would corrupt a multi-valued - # Set-Cookie. Dexpace::Protocol admits HTTP/1.1 and HTTP/2 only (HTTP-33), so an HTTP/1.0 - # response raises Dexpace::InvalidArgumentError here; no TRANSPORT ID asks for 1.0 and the - # gap is phase 1's to widen. The status is total over 100-599, phase 1's Status guard - # (HTTP-10's reading): Net::HTTP parses any three digits and delivers a 999 or a 600 as an - # HTTPUnknownResponse, and such a head raises Dexpace::InvalidArgumentError here the same - # way, after the head and with the connection released (TRANSPORT-22); whether TRANSPORT-24's - # "any code" reaches 600-999 is a phase-1 model question on phase 10's inbound list. + # Set-Cookie. Since phase 10 Dexpace::Protocol admits HTTP/1.0 beside HTTP/1.1 and HTTP/2 + # (HTTP-33), so an HTTP/1.0 response maps; a version it does not know (HTTP/1.2) still + # raises Dexpace::InvalidArgumentError here, after the head and with the connection released + # (TRANSPORT-22). The status is total over every code a status line can carry: Net::HTTP + # parses any three decimal places and delivers a 999 or a 600 as an HTTPUnknownResponse, + # and Dexpace::Status maps every Integer (HTTP-10, TRANSPORT-24). # A private_constant of NetHTTP. module ResponseMapper extend self diff --git a/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http.rbs b/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http.rbs index b7e9924..752af39 100644 --- a/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http.rbs +++ b/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Transport # The reference synchronous transport over net/http (phase 8a). Nothing from net/http names diff --git a/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http/adapter.rbs b/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http/adapter.rbs index e29a89d..596cbb1 100644 --- a/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http/adapter.rbs +++ b/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http/adapter.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Transport module NetHTTP diff --git a/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http/deadline.rbs b/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http/deadline.rbs index 2fd9713..1ce3c5b 100644 --- a/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http/deadline.rbs +++ b/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http/deadline.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Transport::NetHTTP::Deadline is a private_constant and not public API: this declaration # exists because the `net_http` Steep target checks every file under lib/ and needs the class # declared to type Adapter's and ResponsePump's call sites. RBS has no visibility for a constant, diff --git a/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http/failures.rbs b/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http/failures.rbs index 1b56eeb..78a020f 100644 --- a/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http/failures.rbs +++ b/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http/failures.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Transport::NetHTTP::Failures is a private_constant and not public API: this declaration # exists because the `net_http` Steep target checks every file under lib/ and needs the module and # its one function declared to type Adapter's and ResponsePump's call sites. RBS has no visibility diff --git a/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http/proxy_route.rbs b/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http/proxy_route.rbs index 17aa987..7dfd332 100644 --- a/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http/proxy_route.rbs +++ b/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http/proxy_route.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Transport::NetHTTP::ProxyRoute is a private_constant and not public API: this # declaration exists because the `net_http` Steep target checks every file under lib/ and needs # the snapshot declared to type Adapter's call site. RBS has no visibility for a constant, so the diff --git a/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http/request_mapper.rbs b/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http/request_mapper.rbs index dab4ed5..7911273 100644 --- a/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http/request_mapper.rbs +++ b/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http/request_mapper.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Transport::NetHTTP::RequestMapper is a private_constant and not public API: this # declaration exists because the `net_http` Steep target checks every file under lib/ and needs # the module and its one function declared to type Adapter's call site. RBS has no visibility for diff --git a/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http/response_mapper.rbs b/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http/response_mapper.rbs index 4e3772c..15b7916 100644 --- a/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http/response_mapper.rbs +++ b/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http/response_mapper.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Transport::NetHTTP::ResponseMapper is a private_constant and not public API: this # declaration exists because the `net_http` Steep target checks every file under lib/ and needs # the module and its one function declared to type Adapter's call site. RBS has no visibility for diff --git a/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http/response_pump.rbs b/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http/response_pump.rbs index d811195..61fca39 100644 --- a/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http/response_pump.rbs +++ b/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http/response_pump.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Transport::NetHTTP::ResponsePump is a private_constant and not public API: this # declaration exists because the `net_http` Steep target checks every file under lib/ and needs # the class declared to type Adapter's and ResponseMapper's call sites. RBS has no visibility for diff --git a/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http/tls_settings.rbs b/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http/tls_settings.rbs index 53e4343..d7a9f11 100644 --- a/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http/tls_settings.rbs +++ b/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http/tls_settings.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + # Dexpace::Transport::NetHTTP::TLSSettings is a private_constant and not public API: this # declaration exists because the `net_http` Steep target checks every file under lib/ and needs # the module and its two functions declared to type NetHTTP.build's and Adapter's call sites. diff --git a/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http/version.rbs b/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http/version.rbs index a24427c..f6ce108 100644 --- a/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http/version.rbs +++ b/gems/dexpace-transport-net_http/sig/dexpace/transport/net_http/version.rbs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: MIT + module Dexpace module Transport module NetHTTP diff --git a/gems/dexpace-transport-net_http/test/dexpace/transport/net_http/adapter_test.rb b/gems/dexpace-transport-net_http/test/dexpace/transport/net_http/adapter_test.rb index 6b49409..b26a7d8 100644 --- a/gems/dexpace-transport-net_http/test/dexpace/transport/net_http/adapter_test.rb +++ b/gems/dexpace-transport-net_http/test/dexpace/transport/net_http/adapter_test.rb @@ -375,14 +375,16 @@ class WireTest < DexpaceTestCase assert_equal(1, server.await_closed_connection(timeout: 2)) end - # The body is UNDRAINABLE -- a hundred bytes declared, two sent, the connection held -- so + # The head is an HTTP/1.2 status line, which Net::HTTP parses and Dexpace::Protocol refuses + # (HTTP-33) -- an HTTP/1.0 head until phase 10 added that wire form. The body is UNDRAINABLE + # -- a hundred bytes declared, two sent, the connection held -- so # only the adapter's own close in Adapter#dispatch's rescue can release it: with that guard # removed the producer stays blocked in read_body on the 98 bytes that never come, the server # never sees the peer close, and the bound below elapses. A two-byte body drained itself and # released the connection whether or not the guard existed (review round 0's mutation H). test "TRANSPORT-22: an adaptation failure after the head releases the connection and raises" do server = wire(lambda do |conn, _head| - conn.write("HTTP/1.0 200 OK\r\nContent-Length: 100\r\n\r\nok") + conn.write("HTTP/1.2 200 OK\r\nContent-Length: 100\r\n\r\nok") conn.read # holds the connection, and the 98 undelivered bytes, until the peer closes it end) diff --git a/gems/dexpace-transport-net_http/test/dexpace/transport/net_http/response_mapper_test.rb b/gems/dexpace-transport-net_http/test/dexpace/transport/net_http/response_mapper_test.rb index b31f8e2..6e1ca7c 100644 --- a/gems/dexpace-transport-net_http/test/dexpace/transport/net_http/response_mapper_test.rb +++ b/gems/dexpace-transport-net_http/test/dexpace/transport/net_http/response_mapper_test.rb @@ -120,10 +120,20 @@ class HeadTest < DexpaceTestCase assert_equal(%w[a=1 b=2], response.headers["Set-Cookie"]) end - test "an HTTP/1.0 head raises InvalidArgumentError: Protocol admits 1.1 and 2 only (HTTP-33)" do + # HTTP-33, TRANSPORT-24: phase 10 added http/1.0 to Protocol's wire forms. Until then this head + # raised Dexpace::InvalidArgumentError out of the mapper, and a real HTTP/1.0 server could + # reach it; the pin that asserted the raise is this test, inverted. + test "an HTTP/1.0 head maps, carrying Protocol::HTTP_1_0 (HTTP-33)" do native = ::Net::HTTPOK.new("1.0", "200", "OK") native["Content-Length"] = "0" + assert_equal("http/1.0", build(native).protocol.wire) + end + + test "a version Protocol does not know still raises InvalidArgumentError (HTTP-33)" do + native = ::Net::HTTPOK.new("1.2", "200", "OK") + native["Content-Length"] = "0" + assert_raises(Dexpace::InvalidArgumentError) { build(native) } end end diff --git a/rbs_collection.yaml b/rbs_collection.yaml index 7812de5..c3b91ac 100644 --- a/rbs_collection.yaml +++ b/rbs_collection.yaml @@ -1,8 +1,10 @@ -# Third-party RBS, resolved from the gems the bundle actually holds. No third-party row exists -# in phase 0 because no gemspec declares a third-party dependency yet: json arrives with -# dexpace-serde-json's codec in phase 7, net-http and async-http with the transports in phase 8, -# and each adds its own row here then. Core never gets one -- its allowlist is stdlib only, and -# stdlib signatures ship with rbs itself. +# Third-party RBS, resolved from the gems the bundle actually holds. As built, no third-party +# gem needed a row to be ADDED: json's and net-http's signatures are rbs's own stdlib set (`rbs +# collection install` resolves both with `source: type: stdlib`, and json 3.0.2 ships no sig/ -- +# the codec's one JSON::Coder reference is settled in the Steepfile's :serde_json target), and +# async-http's closure needs one row that IGNORES a stale entry, below. Core never gets one -- its +# allowlist is stdlib only. (Corrected by phase 10: this header said json and net-http would each +# "add its own row here", which neither did.) sources: - type: git name: ruby/gem_rbs_collection diff --git a/tasks/gates.rake b/tasks/gates.rake index a3b05b8..63edf8e 100644 --- a/tasks/gates.rake +++ b/tasks/gates.rake @@ -395,4 +395,49 @@ namespace :gates do puts "gates:seam_names: #{core_files(root).size} core files name no adapter (SEAM-2)." end + + # Phase 10's three gates (design addenda A8, A9, A11). Each reads absolute paths joined to + # gate_root and relativises only its message -- P9-27's lesson, applied from the first line. + # + # gates:ledger_audit's third assertion resolves every `Dexpace::` constant a verdict row names, so + # the task loads core and every adapter whose Ruby floor this interpreter meets; a gem it cannot + # load leaves its constants unresolvable and the row an offence, which is the safe direction. The + # gate runs in CI's `gates` job, on the development Ruby. + desc "P10-2: docs/deviations.md still matches design section 10, every verdict citing real code" + task :ledger_audit do + require_relative "../tools/ledger_audit" + require_relative "../tools/versions" + CLEAN_BUNDLE_ENTRIES.each do |name, (feature, _constant)| + next unless DexpaceVersions.gem_supported?(name) + + require feature + end + found = LedgerAudit.offences(root: gate_root) + abort(found.join("\n")) unless found.empty? + + puts "gates:ledger_audit: every register row matches its design section 10 entry, " \ + "and every verdict cites resolvable as-built evidence." + end + + desc "NFR-13, NFR-3: every shipped signature carries the SPDX header and declares something" + task :spdx_rbs do + require_relative "../tools/spdx_rbs" + root = gate_root + paths = Dir.glob(File.join(root, SpdxRbs::GLOB)) + found = SpdxRbs.offences(root: root, paths: paths) + abort(relative_to(root, found).join("\n")) unless found.empty? + + puts "gates:spdx_rbs: #{paths.size} shipped signatures carry the header and declare something." + end + + desc "NFR-6: AstScan.parse is the only RubyVM::AbstractSyntaxTree.parse_file in the tooling" + task :sole_parse do + require_relative "../tools/sole_parse" + root = gate_root + found = SoleParse.offences(root: root) + abort(relative_to(root, found).join("\n")) unless found.empty? + + puts "gates:sole_parse: AstScan.parse is the one parse_file; " \ + "#{SoleParse::ALLOWED.size} deliberate test contrasts allowlisted with reasons." + end end diff --git a/tasks/quality.rake b/tasks/quality.rake index b6ca086..97b37e7 100644 --- a/tasks/quality.rake +++ b/tasks/quality.rake @@ -13,8 +13,12 @@ require_relative "../tools/interpreter" def bundle = Interpreter.executable("bundle") desc "NFR-7: RuboCop, findings fatal, no autocorrection" +# `--ignore-parent-exclusion`: RuboCop takes AllCops/Exclude from the topmost .rubocop.yml on the +# path, so a worktree nested under a parent checkout's `.claude/` inherited that checkout's +# `.claude/**/*` exclusion and the gate inspected almost nothing (phase 10, phase 1's finding). task :rubocop do - sh(bundle, "exec", "rubocop", "--fail-level=convention", "--format", "progress") + sh(bundle, "exec", "rubocop", "--fail-level=convention", "--ignore-parent-exclusion", "--format", + "progress",) end # `--autocorrect` (safe only), never `--autocorrect-all`: `-A` applies unsafe corrections too, and diff --git a/test/fixtures/gates/ledger/chapter.md b/test/fixtures/gates/ledger/chapter.md new file mode 100644 index 0000000..0e1e197 --- /dev/null +++ b/test/fixtures/gates/ledger/chapter.md @@ -0,0 +1,7 @@ +## 10. A synthetic two-entry ledger for gates:ledger_audit's own tests + +1. **The probe seam is retired; its contract is not.** *Touches* **SEAM-3**–**SEAM-5**, **IO-6**. + *Judged.* Nothing else. +2. **A second entry about ownership.** *Touches* **BODY-8**. *Judged.* + +Two things that are deliberately **not** deviations: none, in this fixture. diff --git a/test/fixtures/gates/ledger/gems/dexpace-probe/lib/probe.rb b/test/fixtures/gates/ledger/gems/dexpace-probe/lib/probe.rb new file mode 100644 index 0000000..6703112 --- /dev/null +++ b/test/fixtures/gates/ledger/gems/dexpace-probe/lib/probe.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true +# SPDX-License-Identifier: MIT + +# A file that exists, so a ledger row can cite it as evidence. +module Dexpace + module Probe; end +end diff --git a/test/fixtures/gates/ledger/register_docs_only.md b/test/fixtures/gates/ledger/register_docs_only.md new file mode 100644 index 0000000..a354e66 --- /dev/null +++ b/test/fixtures/gates/ledger/register_docs_only.md @@ -0,0 +1,4 @@ +| # | Title | IDs touched | Status | +|---|---|---|---| +| 1 | The probe seam is retired; its contract is not | SEAM-3–SEAM-5, IO-6 | as-built: confirmed — see `docs/work/mvp/phase3/phase3a/some-design.md` | +| 2 | A second entry about ownership | BODY-8 | design only — not yet built | diff --git a/test/fixtures/gates/ledger/register_extra_ids.md b/test/fixtures/gates/ledger/register_extra_ids.md new file mode 100644 index 0000000..fdc616c --- /dev/null +++ b/test/fixtures/gates/ledger/register_extra_ids.md @@ -0,0 +1,4 @@ +| # | Title | IDs touched | Status | +|---|---|---|---| +| 1 | The probe seam is retired; its contract is not | SEAM-3–SEAM-5, IO-6 | design only — not yet built | +| 2 | A second entry about ownership | BODY-8, BODY-9 | design only — not yet built | diff --git a/test/fixtures/gates/ledger/register_ok.md b/test/fixtures/gates/ledger/register_ok.md new file mode 100644 index 0000000..620d894 --- /dev/null +++ b/test/fixtures/gates/ledger/register_ok.md @@ -0,0 +1,4 @@ +| # | Title | IDs touched | Status | +|---|---|---|---| +| 1 | The probe seam is retired; its contract is not | SEAM-3–SEAM-5, IO-6 | as-built: confirmed — `gems/dexpace-probe/lib/probe.rb` defines `Dexpace::Probe` | +| 2 | A second entry about ownership | BODY-8 | design only — not yet built | diff --git a/test/fixtures/gates/ledger/register_swapped.md b/test/fixtures/gates/ledger/register_swapped.md new file mode 100644 index 0000000..9b6827a --- /dev/null +++ b/test/fixtures/gates/ledger/register_swapped.md @@ -0,0 +1,4 @@ +| # | Title | IDs touched | Status | +|---|---|---|---| +| 1 | A second entry about ownership | BODY-8 | design only — not yet built | +| 2 | The probe seam is retired; its contract is not | SEAM-3–SEAM-5, IO-6 | design only — not yet built | diff --git a/test/fixtures/gates/ledger/register_undefined.md b/test/fixtures/gates/ledger/register_undefined.md new file mode 100644 index 0000000..4346e86 --- /dev/null +++ b/test/fixtures/gates/ledger/register_undefined.md @@ -0,0 +1,4 @@ +| # | Title | IDs touched | Status | +|---|---|---|---| +| 1 | The probe seam is retired; its contract is not | SEAM-3–SEAM-5, IO-6 | as-built: confirmed — `gems/dexpace-probe/lib/probe.rb` defines `Dexpace::Completer` | +| 2 | A second entry about ownership | BODY-8 | design only — not yet built | diff --git a/test/fixtures/gates/ledger/register_wrong_ids.md b/test/fixtures/gates/ledger/register_wrong_ids.md new file mode 100644 index 0000000..50a00c2 --- /dev/null +++ b/test/fixtures/gates/ledger/register_wrong_ids.md @@ -0,0 +1,4 @@ +| # | Title | IDs touched | Status | +|---|---|---|---| +| 1 | The probe seam is retired; its contract is not | SEAM-3–SEAM-5 | design only — not yet built | +| 2 | A second entry about ownership | BODY-8 | design only — not yet built | diff --git a/test/fixtures/gates/ledger/workspace/docs/deviations.md b/test/fixtures/gates/ledger/workspace/docs/deviations.md new file mode 100644 index 0000000..50a00c2 --- /dev/null +++ b/test/fixtures/gates/ledger/workspace/docs/deviations.md @@ -0,0 +1,4 @@ +| # | Title | IDs touched | Status | +|---|---|---|---| +| 1 | The probe seam is retired; its contract is not | SEAM-3–SEAM-5 | design only — not yet built | +| 2 | A second entry about ownership | BODY-8 | design only — not yet built | diff --git a/test/fixtures/gates/ledger/workspace/docs/sdk-design-ruby/10-deliberate-deviations-from-the-reference-contract.md b/test/fixtures/gates/ledger/workspace/docs/sdk-design-ruby/10-deliberate-deviations-from-the-reference-contract.md new file mode 100644 index 0000000..0e1e197 --- /dev/null +++ b/test/fixtures/gates/ledger/workspace/docs/sdk-design-ruby/10-deliberate-deviations-from-the-reference-contract.md @@ -0,0 +1,7 @@ +## 10. A synthetic two-entry ledger for gates:ledger_audit's own tests + +1. **The probe seam is retired; its contract is not.** *Touches* **SEAM-3**–**SEAM-5**, **IO-6**. + *Judged.* Nothing else. +2. **A second entry about ownership.** *Touches* **BODY-8**. *Judged.* + +Two things that are deliberately **not** deviations: none, in this fixture. diff --git a/test/fixtures/gates/ledger/workspace/gems/dexpace-probe/lib/probe.rb b/test/fixtures/gates/ledger/workspace/gems/dexpace-probe/lib/probe.rb new file mode 100644 index 0000000..6703112 --- /dev/null +++ b/test/fixtures/gates/ledger/workspace/gems/dexpace-probe/lib/probe.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true +# SPDX-License-Identifier: MIT + +# A file that exists, so a ledger row can cite it as evidence. +module Dexpace + module Probe; end +end diff --git a/test/fixtures/gates/require_allowlist/dot_segments.rb b/test/fixtures/gates/require_allowlist/dot_segments.rb new file mode 100644 index 0000000..9fea85b --- /dev/null +++ b/test/fixtures/gates/require_allowlist/dot_segments.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true +# SPDX-License-Identifier: MIT + +# Gate fixture. A `dexpace/` prefix that climbs back out of the namespace: `dexpace/../json` is +# `json` to Kernel#require, and a prefix test reads it as the gem's own path (phase 0's R3-2). +require "dexpace/../json" +require "dexpace/./../base64" diff --git a/test/fixtures/gates/sole_parse/direct.rb b/test/fixtures/gates/sole_parse/direct.rb new file mode 100644 index 0000000..b7480e5 --- /dev/null +++ b/test/fixtures/gates/sole_parse/direct.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true +# SPDX-License-Identifier: MIT + +# A tool that parses a file directly, outside AstScan.parse: gates:sole_parse must report it. +module RogueDirect + def self.scan(path) = RubyVM::AbstractSyntaxTree.parse_file(path) +end diff --git a/test/fixtures/gates/sole_parse/owner.rb b/test/fixtures/gates/sole_parse/owner.rb new file mode 100644 index 0000000..5596619 --- /dev/null +++ b/test/fixtures/gates/sole_parse/owner.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true +# SPDX-License-Identifier: MIT + +# An owner file: the parse method is exempt, a second method in the same file is not. +module AstScan + def parse(path) + [1].each { RubyVM::AbstractSyntaxTree.parse_file(path) } + end + + def sneaky(path) = RubyVM::AbstractSyntaxTree.parse_file(path) +end diff --git a/test/fixtures/gates/sole_parse/prism.rb b/test/fixtures/gates/sole_parse/prism.rb new file mode 100644 index 0000000..19bbe69 --- /dev/null +++ b/test/fixtures/gates/sole_parse/prism.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true +# SPDX-License-Identifier: MIT + +# Prism returns its diagnostics on the result and never routes them through Warning.warn, so a +# Prism parse is not the hazard the gate exists for and must not be reported. +module PrismOnly + def self.scan(path) = Prism.parse_file(path) +end diff --git a/test/fixtures/gates/sole_parse/reflective.rb b/test/fixtures/gates/sole_parse/reflective.rb new file mode 100644 index 0000000..1d37dc0 --- /dev/null +++ b/test/fixtures/gates/sole_parse/reflective.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true +# SPDX-License-Identifier: MIT + +# The two reflective spellings on the same receiver, by Symbol and by String. The cop directives +# are the fixture's own: the reflective spelling is what the gate must see. +module RogueReflective + # rubocop:disable Style/SendWithLiteralMethodName, Performance/StringIdentifierArgument + def self.by_symbol(path) = ::RubyVM::AbstractSyntaxTree.send(:parse_file, path) + def self.by_string(path) = RubyVM::AbstractSyntaxTree.public_send("parse_file", path) + # rubocop:enable Style/SendWithLiteralMethodName, Performance/StringIdentifierArgument +end diff --git a/test/fixtures/gates/sole_parse/workspace/tools/x.rb b/test/fixtures/gates/sole_parse/workspace/tools/x.rb new file mode 100644 index 0000000..b7480e5 --- /dev/null +++ b/test/fixtures/gates/sole_parse/workspace/tools/x.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true +# SPDX-License-Identifier: MIT + +# A tool that parses a file directly, outside AstScan.parse: gates:sole_parse must report it. +module RogueDirect + def self.scan(path) = RubyVM::AbstractSyntaxTree.parse_file(path) +end diff --git a/test/fixtures/gates/spdx_rbs/empty.rbs b/test/fixtures/gates/spdx_rbs/empty.rbs new file mode 100644 index 0000000..a7baca1 --- /dev/null +++ b/test/fixtures/gates/spdx_rbs/empty.rbs @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: MIT + +# Nothing but comments: passes rbs validate, tells a type checker nothing. diff --git a/test/fixtures/gates/spdx_rbs/header_on_line_three.rbs b/test/fixtures/gates/spdx_rbs/header_on_line_three.rbs new file mode 100644 index 0000000..d5d0815 --- /dev/null +++ b/test/fixtures/gates/spdx_rbs/header_on_line_three.rbs @@ -0,0 +1,7 @@ + + +# SPDX-License-Identifier: MIT + +module Dexpace + VERSION: String +end diff --git a/test/fixtures/gates/spdx_rbs/missing_header.rbs b/test/fixtures/gates/spdx_rbs/missing_header.rbs new file mode 100644 index 0000000..35dc193 --- /dev/null +++ b/test/fixtures/gates/spdx_rbs/missing_header.rbs @@ -0,0 +1,3 @@ +module Dexpace + VERSION: String +end diff --git a/test/fixtures/gates/spdx_rbs/ok.rbs b/test/fixtures/gates/spdx_rbs/ok.rbs new file mode 100644 index 0000000..73401b7 --- /dev/null +++ b/test/fixtures/gates/spdx_rbs/ok.rbs @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: MIT + +module Dexpace + VERSION: String +end diff --git a/test/fixtures/gates/spdx_rbs/workspace/gems/dexpace-probe/sig/dexpace/probe.rbs b/test/fixtures/gates/spdx_rbs/workspace/gems/dexpace-probe/sig/dexpace/probe.rbs new file mode 100644 index 0000000..35dc193 --- /dev/null +++ b/test/fixtures/gates/spdx_rbs/workspace/gems/dexpace-probe/sig/dexpace/probe.rbs @@ -0,0 +1,3 @@ +module Dexpace + VERSION: String +end diff --git a/test/fixtures/surface/dexpace-core.txt b/test/fixtures/surface/dexpace-core.txt index 0cb841f..a6f9b08 100644 --- a/test/fixtures/surface/dexpace-core.txt +++ b/test/fixtures/surface/dexpace-core.txt @@ -864,6 +864,7 @@ Dexpace::Protocol#wire Dexpace::Protocol.build Dexpace::Protocol.parse Dexpace::Protocol::ALIASES : Hash +Dexpace::Protocol::HTTP_1_0 : Dexpace::Protocol Dexpace::Protocol::HTTP_1_1 : Dexpace::Protocol Dexpace::Protocol::HTTP_2 : Dexpace::Protocol Dexpace::Protocol::WIRE_FORMS : Array @@ -1274,6 +1275,7 @@ Dexpace::Status#error? Dexpace::Status#informational? Dexpace::Status#redirect? Dexpace::Status#server_error? +Dexpace::Status#standard? Dexpace::Status#success? Dexpace::Status.build Dexpace::Status.canonical_name diff --git a/test/gates/default_task_test.rb b/test/gates/default_task_test.rb index e6cbe69..296e230 100644 --- a/test/gates/default_task_test.rb +++ b/test/gates/default_task_test.rb @@ -9,27 +9,30 @@ # test:gates` red for seventeen tasks, and a suite that is expected to be red is a suite nobody # reads. class DefaultTaskTest < GateCase - # Phase 0's seventeen, 7b's gates:serde_boundary and phase 9's three repository-wide invariant - # scans -- twenty-one. A gate outside DEFAULT_GATES is off `task default:` and therefore + # Phase 0's seventeen, 7b's gates:serde_boundary, phase 9's three repository-wide invariant + # scans and phase 10's three -- gates:ledger_audit, gates:spdx_rbs, gates:sole_parse -- + # twenty-four. A gate outside DEFAULT_GATES is off `task default:` and therefore # advisory, which NFR-17 forbids, so this list is what makes the count a fact. EXPECTED = %w[ rubocop cops:test rbs:validate steep test:gems test:gates gates:gemspec_audit gates:require_allowlist gates:serde_boundary gates:cause_walk gates:bounded_map - gates:seam_names gates:clean_bundle gates:rbs_surface + gates:seam_names gates:ledger_audit gates:spdx_rbs gates:sole_parse gates:clean_bundle + gates:rbs_surface gates:sig_diff gates:surface_snapshot gates:single_instance gates:versions gates:reproducible yard bundler_audit ].freeze - # The COUNT, asserted against the repository and not only against the literal above: the three - # tasks phase 9 added are gates because they are in `task default:`, and a count read off - # EXPECTED alone would still pass if all three left DEFAULT_GATES together. - test "there are twenty-one gates, and every one of them is blocking" do + # The COUNT, asserted against the repository and not only against the literal above: the six + # tasks phases 9 and 10 added are gates because they are in `task default:`, and a count read off + # EXPECTED alone would still pass if all six left DEFAULT_GATES together. + test "there are twenty-four gates, and every one of them is blocking" do prerequisites = `bundle exec rake -s -P`.split("\n").drop_while { |l| l != "rake default" } .drop(1).take_while { |line| line.start_with?(" ") }.map(&:strip) - assert_equal(21, EXPECTED.size) - assert_equal(21, prerequisites.size, "the default task's prerequisite count drifted") - %w[gates:cause_walk gates:bounded_map gates:seam_names].each do |gate| + assert_equal(24, EXPECTED.size) + assert_equal(24, prerequisites.size, "the default task's prerequisite count drifted") + %w[gates:cause_walk gates:bounded_map gates:seam_names gates:ledger_audit gates:spdx_rbs + gates:sole_parse].each do |gate| assert_includes(prerequisites, gate, "#{gate} is not blocking") end end diff --git a/tools/invariant_gates.rb b/tools/invariant_gates.rb index 4063540..eeba386 100644 --- a/tools/invariant_gates.rb +++ b/tools/invariant_gates.rb @@ -75,7 +75,11 @@ module InvariantGates "to the bound instead of overshooting permanently, which is XCUT-14's drain clause " \ "exactly; every evicted client's pool is retired outside the lock. Phase 9's planning " \ "adjudicated this as the one TRUE positive of six, against 8c's filed plan fence rather " \ - "than its code, and 8c shipped the bound its Task 8 promised", + "than its code, and 8c shipped the bound its Task 8 promised. The MECHANISM behind this " \ + "reading (phase 10, 2026-09-25): clients_test.rb's three XCUT-14 cases -- 'the map is " \ + "bounded at MAX_ORIGINS and drains back to the cap after each insert', 'an evicted " \ + "client's pool is retired and closed, never merely dropped' and 'a client whose reactor " \ + "has closed is evicted before a live one' -- go red if the cap, the loop or the close goes", }.freeze extend self diff --git a/tools/ledger_audit.rb b/tools/ledger_audit.rb new file mode 100644 index 0000000..f613d94 --- /dev/null +++ b/tools/ledger_audit.rb @@ -0,0 +1,155 @@ +# frozen_string_literal: true +# SPDX-License-Identifier: MIT + +# The body of gates:ledger_audit (phase 10, design addendum A11; P10-2 made mechanical). +# +# docs/deviations.md is the as-built audit of design section 10, and a register audited once +# drifts: a chapter entry gains an ID the row never lists, two rows swap, or a verdict row stops +# pointing at anything real. Three decidable assertions keep it tied to the chapter: +# +# 1. Row N carries chapter entry N's subject -- the register title, emphasis stripped, is a WORD +# SUBSEQUENCE of the entry's bolded lead. Equality is the wrong test (the register's titles +# are deliberately shorter); a subsequence is decidable and a renamed or swapped entry still +# breaks it. +# 2. Row N's `IDs touched` column, every range expanded, EQUALS the ID set extracted from entry +# N's text. That is what makes phase 10's count of 124 reproducible from the tree. +# 3. A row whose status is anything but `design only -- not yet built` cites at least one +# `gems/...` path that exists, every `gems/...` path it cites exists, and every `Dexpace::` +# constant it names resolves. A row citing only `docs/...` fails: a design document is never +# evidence. +# +# Every path is joined to `root` -- never read relative to the process CWD, which is the defect +# phase 9 shipped and repaired as P9-27: with DEXPACE_GATE_ROOT pointed at a fixture, a +# CWD-relative read opens the real repository's files and the gate can never be shown to reject +# its fixture. +# +# Assertion 3 needs the gems LOADED to resolve a constant; the rake task requires them and the +# resolver reads the running process. `const_get(name, false)` reaches a private_constant too, so +# a row naming one passes -- the rule is that the name is real, not that it is public. +module LedgerAudit + PREFIXES = %w[SEAM HTTP IO BODY CTX PIPE RECOV RETRY REDIR AUTH PAGE SSE SERDE OBS CFG + TRANSPORT ASYNC XCUT NFR].freeze + CHAPTER = "docs/sdk-design-ruby/10-deliberate-deviations-from-the-reference-contract.md" + REGISTER = "docs/deviations.md" + UNBUILT = "design only — not yet built" + ENTRY = /^(\d+)\.\s+\*\*(.+?)\*\*(.*?)(?=^\d+\.\s+\*\*|^Two things that are deliberately)/m + RANGE = /\b([A-Z]+)-(\d+)\s*[–—]\s*([A-Z]+)-(\d+)\b/ + SINGLE = /\b([A-Z]+)-(\d+)\b/ + CITED_PATH = %r{gems/[\w./-]+} + CONSTANT = /\bDexpace(?:::[A-Z]\w*)+/ + + # Resolves a `Dexpace::A::B` path against the running process. A NameError anywhere on the + # path (including an unloaded root) is "not defined". + DEFAULT_RESOLVE = lambda do |path| + path.split("::").reduce(::Object) { |scope, segment| scope.const_get(segment, false) } + true + rescue ::NameError + false + end + + class << self + # @param root [String] the workspace every relative path is joined to + # @param resolve [#call] answers whether a `Dexpace::` constant path is defined + # @return [Array] one line per offence + def offences(root:, chapter: CHAPTER, register: REGISTER, resolve: DEFAULT_RESOLVE) + entries = chapter_entries(File.read(File.join(root, chapter))) + rows = register_rows(File.read(File.join(root, register))) + count(rows, entries) + entries.zip(rows).each_with_index.flat_map do |(entry, row), index| + row.nil? ? [] : check(index + 1, entry, row, root, resolve) + end + end + + # Every canonical ID in the text, each `A-n–A-m` range expanded. Public because the + # checklist's own row count is read through it. + def ids_in(text) + plain = text.gsub(/[`*]/, "") + (ranged(plain) + singles(plain)).uniq.sort + end + + private + + def count(rows, entries) + return [] if rows.size == entries.size + + ["row count #{rows.size} != chapter entry count #{entries.size}"] + end + + def check(number, entry, row, root, resolve) # rubocop:disable Metrics/ParameterLists -- the module keeps no state (`class << self`), so the per-run root and resolver are threaded to every row check rather than held + drift(number, entry, row) + evidence(number, row[:status], root, resolve) + end + + def ranged(plain) + plain.scan(RANGE).flat_map do |first, low, last, high| + next [] unless first == last && PREFIXES.include?(first) + + (low.to_i..high.to_i).map { |number| "#{first}-#{number}" } + end + end + + def singles(plain) + plain.scan(SINGLE).filter_map do |prefix, number| + "#{prefix}-#{number}" if PREFIXES.include?(prefix) + end + end + + # Each entry is "N. **Subject.** ..." up to the next "N. **" or the closing note. + def chapter_entries(text) + text.scan(ENTRY).map do |_number, subject, body| + { subject: normalise(subject), ids: ids_in(subject + body) } + end + end + + def register_rows(text) + text.lines.grep(/^\|\s*\d+\s*\|/).map do |line| + cells = line.split("|").map(&:strip) + { subject: normalise(cells[2].to_s), ids: ids_in(cells[3].to_s), status: cells[4].to_s } + end + end + + # Assertions 1 and 2. + def drift(number, entry, row) + out = [] + unless subsequence?(row[:subject], entry[:subject]) + out << "row #{number} subject drifted from entry #{number}" + end + missing = entry[:ids] - row[:ids] + extra = row[:ids] - entry[:ids] + unless missing.empty? + out << "row #{number} ID set differs: chapter-only #{missing.join(", ")}" + end + out << "row #{number} ID set differs: row-only #{extra.join(", ")}" unless extra.empty? + out + end + + # Assertion 3, for a row that carries a verdict. + def evidence(number, status, root, resolve) + return [] if status.include?(UNBUILT) + + paths = status.scan(CITED_PATH).map { |path| path.chomp(".") } + real = paths.select { |path| File.exist?(File.join(root, path)) } + if real.empty? + return ["row #{number} carries a verdict and no resolvable as-built evidence (P10-2)"] + end + + (paths - real).map { |path| "row #{number} cites #{path}, which does not exist" } + + undefined(number, status, resolve) + end + + def undefined(number, status, resolve) + status.scan(CONSTANT).uniq.reject { |constant| resolve.call(constant) } + .map { |constant| "row #{number} names #{constant}, which is not defined" } + end + + def normalise(text) = text.gsub(/[`*]/, "").downcase.gsub(/[^a-z0-9 ]/, " ").split.join(" ") + + # A register title is shorter than the chapter's sentence, so equality is the wrong test; a + # word subsequence is decidable and a renamed entry still breaks it. + def subsequence?(short, long) + words = long.split + short.split.all? do |word| + index = words.index(word) + index && (words = words.drop(index + 1)) + end + end + end +end diff --git a/tools/require_allowlist.rb b/tools/require_allowlist.rb index 412b176..db6b835 100644 --- a/tools/require_allowlist.rb +++ b/tools/require_allowlist.rb @@ -71,6 +71,10 @@ def binds?(gem) NO_TRANSPORT = Denial.new(reason: NO_TRANSPORT_REASON, scope: nil) NO_SOCKET = Denial.new(reason: NO_TRANSPORT_REASON, scope: %w[dexpace-core dexpace-transport-*]) + # The two path segments that make a `dexpace/`-prefixed feature name something outside the + # namespace (`dexpace/../json` is `json`), refused by #reachable?. + DOT_SEGMENTS = %w[. ..].freeze + # Names that pass a category test and must still fail. Without this list the gate's message # would be "not in the allowlist", which says nothing about why. DENIED = { @@ -157,8 +161,13 @@ def reason_for(name, permitted, gem) # A declared dependency, or a path inside the gem's own namespace -- including core's entry # point itself, which every adapter requires by the name `dexpace` and not `dexpace/...`. + # + # A dot segment disqualifies the prefix: `dexpace/../json` is `json` to Kernel#require, and a + # bare `start_with?` read it as the gem's own path (phase 10, repairing phase 0's review R3-2). def reachable?(name, permitted) - permitted.include?(name) || name == "dexpace" || name.start_with?("dexpace/") + return true if permitted.include?(name) || name == "dexpace" + + name.start_with?("dexpace/") && !name.split("/").intersect?(DOT_SEGMENTS) end # An adapter may require the one third-party gem its own gemspec declares. In phase 0 no diff --git a/tools/sole_parse.rb b/tools/sole_parse.rb new file mode 100644 index 0000000..62d1c38 --- /dev/null +++ b/tools/sole_parse.rb @@ -0,0 +1,129 @@ +# frozen_string_literal: true +# SPDX-License-Identifier: MIT + +require_relative "ast_scan" + +# The body of gates:sole_parse (phase 10, design addendum A9; NFR-6, NFR-17). +# +# AstScan.parse opens a `$VERBOSE = nil` window around `RubyVM::AbstractSyntaxTree.parse_file`, +# because a SCANNED file's own -w diagnostics reach `Warning.warn` and fire phase 0's FatalWarnings. +# The rule that keeps that the single entry point was a comment in tools/ast_scan.rb ("nothing else +# in this file may call `parse_file`"); this is the assertion, over every repository tool. +# +# Scoped to a `parse_file` whose receiver is the constant `RubyVM::AbstractSyntaxTree` -- `Prism`'s +# `parse_file` (tools/gemspec_audit.rb, require_scan.rb, serde_boundary.rb) returns its diagnostics +# on the result and never routes them through `Warning.warn`, so it is not this hazard and a +# receiver-blind scan reports three false positives there. The reflective spellings +# (`send(:parse_file, …)`, `public_send("parse_file", …)`) on the same receiver are caught too, by +# Symbol on `:LIT` (3.2, 3.3) and `:SYM` (3.4, 4.0) alike. +# +# Deliberately NOT built on AstScan.receiver_calls: that helper records a direct hit only when the +# send carries no argument (5b's `Event#cause(error)` narrowing), and every `parse_file` carries a +# path. Its two private helpers the walk needs are re-stated here rather than made public, which +# would widen phase 9's interface. +# +# Stated gap: a receiver reached through a variable (`ast = RubyVM::AbstractSyntaxTree; +# ast.parse_file(p)`) and a method name held in a variable are undecidable statically, the residue +# phase 9 wrote down for gates:cause_walk. +module SoleParse + TREES = ["tools/**/*.rb", "tasks/**/*.rake", "test/gates/**/*.rb", + ".claude/skills/**/*.rb",].freeze + TARGET = :parse_file + RECEIVER = %w[RubyVM::AbstractSyntaxTree ::RubyVM::AbstractSyntaxTree].freeze + OWNER_FILE = "tools/ast_scan.rb" + OWNER_METHOD = :parse + + # Each entry is a file that calls `parse_file` bare ON PURPOSE, with the reason. Both are the + # contrast that proves AstScan.parse's window: a bare parse of `warns_unused.rb` must emit the + # warning the window suppresses, or the window's own test cannot discriminate. The integrity test + # asserts every entry still names a file the scan reports, so a stale entry silences nothing. + ALLOWED = { + "test/gates/phase9_ruby_facts_test.rb" => + "the bare parse is the measured contrast for AstScan.parse's $VERBOSE window (phase 9)", + "test/gates/invariant_gates_test.rb" => + "the bare parse proves the fixture is not inert before asserting the gate emits nothing", + "test/gates/sole_parse_test.rb" => + "the reflective bare parse is this gate's own contrast: the window it protects still works", + }.freeze + + class << self + # @param root [String] + # @param paths [Array] absolute paths + # @return [Array] "path:line: ..." with absolute paths; the task relativises + def offences(root:, paths: TREES.flat_map { |glob| Dir.glob(File.join(root, glob)) }.sort) + allowed = ALLOWED.keys.map { |path| File.join(root, path) } + owner = File.join(root, OWNER_FILE) + scanned = paths.reject { |path| allowed.include?(path) } + scanned.flat_map { |path| offences_in(path, owner: path == owner) } + end + + def offences_in(path, owner: false) + lines(AstScan.parse(path), owner: owner).map do |line| + "#{path}:#{line}: RubyVM::AbstractSyntaxTree.parse_file outside AstScan.#{OWNER_METHOD} " \ + "(NFR-6)" + end + end + + private + + # Depth-first, carrying the owner flag down, so a block nested inside AstScan#parse is still + # inside it. The exemption is the owner METHOD, not the owner file: the rule is about the + # file's other methods, and a file-wide exemption could not enforce it. + def lines(node, owner:, inside: false, out: []) + return out unless node.is_a?(::RubyVM::AbstractSyntaxTree::Node) + + inside ||= owner && defines_owner?(node) + out << node.first_lineno if !inside && target?(node) + node.children.each { |child| lines(child, owner: owner, inside: inside, out: out) } + out + end + + # `:DEFN` carries the method name first; `:DEFS` (`def self.parse`) carries the receiver first. + def defines_owner?(node) + case node.type + when :DEFN then node.children[0] == OWNER_METHOD + when :DEFS then node.children[1] == OWNER_METHOD + else false + end + end + + def target?(node) + return false unless %i[CALL QCALL].include?(node.type) + return false unless RECEIVER.include?(render(node.children[0])) + + called = node.children[1] + return true if called == TARGET + + AstScan::REFLECTIVE_SENDS.include?(called) && first_argument_name(node) == TARGET + end + + def first_argument_name(node) + args = node.children[2] + return nil unless args.is_a?(::RubyVM::AbstractSyntaxTree::Node) && args.type == :LIST + + literal_name(args.children.first) + end + + # A String or Symbol literal's name, nil for anything dynamic. + def literal_name(node) + return nil unless node.is_a?(::RubyVM::AbstractSyntaxTree::Node) + + value = node.children.first + return value.to_sym if node.type == :STR && value.is_a?(::String) + + AstScan::SYMBOL_TYPES.include?(node.type) && value.is_a?(::Symbol) ? value : nil + end + + def render(node) + return nil unless node.is_a?(::RubyVM::AbstractSyntaxTree::Node) + + case node.type + when :CONST then node.children[0].to_s + when :COLON3 then "::#{node.children[0]}" + when :COLON2 + left = render(node.children[0]) + left.nil? ? nil : "#{left}::#{node.children[1]}" + end + end + end +end diff --git a/tools/spdx_rbs.rb b/tools/spdx_rbs.rb new file mode 100644 index 0000000..eced974 --- /dev/null +++ b/tools/spdx_rbs.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true +# SPDX-License-Identifier: MIT + +require "pathname" +require "rbs" + +# The body of gates:spdx_rbs (phase 10, design addendum A8; NFR-13, NFR-3). +# +# NFR-13's conformance clause is "scan ALL source files for the required header", and sig/ ships +# inside every gem, so the signatures are shipped source. Phase 0 mechanised the header as a +# RuboCop cop, and a cop parses Ruby -- it cannot reach a .rbs file, which is how 307 shipped +# signatures went out with none (phase 9's aggregate run, 2026-09-23). The header is ONE line: +# `# frozen_string_literal: true` has no meaning in RBS. +# +# The second assertion -- no shipped signature declares nothing -- is NFR-3's: an empty .rbs passes +# `rbs validate` and `steep check` and tells a consumer's type checker nothing. On rbs 4.2.0 +# `RBS::Parser.parse_signature` returns `[buffer, directives, declarations]`, and the plan's +# `.flatten.compact.empty?` is never true because the Buffer survives the flatten (measured on an +# empty string and on a comment-only file). The declarations are the third element. +module SpdxRbs + HEADER = "# SPDX-License-Identifier: MIT" + GLOB = "gems/*/sig/**/*.rbs" + + def self.offences(root:, paths: Dir.glob(File.join(root, GLOB))) + paths.flat_map { |path| offences_in(path) } + end + + def self.offences_in(path) + source = File.read(path) + out = [] + out << "#{path}: line 1 is not `#{HEADER}` (NFR-13)" unless source.lines.first&.chomp == HEADER + _buffer, _directives, declarations = RBS::Parser.parse_signature( + RBS::Buffer.new(name: Pathname(path), content: source), + ) + out << "#{path}: declares nothing (NFR-3)" if declarations.empty? + out + rescue RBS::ParsingError => error + ["#{path}: does not parse -- #{error.message}"] + end +end