Skip to content

Phase 5a: configuration and the clock — documentation and phase record - #65

Merged
Wahbeh-Mohammad merged 14 commits into
mainfrom
18-phase-5a-configuration-docs
Sep 18, 2026
Merged

Wahbeh-Mohammad merged 14 commits into
mainfrom
18-phase-5a-configuration-docs

Conversation

@Wahbeh-Mohammad

Copy link
Copy Markdown
Contributor

Closes #18. Third PR of the nine-PR phase-5 stack — the documentation and the phase record for phase 5a's two PRs below it. Targets the tests PR's branch. Its CLAUDE.md, READMEs, architecture.md and roadmap text are written for 5a only, on top of main at 993c439; the phase 5c docs PR three PRs up carries the reconciled text for both sub-phases, and the phase 5b docs PR at the top of the stack the text for all three.

What lands

9 files, +1,318 / −26, all Markdown.

  • The checklist, docs/work/mvp/phase5/phase5a/2026-09-09-phase5a-configuration-checklist.md, written from the build: 38 own rows — 35 ✅, each naming its task and the test that proves it, and three ✅ in part each naming the unmet clause and its owner: CFG-20 (the fourth clause, cancel-with-interrupt, is ASYNC-3's mechanism under a second ID, ⏳ against docs/first-release.md's Unsatisfied-MUSTs entry, which already names it — R7), CFG-34 (the container-kind clause N/A per §11.15), CFG-35 (the throwable half ⏳ against phase 6a's plan Task 3). Nothing in CFG deferred outright. Plus seven cross-reference rows (XCUT-5, CTX-11, IO-9, BODY-32, SEAM-18, XCUT-11, NFR-11) the way 4b and 4c carried theirs; what was built; the guards run red (36 under 25 headings, seven on 3.2.11 too, plus review round 0's nine repair mutations in a second table); the audit groups run; 26 deviations from the plan's text (none lowers a gate); the findings routed; the postponed work.
  • The phase 5a design's ledger gains an "As built" addendum, P5-51–P5-58, with a round-0 paragraph on P5-55: Configuration::Builder in its own file; Async.delay settling with true; HostPattern's compiled pattern as an ivar; HTTPDate's component round-trip; the context-store cap built on first call under a mutex, replacing phase 4a's load-time assignment and its fresh-process test; the ceiling's five readers moved to the configured function; the three private constants' sig/ mirrors and three private helper modules; the doubles renamed and top-level. Numbering follows the phase-5 blocks fixed at dispatch (5c's as-built from P5-71, 5b's from P5-91). docs/sdk-design-ruby/ §8.2, §8.3, §10.16, §10.17 and §10 are frozen; the addenda and the consolidation are a human's, stated in the roadmap note as 3a–4c did. No frozen sentence is contradicted, so no C15.
  • docs/sdk-documentation/configuration.md — new as-built page (23 fences, 154 annotation checks, every one run verbatim on 4.0.6 and 3.2.11 by implementer and both reviewers; on 3.2.11 the only difference is BuildInfo::IDENTITY_TOKENS' runtime token, as the page says); architecture.md, the core README, README.md and docs/README.md updated to point at it.
  • CLAUDE.md — the built-phases sentence gains 5a, the opening paragraph gains the configuration layer, the lib-file count (103 → 120 under lib/dexpace/), the test-mirror exceptions (six → nine: configuration/parsers.rb, proxy/resolution.rb, deep_value.rb), the checklist count (nine), and four lines in "Constraints that will bite" (the four-tier order that inverts Ruby convention; Clock#sleep as a queue wait, never Kernel#sleep; Time#httpdate to format and HTTPDate.parse to parse; UUID never SecureRandom; Async.delay raising SeamError without a scheduler — P5-9). The roadmap gains the phase-5a status note after 4a's, naming the postponed items that landed (the deadline: keyword and the clock; the context store's cap with its construction decision; the ceiling's source) and the one half-supplied (the body-logging caps' other two wirings are 5b's — 5a declares no key for the preview size or the enablement setting, by design).

Verification

  • bundle exec rake on Ruby 4.0.6 at this tip: exit 0, all seventeen gates green (1,762 / 44,284, 99.97%, YARD 559 methods, 0 undocumented), honest RuboCop 350 files clean.
  • ruby .claude/skills/housekeeping/probe.rb: no drift, all eight checks; verify_knowledge_structure.rb OK; the housekeeping and knowledge suites green. No corpus note was needed (the one candidate — retry-and-resilience/7954a775's classifier placement — is already overridden by the design's R1/P5-1).
  • Empty diff under docs/product-spec/, docs/sdk-design-ruby/, docs/knowledge/, docs/deviations.md, docs/first-release.md, every earlier phase's documents, the phase-5 charter, and 5b's and 5c's documents.
  • The final reviewer re-derived every count CLAUDE.md and the status note state against the tree (120 lib files beside version.rb, 121 sig mirrors, nine test-mirror exceptions, nine checklists), read 36 checklist rows against their tests (33 proven, the three in-part rows partially — as each row itself says), confirmed the first-release.md entry, §11.15 and phase 6a's Task 3 exist where cited, and ran every page fence by hand on both interpreters.

Known follow-ups from the review (not blocking)

  • R1-2 (code) — the property-seam composition grows one closure per Dexpace.configure { |c| c.property(...) } over the live slot; listed on the code PR with its fix shape.
  • R1-1 (tests) — one builder_test.rb assertion reads the host's K through the default env seam; listed on the tests PR.
  • Phase 3a's and 3b's materialisation suites' implicit assumption that the host does not export MAX_MATERIALIZED_BYTES is stated in the IO-9 row; a phase-10 inbound candidate if it should be hardened.
  • The roadmap status note's counts are the post-fix figures (1,762 runs; 4,703 of 4,704 lines).

Phase 5a, Tasks 2 through 5: the four free-standing utilities the
configuration chapter files beside the chain.

- Dexpace::BuildInfo (CFG-36) resolves the SDK version and the runtime's
  version, vendor and OS name into frozen constants at load, each
  falling back to the non-blank "unknown", and composes IDENTITY_TOKENS
  in the fixed SDK-then-runtime order. Every component is read off a
  constant that needs no require, so the allowlist does not grow.
- Dexpace::UUID.generate (CFG-32) draws sixteen bytes from a ::Random
  kept in the fiber-local Thread.current[:dexpace_prng] -- one generator
  per execution context, none shared -- masks the version and variant
  nibbles and returns the frozen canonical form. Never SecureRandom.
- Dexpace::Retryability.retryable_status? (CFG-35's status half,
  XCUT-5's single classifier) answers for an Integer or anything
  answering #code: 408, 429 and every 5xx but 501 and 505.
- Dexpace::HTTPDate (CFG-29 to CFG-31) formats with Time#httpdate and
  parses with an owned anchored grammar, never Time.httpdate, which
  accepts the obsolete RFC 850 and asctime forms; every component is
  checked back against what Time.utc built, because Time.utc(1994, 11,
  31) is silently 1 December (P5-54). The grammar is one Regexp compiled
  once with a per-pattern timeout.

seam_surface_test.rb now counts four stdlib features: http_date.rb
requires "time" in the file that uses it.
Phase 5a, Tasks 6 through 8: the injectable time source and what waits
on it.

- Dexpace::Clock (CFG-15, CFG-16) exposes exactly the three operations
  the _Clock interface declares -- #now, #monotonic on CLOCK_MONOTONIC's
  scale, and #sleep -- with Clock::SYSTEM as the platform-backed default
  and Clock.deadline_in as the one way to compute an instant, so a
  deadline is never derived from Time.now. #sleep is a per-call
  ::Thread::Queue#pop(timeout:) that the cancellation token's #on_cancel
  hook wakes, with the token re-checked after the wake (CFG-17): never
  Kernel#sleep, which cannot be woken without Thread#raise. The hook is
  detached in an ensure.
- Future#wait, #value and Completer#await take deadline: and clock:
  beside the positional cancellation (SEAM-18, phase 2's P2-5 pick-up).
  The deadline is the same timed gate pop, and expiry settles the
  completer through request_cancel(:deadline_expired) rather than
  raising past the wait; the loop lives in the private Async::Deadline
  helper.
- Dexpace::Async.delay (CFG-18) is the scheduler-conditional
  counterpart: a Future settling with true after the duration elapses on
  the registered Fiber.scheduler, and Dexpace::SeamError without one,
  since a thread-backed fallback would violate the headline it exists
  for. It settles with true, not nil, because SEAM-16 makes a nil-valued
  Settlement unconstructible (P5-52).
Phase 5a, Tasks 9 through 13: the four-tier chain, the process-wide
slot, and the two earlier layers that now read them.

- Dexpace::Configuration (CFG-1 to CFG-12, CFG-37, CFG-38) is a frozen
  Data over the override map and the two seams. #string looks up in the
  strict order -- override, environment, normalised property, default --
  that inverts Ruby's convention on purpose (design ledger entry 16);
  CFG-2's emptiness rule applies to the environment tier alone; and
  #raw_property reads the property tier by the exact name. #integer,
  #boolean and #duration route through #string and never raise, with
  durations in Float seconds and a bare number read as milliseconds.
  Configuration::Builder is a file of its own beside its model, as phase
  1 files every builder (P5-51); an inherited seam passes through by
  reference, added properties compose over it, and the two seam
  operations are mutually exclusive on one builder. Keys declares the
  seven CFG-14 names; Sources holds ENVIRONMENT, NONE and .from_hash.
  ConfigParsers and DeepValue (CFG-33, CFG-34: NaN equals NaN and hashes
  alike, signed zeros differ, Integer and Float arrays differ, cycles
  survive) are private_constants with sig/ mirrors (P5-57).
- Dexpace.configure, .configuration and .reset_config! (CFG-13) swap one
  frozen reference under a ::Thread::Mutex and read it lock-free; the
  builder runs outside the mutex, so a block that reads the slot cannot
  deadlock it.
- ContextStore.default is built on its first call, under one mutex,
  reading Keys::MAX_TRACKED_CONTEXTS then and falling back to the
  constant on a non-positive value (P5-55). Phase 4a's load-time
  assignment could never see a Dexpace.configure at boot, and an
  unsynchronised ||= publishes one store per first caller; phase 4a's
  fresh-process case now asserts no store exists before the first call.
- Dexpace::IO.max_materialized_bytes reads Keys::MAX_MATERIALIZED_BYTES
  with the constant as the fallback, and the five readers of the
  constant -- TypedReads#guard_materialization!, Body.clamp_cap,
  StreamBody#replayable? and BufferBody#== -- read the function per call
  (P5-56), so the live configuration governs every materialisation.
Phase 5a, Tasks 14 and 15: CFG-22 through CFG-28.

- Dexpace::Proxy is a frozen Data over eight members, built through
  .build with the port validated into 0..65535 and every non-proxy entry
  a HostPattern; #to_s renders type://user:****@host:port and #inspect
  masks the password and names the challenge handler by class only.
  #bypass? is true under bypass_all or on any matching pattern.
- Proxy::Type is a closed set of three in the pipeline's Stage shape:
  .of is the only lookup and canonicalises a copy back to its constant,
  .new and .[] are private, there is no .build and #with refuses, so
  identity comparison over the constants holds.
- Proxy::HostPattern is a one-member Data over the glob with the
  compiled Regexp a private instance variable set at construction
  (P5-53): anchored \A...\z, case-insensitive, every character but * and
  ? literal, per-pattern timeout, never the process-global one.
- Proxy.resolve reads the chain through the private ProxyResolution and
  never raises on its content: the property tier first, https.* over
  http.* with the port taken from the same layer as the chosen host and
  credentials from https.* only; the environment second, HTTPS_PROXY
  then HTTP_PROXY, with an explicit port required since CFG-25 forbids
  defaulting to 80 or 443. Every malformed input yields nil after one
  Kernel#warn prefixed "[dexpace]" (P5-8), with a StandardError backstop
  behind the explicit branches. http.nonProxyHosts wins over NO_PROXY,
  an escaped separator is literal, and a bare "*" yields nil because a
  proxy no request will use is not a proxy (CFG-27). Nothing in core
  calls .resolve, which is how CFG-28's prohibition on implicit reads is
  met.

proxy.rb loads its three nested files from inside its own class body;
the entry file's phase-5a block is now complete at ten lines.
Phase 5a, Task 16's surface half. The runtime surface snapshot grows by
84 rows, 730 to 814: the public constants and methods of Configuration
and its Builder, Keys and Sources, the slot's three module methods,
Clock and Clock::SYSTEM, Async.delay, the deadline: and clock: keywords
on the future and the completer, Proxy with Type and HostPattern,
HTTPDate, UUID, Retryability, BuildInfo and IO.max_materialized_bytes.
Every new row was read against the object model; no row was removed, and
the three private_constants contribute none.

The smoke suite pins the configuration layer beside the seven before it
and preloads "time" so its "defines nothing outside Dexpace" case keeps
its meaning: http_date.rb requires the feature, which defines Date and
DateTime at the top level.
Review round 0 of the phase-5a stack. CFG-22's canonical text is "never
emit username/password in cleartext", and Proxy#to_s and #inspect
printed the username verbatim beside a masked password (R0-2): both
credentials now render as "****" when present and as nothing when
absent, so a rendering says only whether a credential is set. Four nits
on the same layer: ContextStore.default reads the chain outside its
mutex and only the `||=` runs under it, with a lock-free read of the
published reference in front, so no caller-supplied seam runs under a
non-reentrant lock and a promotion after the first pays no lock (R0-4);
Completer#await validates deadline: and clock: before the settled
short-circuit, so a settled future ignores an expired deadline and not
an invalid one (R0-5); Configuration::Builder.new routes its seed map
through #override and its seams through the setters' guard, so a nil
seed value or a non-callable seam fails fast under CFG-37 instead of
stringifying to "" (R0-6); and a blank HTTPS_PROXY override or property
no longer masks HTTP_PROXY, since CFG-2's fall-through covers only the
environment tier and a blank is not a URL (R0-8). The RBS mirrors
follow; the manifest is unchanged because every added member is
private.
Phase 5a, Task 1 and the test-support half of the plan.

- matrix_facts_test.rb asserts, on whichever interpreter runs it, the
  floor-straddling facts the design rests on: Time.httpdate accepting
  asctime and RFC 850 while rejecting UTC and a missing comma, Fiber
  storage leaking across threads while Thread.current is isolated,
  Random#bytes returning an unfrozen BINARY buffer, Queue#pop with a
  negative timeout returning nil at once and unmounting the fiber under
  a scheduler, the RUBY_* constants needing no require, and ENV handing
  out a frozen String per call. Each is a guard of its own beside the
  suites, and the three doubles are exercised here too.
- FakeClock is the _Clock double: a settable monotonic instant advanced
  by the test, a recorded sleep, and nothing the interface does not
  declare.
- FakeConfigSource is the configuration-seam double, named so because
  fake_source.rb is phase 3a's IO-17 double and would have been
  overwritten by the plan's name (P5-58).
- ParkingScheduler is the Fiber.scheduler double that parks a fiber with
  a deadline and runs its loop in #close; phase 2's ProbeScheduler
  records hooks and cannot drive a timed pop. It defines
  #fiber_interrupt because 4.0.6 warns without it.

All three are top level, as every double on main is.
Phase 5a, Tasks 2 through 8: the suites for BuildInfo, UUID,
Retryability, HTTPDate, Clock, Async.delay and the deadline keyword,
each opening with the IDs it exercises.

- build_info_test.rb: every constant frozen and non-blank, the token
  order, and the blank guard resolving to "unknown" (CFG-36).
- uuid_test.rb: the version-4 shape and the variant nibble over a
  thousand draws, one generator per context and none shared, and a text
  scan of uuid.rb for SecureRandom, since securerandom is allowlisted
  and the require gate would not catch the substitution (CFG-32).
- retryability_test.rb: 408, 429 and every 5xx but 501 and 505, over an
  Integer and a Status, and XCUT-7's subset (CFG-35).
- http_date_test.rb: the specification's own example byte for byte, the
  four zone tokens as one instant, the RFC 850 and asctime rejections,
  the impossible calendar dates Time.utc would normalise, and the
  grammar's per-pattern timeout (CFG-29 to CFG-31).
- clock_test.rb: the three-method seam, a cancel during a thirty-second
  wait waking it inside five seconds, the token re-asserted after the
  wake, and a text scan for Kernel.sleep (CFG-15 to CFG-17).
- delay_test.rb: the zero-duration fast path, SeamError without a
  scheduler, and settlement under ParkingScheduler (CFG-18).
- future_deadline_test.rb and close_quietly_cfg21_test.rb: expiry
  settling the completer with :deadline_expired rather than raising past
  the wait, non-expiry returning the value, the identical error object
  through #value, and CFG-21's late close (SEAM-18, CFG-19 to CFG-21).
Phase 5a, Tasks 9 through 15: the suites for Configuration and its
Builder, Keys and Sources, the slot, the two wirings into earlier
layers, and the proxy model with its resolver.

- configuration_test.rb: CFG-1's order with every tier populated, the
  CFG-2 asymmetry, CFG-3's one-way normalisation, the never-throw
  accessors over every grammar, and DeepValue driven from inside module
  Dexpace -- NaN payloads, signed zeros, Integer against Float, and two
  self-referential arrays (CFG-1 to CFG-7, CFG-33, CFG-34, CFG-38).
- builder_test.rb, keys_test.rb, sources_test.rb: the builder's contract
  and every fail-fast branch, the inherited seam passed by reference and
  composed over, the seven declared key names, and the hermetic hash
  seam (CFG-8 to CFG-12, CFG-14, CFG-37).
- config_test.rb: the slot's identity default, last-write-wins at the
  key level, a re-entrant block, sixteen writers against sixteen readers
  seeing only whole snapshots, and a text pin that both publications
  happen inside the one mutex, because under the GVL no behavioural case
  can see the mutex dropped (CFG-13, XCUT-11).
- context_store_config_test.rb: the cap read on first use in a fresh
  process, the non-positive fallback, and sixteen threads reaching
  .default first getting one store under a seam slowed to 50 ms, which
  is the case an unsynchronised ||= fails (CTX-11).
- io_ceiling_test.rb: the configured ceiling honoured by every reader,
  including Buffer#snapshot through the materialisation guard and
  Body.buffer_bounded clamping down and never up (IO-9, BODY-32).
- proxy_test.rb, type_test.rb, host_pattern_test.rb: the model's
  refusals and masked renderings, the closed type set, the anchored
  case-insensitive glob with its timeout, and the resolver over both
  tiers -- the same-layer port rule, the credential source, every
  malformed input answered by its own branch and the backstop only by a
  raising seam, port 65536 refused by the resolver's own check, and the
  NO_PROXY and http.nonProxyHosts splitting rules (CFG-22 to CFG-28).
Review round 0 of the phase-5a stack. The CFG-28 case resolved against
the empty slot, whose environment seam is the real ENV, and failed on a
host with HTTPS_PROXY set (R0-1, blocking): the lib scan stays, and the
default-argument half now installs a fake seam in the slot through
Dexpace.configure, asserts .resolve with no argument reads it, and
resets in ensure. The ceiling suite seeds the slot's environment seam
with an empty fake in every setup and after each mid-case reset, and
the cap suite's fresh processes clear MAX_TRACKED_CONTEXTS by default
(R0-7). The masking cases flip to the username masked with the password
and add the one-credential shapes (R0-2). The race case slows `new`
rather than the seams, because the seams now run outside the lock and a
slow seam alone no longer discriminates the mutex; two cases observe
that the seams run with the mutex unowned and that a later .default
runs no seam (R0-4). One case each for a settled future refusing an
invalid deadline (R0-5), Builder.new refusing a nil seed value, a blank
seed key and a non-callable seam (R0-6), and a blank HTTPS_PROXY from
any tier not masking HTTP_PROXY (R0-8).
The checklist, written from what was built: thirty-eight own rows, CFG-1
to CFG-38, 35 implemented and 3 implemented in part -- CFG-20, whose
fourth clause docs/first-release.md's Unsatisfied-MUSTs entry already
names; CFG-34, whose container clause has no Ruby manifestation per
section 11.15; CFG-35, whose throwable half is phase 6a's Task 3 --
nothing deferred, nothing not built, nothing not applicable, plus seven
cross-reference rows for the IDs of other phases this build reached. It
carries the thirty-six single-edit mutations run red under twenty-five
headings, the audit groups run, twenty-three departures from the plan's
text with their reasons, the findings routed, and the postponed work
with its owners.

The design gains an "As built" addendum with rows P5-51 to P5-58,
numbered from the tree so the parallel phase-5 lanes cannot collide: the
Builder's own file, Async.delay settling with true, the one-member
HostPattern, HTTPDate's component round trip, the first-call store
construction under a mutex, the ceiling's readers moved to the function,
the private constants' sig/ mirrors, and the three doubles' names. The
addendum records six places the design's text reads differently against
the source without rewriting it, and states that no frozen-chapter
sentence is contradicted, so docs/first-release.md is untouched.
docs/sdk-documentation/configuration.md is the ninth as-built page: the
four-tier chain and the strict order that inverts Ruby's habit, the
never-throw typed accessors, the builder with its two seams and
derivation, the process-wide slot, the seven declared keys and the two
earlier layers that read them, the injectable clock and its cancellable
queue wait, the deadline on the future and the scheduler-conditional
delay, the proxy model with its closed type set, host patterns and
never-raising resolver, the four utilities, and the private structural
equality nothing calls yet. Every example was run verbatim on 4.0.6 and
3.2.11 and printed the same, but for the floor's Hash#inspect spelling
and BuildInfo::RUNTIME_VERSION, which the page says.

architecture.md, the core README, README.md and docs/README.md point at
it, and each names phase 5a among the built phases.
CLAUDE.md's built-phases paragraph gains the configuration layer in the
same shape as the seven before it, citing the 5a checklist; the counts
the probe reads are re-derived from the tree -- one hundred and twenty
phase files under lib/dexpace/, nine private_constants without a test/
mirror, nine checklists written -- and the constraints-that-bite list
gains five lines: the chain's precedence and the CFG-2 asymmetry, the
queue wait behind Clock#sleep and the deadline, Time#httpdate against
the owned parser, the UUID generator's fiber-local Random, and the
context store's first-call construction beside the per-call ceiling.

The roadmap's status notes gain the 2026-09-17 entry after 4a's, in the
same shape: the stack and its base, the seventeen files, the three
pick-ups from earlier phases and how each landed -- the deadline as a
timed gate pop, the store's cap by option (b), the ceiling's readers
moved -- the fourth left to 5b, the checklist's counts, the gate runs,
the thirty-six guards, the facts re-verified on every row, the four
shape changes against the design, and the addenda that remain a human's
because the design chapters are frozen.
Review round 0 of the phase-5a stack. The CFG-22 row now says both
credentials are masked and why, the CFG-28 row that its slot half is
proven through a fake seam and not the real ENV, the CFG-24 row that a
blank HTTPS_PROXY from any tier is absent for the preference, the CFG-37
row that Builder.new validates its seeds, the CTX-11, XCUT-11 and
SEAM-18 rows the seams-outside-the-mutex shape and the validate-before-
return order, the IO-9 row the hermetic slot and the assumption phase
3a's and 3b's own suites still make, and the CFG-26 row the plan's
self-review item 4 as the reading behind the dropped empty token (R0-9).
A second guard table carries the nine mutations run red after the
repair, deviations 24-26 and a findings-routed bullet carry the nine
findings, and the design's P5-55 row is amended for the chain read
outside the lock with a round-0 paragraph beside the addendum. The
as-built page's proxy fences render the masked username, gain the
one-credential shape and the blank-HTTPS_PROXY case, and its store
paragraph says the seams never run under the lock; every fence was
re-run on 4.0.6 (154 checks, 0 mismatches) and 3.2.11. CLAUDE.md cites
P5-9 for Async.delay's raise-rather-than-degrade decision (R0-3) and
describes the store's lock shape; the roadmap's status note gains the
round-0 sentence and the re-run counts (1,762 runs; 4,703 of 4,704).
@Wahbeh-Mohammad

Copy link
Copy Markdown
Contributor Author

Review record for the phase 5a stack (#63 → #64 → #65)

Two independent reviews, each by a fresh agent with no memory of the previous one, each re-running the gates itself on every tip and both interpreters; one fix round between them. The stack was cut from main at 993c439 (pinned and confirmed unchanged by every agent at start and finish) beside the phase 5c lane, which is cut from the same commit; whichever phase-5 stack merges second gets a rebase-and-reprove pass. Every finding got a stable id, a severity, a file:line and the command output that was its evidence. approve required zero blocking and zero should-fix; the run was capped at four reviews and stopped at the second because it approved.

Round Verdict Blocking Should-fix Nits Disposition
0 changes required 1 1 7 all 9 fixed
1 (final) approve 0 0 2 carried into the PR bodies

Nothing was disputed, deferred or skipped. The blocking finding and four of the nits were hermeticity — a suite reading the host's real environment through the process-wide slot's default ENV seam, exactly what CFG-11's injectable seams exist to prevent — found by the reviewer exporting HTTPS_PROXY and running the suite, not by a mutation.

Round 0 → fixed in round 1

  • R0-1 blocking — proxy_test.rb's CFG-28 case assert_nil(Dexpace::Proxy.resolve) resolved against Configuration::EMPTY, whose env seam is Sources::ENVIRONMENT, so it read the host's HTTPS_PROXY/HTTP_PROXY/NO_PROXY; reproduced with HTTPS_PROXY=http://corp-proxy.example:3128 → 1 failure (a port-less value would instead emit the CFG-25 warning, which the shared test case turns into a failure). Fixed on the tests branch (fc08160): the case keeps only its lib scan, and a new case installs FakeConfigSource seams through Dexpace.configure, asserts the no-argument .resolve reads the slot, swaps in an empty fake for nil, resets in ensure; reproduced red first.
  • R0-2 should-fix — appendix C's CFG-22: "Its string rendering MUST mask credentials (never emit username/password in cleartext)"; as built Proxy#to_s rendered http://admin:****@proxy.internal:8080 and #inspect username="admin", the plan's own fence quoting the parenthetical and then printing the username beneath it. Fixed on the code branch (0294e56): #to_s renders type://****:****@host:port (****@ username-only, :****@ password-only), #inspect username="****"/password="****"; MASK a private_constant; the assertions flipped and the one-credential shapes added; the page's fences updated.
  • R0-3 nit — CLAUDE.md cited P5-52 for Async.delay's raise-rather-than-degrade decision; that is P5-9. Fixed on docs (d5df0ca): cites P5-9, and P5-52 for the true it settles with.
  • R0-4 nit — ContextStore.default ran the caller's env and property seams under @default_mutex (a seam re-entering .default would deadlock the non-reentrant mutex). Fixed on code: def default = @default || build_default, with cap = configured_cap read before synchronize { @default ||= new(cap: cap) } — no user code under the lock, no lock and no seam after the first call; the race fixture consequently slows new (a prepend in the fresh child) rather than the seams, and a case asserts no seam is consulted after the first call.
  • R0-5 nit — Completer#await returned early on a settled completer before validating deadline:/clock:, so fulfil(:v); value(deadline: "5") returned :v. Fixed on code: Deadline.validate precedes the settled short-circuit; a case on tests.
  • R0-6 nit — Configuration::Builder.new(overrides: { "K" => nil }) stringified nil to "" instead of failing fast (CFG-37). Fixed on code, slightly wider than asked: initialize refuses a nil or non-Hash seed map and routes every seed through #override and both seams through the setters' guard; cases on tests.
  • R0-7 nit — io_ceiling_test.rb and context_store_config_test.rb depended on the host not defining MAX_MATERIALIZED_BYTES/MAX_TRACKED_CONTEXTS. Fixed on tests: an empty FakeConfigSource installed as the slot's env seam in setup and after each mid-case reset; the fresh children clear MAX_TRACKED_CONTEXTS by default.
  • R0-8 nit — a set-but-blank HTTPS_PROXY override or property masked HTTP_PROXY (CFG-2's emptiness is the environment tier's only, so "" is truthy). Fixed on code: environment_url takes the first non-blank of the two; a case on tests.
  • R0-9 nit — the CFG-26 row's dropped whitespace-only token follows the plan's self-review item 4, which the design's fact 12 (["a", "", "c"]) does not share, and nothing cited the plan item as the reading. Fixed on docs: the row cites the item and states the design's list is the pre-compilation token list.

Round 1 (final) — open, carried into the PR bodies

  • R1-1 nit — builder_test.rb:203: Configuration::Builder.new(property_source: nil).build.string("K") builds over Sources::ENVIRONMENT and reads the host's K (K=hostile → 1 failure). The last 5a case resolving a key through the default env seam; pass env_source: Sources::NONE. Listed on the tests PR.
  • R1-2 nit — every Dexpace.configure { |c| c.property(...) } over the live slot composes one more closure over the inherited property seam, so repeated reconfiguration without reset_config! grows the lookup chain without bound: raw_property("missing") ×1000 measured at 238 µs on a fresh slot and 275,343 µs (1157×) after 2,000 configures, every earlier table retained. Fix shape: flatten over a builder-made inherited table into one Sources.from_hash, compose only over a caller-installed seam. Listed on the code PR.

What the final reviewer verified, in its own runs

  1. Gates — code tip 0294e56: all seventeen individually on 4.0.6, all green including the SimpleCov floor (1,553 runs / 8,471 assertions, 93.45%; the tolerated red never occurred), honest RuboCop 328 files clean; the matrix set on 3.2.11 (93.42%). Tests tip fc08160: full bundle exec rake green on 4.0.6 (1,762 runs / 44,284 assertions, 4,703 / 4,704 lines, 99.97%), RuboCop 350 files clean, the matrix set on 3.2.11 (4,655 / 4,656) and 3.4.10, the core suite on seeds 1, 99991, 424242 on 4.0.6 and 31337 on 3.2.11 at 1,745 runs every time, the six hang-shaped suites five repeats each with no hang or flake. Docs tip d5df0ca: full rake green (YARD 559 methods, 0 undocumented), probe exit 0 on all eight checks, knowledge verifier OK, all 23 configuration.md fences (154 checks) run on both interpreters with 0 mismatches on 4.0.6 and exactly the two quoted resolver warnings on stderr.
  2. Every prior finding individually — R0-1 with HTTPS_PROXY/HTTP_PROXY/NO_PROXY exported and with a port-less HTTPS_PROXY (35/293 green twice); R0-7 under hostile MAX_MATERIALIZED_BYTES/MAX_TRACKED_CONTEXTS (18/99 green); R0-2, R0-4, R0-5, R0-6 and R0-8 by hand on 4.0.6 and 3.2.11 and by regression mutations M21, M31–M35 and M46 going red when each repair is reverted (M28: sixteen stores from an unsynchronised ||= on both interpreters); R0-3 and R0-9 by reading the cited rows and the plan's self-review item at its line.
  3. The mutation battery — 53 single-edit mutants applied by the reviewer itself (47 in round 0, also all caught), every one caught, ten on 3.2.11 as well: the tier order swapped; CFG-3's normalisation dropped and CFG-4's accessor normalising; 'yes' accepted; a bare number as seconds; the builder aliasing the caller's hash; the slot swapped without the mutex; the wait as sleep and the pop without timeout: (hangs by construction; killed at exit 124); the token not re-checked; #monotonic via Time.now; the deadline not calling request_cancel(:deadline_expired); #await raising on deadline; CFG-19 without each_cause; the glob unanchored, unescaped, case-sensitive; the resolver raising; 65536 and 0 accepted; the escape mis-split; UTC/+0000/+00:00 rejected; blank accepted; the version and variant nibbles wrong; SecureRandom substituted; NaN by == with two distinct objects; 0.0 == -0.0; the hash inconsistent; 501 retryable; a blank BuildInfo token; a bare ArgumentError; the eager default replaced by a bare ||=; a Regexp without timeout:; a ceiling reader back on the constant; chain-wide emptiness; and the round-0 repairs reverted one by one.
  4. The layer by experiment, both interpreters — CFG-1–CFG-4's four tiers removed one at a time; Clock::SYSTEM.sleep(5, cancellation:) cancelled at 50 ms returning in 0.050 s, sleep(0.05) in 0.0501 s, an already-cancelled token at once, a FakeClock advancing 1,000 s in 0.0 s; the bounded wait over an unsettled Completer (:deadline_expired, #await returning self, nil as phase 2, the real clock cancelling at 0.05 s); Async.delay (SeamError naming Fiber.set_scheduler without a scheduler; true at once for 0; InvalidArgumentError for −1; #block once and #kernel_sleep never under the parking scheduler); the proxy resolver over hand-built sources (bypass true/false, ['a|b','c'] and ['a,b','c'], nil plus exactly one warning for 65536, garbage and a port-less URL, a newline never matching *, the pattern assert_same across lookups); HTTPDate (the example verbatim, all four zones to one instant, weekday and case tolerated, every malformed input InvalidArgumentError); UUID (10,000 distinct, all v4, 8,000 concurrent distinct, three Random objects across a fiber and a thread, no SecureRandom in non-comment lines); DeepValue through module Dexpace (NaN pair equal and hash-equal, signed zeros unequal, [1] != [1.0], a cycle returning, ::Dexpace::DeepValue a NameError); Retryability over the whole set and a Status, no throwable-half method; BuildInfo under --disable-gems on all four Rubies with RbConfig undefined before and after; Dexpace.configure from 16 threads with 0 torn observations and the mutex held across the assignment only; ContextStore.default the same object across calls and IO.max_materialized_bytes 67,108,864 by default and 4 when configured, with body.rb, buffer_body.rb, stream_body.rb and typed_reads.rb diffed as reading the function; proxy/type.rb and configuration/keys.rb loaded first raising superclass mismatch.
  5. Layering — main ⊂ 0294e56 ⊂ fc08160 ⊂ d5df0ca, the base unchanged; 14 commits (5 feat: + 1 fix:, 4 test:, 4 docs:), subjects 50–72 characters, no body line over 72, no attribution lines; every file in each diff classified (51 code, 22 tests, 9 docs); the eight earlier lib files diffed one by one — completer.rb/future.rb widened by two keywords with defaults, context_store.rb's construction shape replaced (P5-55, recorded), io.rb one module function, four readers moved to it, no narrowing; the three gate-read test files on the code branch for the stated reasons; the manifest +84 / −0 matching the reviewer's own derivation (7+2+2+3+6+1+14+7+8+4+3+1+14+5+7) with no row for a private constant or helper; tools/ untouched; sig/ 121 mirrors for 121 lib files; phase 3a's fake_source.rb and phase 2's probe_scheduler.rb byte-identical to main; nothing of 5b's or 5c's; empty diffs under the frozen trees, docs/deviations.md, docs/first-release.md, every earlier phase's documents, the charter, 5b's and 5c's.
  6. Global constraints by script over the 39 new .rb — both header lines; only time and uri added as plain requires; the only blocking primitives the three Queue#pop(timeout:) sites plus phase 2's unbounded gate pop when no limit is set; every Regexp with timeout:; no Time.parse; no SecureRandom in uuid.rb; every downcase bare; no bare core constant inside module Dexpace; no ** splat in lib; no real ENV read outside the default env-source object; no assert_nothing_raised; no Ractor claim for Configuration.
  7. Plan and spec coverage — the 16 tasks' files; each Scope bullet of Phase 5a: Configuration and the Clock #18 mapped; 36 of the 45 rows read against their tests (33 proven, the three in-part rows partially — as each says) with the first-release.md entry, §11.15 and phase 6a's Task 3 verified at their cited places; --req over the sampled IDs with the appendix-B roll-ups beside the substantive rules; the ledger addendum P5-51–P5-58 with the cap decision, the ceiling decision and the double naming each recorded with its test; the design's findings verified as owned and not re-recorded.
  8. Phase record — CLAUDE.md counts re-derived from the docs-tip tree (120 lib files beside version.rb, 121 sig mirrors, nine test-mirror exceptions, nine checklists); the status note's numbers against the checklist and the re-run figures; nothing describing 5b or 5c as landed.
  9. Report discrepancies — one coverage line of run-to-run variance on 3.2.11 (the registry race branch); the fix report's "slows new" fixture confirmed; the design's "no 5a test reads the process environment" contradicted once more by R1-1 (nit, improbable key). One observation, not a finding: a standalone require "dexpace/io/buffer" or "dexpace/context_store" now fails on Dexpace.configuration where it loaded on main — the same call-time pattern main already has elsewhere; the entry file is the load path.

Run: workflow wf_0ef75772-dce, 4 agents (1 implementer, 2 reviewers, 1 fixer), 1.6M tokens, 3.9 h.

@Wahbeh-Mohammad
Wahbeh-Mohammad changed the base branch from 18-phase-5a-configuration-tests to main September 18, 2026 08:49
@Wahbeh-Mohammad
Wahbeh-Mohammad merged commit 5479da0 into main Sep 18, 2026
5 checks passed
@Wahbeh-Mohammad
Wahbeh-Mohammad deleted the 18-phase-5a-configuration-docs branch September 18, 2026 09:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Phase 5a: Configuration and the Clock

1 participant