Repository navigation
Phase 5b: logging facade and redaction — documentation and phase record - #71
Conversation
Review record for the phase 5b stack (#69 → #70 → #71)Four 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, with a fix round by a fresh agent between each. The stack was cut from phase 5c's docs tip
Nothing was skipped. The pattern across the four rounds is the point of the record: each round's blocking finding was a new layer of the redaction surface reached by the reviewer's own hostile inputs rather than by a mutation — a Round 0 → fixed in round 1
Round 1 → fixed in round 2
Round 2 → fixed in round 3
Round 3 (final) — open, carried into the PR bodies
What the final reviewer verified, in its own runs
Run: workflow |
36952b4 to
a40dbc5
Compare
d98191e to
3c8160c
Compare
a40dbc5 to
60d3e68
Compare
3c8160c to
ab6e06b
Compare
60d3e68 to
f5a9941
Compare
ab6e06b to
cfd699f
Compare
Add fourteen suites under test/dexpace/instrumentation/ -- twelve mirrors of the phase-5b files plus the floor-straddling matrix facts as a standing test and the four downstream wirings -- and rewrite 5c's diagnostics mirror as this phase's, asserting the fold, the snapshot bridge across a real thread boundary, and what the 3.2 floor makes of a restored key, floor-aware through FiberStorageFacts. The two steps are driven through the real Pipeline and AsyncPipeline over phase 2's transport fakes and Completer, with 5c's recording tracer factory and meter and 5a's FakeClock, and every configuration a suite consults is built over FakeConfigSource seams so the process environment is never read. Add two top-level doubles under test/support/: RecordingSink, a real in-memory sink with per-severity enablement, and DiagnosticContext, the save-and-restore block for Fiber storage. Every "never throws" claim is asserted on the substituted value; the inert event is measured at zero allocations per chained call; and the four tests the guard pass found missing are here: a sink that re-enters the logger from inside its own write, the async path's header redaction, a recording cursor whose fork raises, and the headers level with a cap supplied. Forty-eight single-edit mutations of lib/ are caught by these suites on 4.0.6 and 3.2.11.
Review round 0's findings, each with the suite that now runs red on its revert. The redactor suite gains the three inputs that leaked through Redactor#header_value -- a network-path reference on the relative route, an authority the parser rejects on the surgery route, and a userinfo on the exploding-policy fallback -- plus the `%zz` parameter name that used to sentinel the whole URL and the opaque URI's query-shaped tail (P5-100, P5-101). The step suite drives four hostile Location values through a real pipeline at HEADERS and asserts the credential in no payload. The event suite gains a HeaderGateTest class: a credential header written straight into Event#field is REDACTED, dropped in omit mode, and the name gate runs before the URL-value redactor (P5-102). Fact 3 of logging_matrix_facts_test.rb was order-dependent on the 3.2 floor: every teardown's `Fiber[OTHER] = nil` is retained there, so `storage == prior` held for any test but the first and the assertion "exact iff = nil deletes" failed on 12 of 13 orders. The expected map is now stated for the row -- `prior` where `= nil` deletes, `prior.merge(OTHER => nil)` on the floor -- which is the same map whether or not the residue was already present. Green on 3.2.11 with seeds 9818, 1 and 42 and standalone; the old spelling reproduces the failure on seed 9818.
Twelve cases for review round 1 of phase 5b. The proxy resolver's warning and its config diagnostic are driven with nine credential-bearing malformed HTTPS_PROXY values -- the no-port, out-of-range, non-numeric, space-in-host, no-host, double-@, scheme-less, network-path and query-carrying spellings -- asserting the placeholder in both channels and the credential in neither, and the not-a-URI message named once without the parser's text (R1-1, P5-103). The two ambient sources meet the reserved-key table: a url.full, an Authorization and a Location in the logger's global context are redacted once at build and the caller's Hash is untouched; the unfiltered and the listed fold redact the same keys; omit mode drops a non-allow-listed header from either source (R1-2, P5-104). A Location with a leading SP, HTAB, VT, NUL or a trailing high byte loses its userinfo through the redactor and through the step at HEADERS over an inbound Headers built with the space, while the four non-authority spellings are written back as given (R1-3, P5-105). charset=utf-7 and iso-2022-jp-2 decode as UTF-8 at Preview.render and the step at BODY still emits the http.response event (R1-4, P5-106). The two mutations that survived round 1 now fail: a settlement failed from a Thread whose own Fiber storage differs emits the failure event under the caller's captured trace.id and span.id and restores the settler's keys (R1-5); a chunked request and response body of declared length -1 put no size field on either event at HEADERS (R1-6). Three suites are split under Metrics/ClassLength: the redactor's surgery-route cases move to SurgeryRouteTest, the step's header cases to HeaderRedactionTest, and WritingTransport moves into the step suite's shared Fixtures.
The surgery route: every prefix review round 2 named before a real authority -- RFC 3986 Appendix C's delimiters, quotes, parentheses, a word, an encoded space, `+`, `@`, a backslash, an NBSP, a line separator, the BINARY obs-text spellings -- and a doubled proxy URL, through Redactor#header_value, through a real pipeline at HEADERS over inbound Headers, and through Proxy.resolve with a quoted, bracketed or doubled HTTPS_PROXY (P5-107). The P5-105 assertion that a non-whitespace prefix is written back verbatim is gone; the not-an-authority list is now what the parser accepts without one and the backslash spellings, and a path that spells a second authority is asserted as OBS-14's path. Per-value redaction: two Locations through the step and an Array through Event#field, each value redacted before the ", " join (P5-108). The async step under a throwing meter at every level: a later settlement raises into the settler while the caller still receives the response, an inline settlement fails the request, and the counter and the span finish are counted at exactly one each way (P5-109). AllocationDelta#allocations_per_call returns the figure two consecutive measurements agree on: on the 3.2.11 floor a one-time cost of 7 or 28 objects can land inside a measured block after the warm-up, once per process and test-order dependent, so a single measurement came back negative in roughly one whole-file run in fifteen and the exact zero assertion failed it (R2-4). A change to 5c's support file; its five allocation suites re-run green and the M02 guard still runs red.
The redactor suite's not-an-authority list gains a path-absolute reference (`/http://user:secret@h/p`, which the parser accepts as a path and the relative route writes back) and a `/ /` that is not a `//` (P5-100, P5-107): both surfaced by re-running review round 2's battery against the repaired redactor, and both are what RFC 3986 decides rather than what the surgery pattern reaches.
Write the phase-5b checklist: twenty-eight own rows (26 implemented, OBS-19 deferred to phase 8c and OBS-37 post-v1) plus fourteen cross-reference rows, the matrix facts re-run on all four interpreters, the floor decision stated once, the forty-eight guards run red with their messages, and twenty-seven departures from the plan's text. Add the design ledger's as-built addendum, rows P5-91 to P5-99, correcting the design's verified facts 6 and 13 beside the facts themselves. Add docs/sdk-documentation/logging-and-redaction.md, every fence run on 4.0.6 and 3.2.11 with the three interpreter differences stated where they appear, and point architecture.md, the core README, README.md and docs/README.md at it. Bring the four earlier as-built sentences that described this layer as unbuilt to what is true: the tracing page's "no pipeline step exists yet", the configuration page's seven keys and its proxy warning, and the body page's "nothing in core constructs either". Re-derive CLAUDE.md's claims from the tree: eleven built phases, one hundred and forty files under lib/dexpace/ beside version.rb, eleven private constants without a test mirror, eleven checklists, and four constraints-that-bite lines for the logging layer. Append the phase-5b status note to the roadmap after 5c's and three phase-10 inbound bullets: the design's two stale verified facts, the unused-block warning the one-process gate cannot see, and the four earlier tests that read the live materialisation ceiling. Correct the one first-release line that described the step's context probe.
Review round 0 closed three blocking findings and one should-fix on the code and tests branches; this commit carries them into the record. The design's as-built addendum gains P5-100 (OBS-11 on every route of Redactor#header_value, and its collision with OBS-16's "returned verbatim" resolved for OBS-11), P5-101 (a bad percent-encoding in a parameter name is unmatchable, an opaque URI's query-shaped tail is redacted) and P5-102 (OBS-18's name gate is structural at Event#field), and two more "reads differently against the source" bullets beside the build's four: R9's relative and unparseable rows never considered an authority, and the reserved-key table routed a header key through the value redactor alone. The checklist's OBS-11, OBS-12, OBS-15, OBS-16, OBS-17 and OBS-18 rows state the repaired behaviour and cite the tests that hold it; a second guard table records the ten mutations run red after the repair on 4.0.6 and 3.2.11, with the two test-side and two docs-side findings that have no mutation; deviations 28 to 31 and a findings-routed bullet carry the round; the gate paragraph carries the re-proven figures (2,031 runs, 99.98%, honest RuboCop clean at every tip, 385 files at the code tip, three seeds on the floor). The as-built page's first record no longer shows a credential reaching the sink -- Authorization renders as REDACTED and the Location example carries a userinfo that comes back as ***:***@ -- and fence 5 now clears the carrier it set, so the sixteen fences run top to bottom in one process with every stated result holding on 4.0.6 and 3.2.11. CLAUDE.md's redaction constraint states the name gate and the every-route userinfo rule; the roadmap's status note counts thirty-one departures, cites P5-91 to P5-102, and records the round in the shape 5a's note used.
The design's as-built addendum gains rows P5-103 through P5-106 -- the proxy warning's redacted URL, the reserved-key table over all three of OBS-5's sources, the surgery pattern's tolerated leading whitespace, and Preview.decode's UTF-8 fallback for a converter-less charset -- two more "reads differently" entries against the design's own text (the table stated at #field alone; "Encoding.find unreachable from here"), a round-1 paragraph beside round 0's, and P5-100's closing sentence extended to the backslash, triple-slash and non-scheme spellings RFC 3986 excludes (R1-7). The checklist's OBS-9, OBS-10, OBS-11, OBS-16, OBS-18, OBS-24, OBS-38, OBS-39, XCUT-20 and CFG-24/CFG-25 rows state what changed and cite the new tests; a third guard table records mutations 59 through 68 run red on 4.0.6 and 3.2.11; deviations 32 through 37 itemise the round; the findings-routed list closes its seven findings in this stack; and the gate paragraph carries the re-proven figures (2,043 runs at the tests and docs tips, 99.98%; the code tip at 94.81% on both rubies). The as-built page shows the ambient sources redacted, a leading OWS kept on the surgery route, the utf-7 preview and a credential-bearing proxy URL named redacted in both channels, its sixteen fences re-run as one script on 4.0.6 and 3.2.11; CLAUDE.md's redaction constraint states the table's three sources, the leading-OWS route and the proxy warning; the roadmap's phase-5b note records the round and counts thirty-seven departures and P5-91 through P5-106.
The design's as-built addendum gains P5-107 (the surgery route substitutes every //-authority wherever it sits), P5-108 (an Array under a header key is redacted per value before the join) and P5-109 (the async settlement work sits on the source future, and the teardown has one owner), the round-2 paragraph, P5-100's two parsed-path spellings and a superseded closing clause on P5-105. The checklist's OBS-1, OBS-11, OBS-16, OBS-17, OBS-20, OBS-34 and CFG-24/CFG-25 rows state the round, the matrix table gains the floor's one-time allocation cost, departure 15 reads the head's new shape, a fourth guard table carries guards 69-76, and deviations 38-41 record the three repairs and the change to 5c's allocation helper; the gate figures are re-derived from the tree (2,050 runs, 5,655 / 5,656). The as-built page's redactor fence gains a prefixed and a doubled value, its prose states the every-authority rule, the per-value join and the async step's one-owner teardown; CLAUDE.md's redaction and containment constraints and the roadmap's phase-5b status note say the same.
The code tip's 3.2.11 coverage is 94.83% (5,287 / 5,575) as run, not the extrapolated 94.81% (…/5,576); the not-an-authority lists in the checklist's OBS-11 row, P5-107's row and the as-built page name the path-absolute reference and the `/ /` spelling the round-2 battery re-run surfaced.
f5a9941 to
5e420e6
Compare
cfd699f to
da80b0b
Compare
Closes #19. Ninth and last PR of the phase-5 stack — the documentation and the phase record for phase 5b's two PRs below it. Targets the tests PR's branch. Its
CLAUDE.md, READMEs,architecture.mdand roadmap text are written on top of the reconciled 5a + 5c tip, so this is the tip at which the whole phase-5 stack's documentation is consistent; merging bottom-up lands it last.What lands
13 files, +1,872 / −69, all Markdown.
docs/work/mvp/phase5/phase5b/2026-09-09-phase5b-logging-and-redaction-checklist.md, written from the build: 28 own rows — 26 ✅, each naming its task and the test that proves it, withOBS-24andOBS-10stating the Ruby 3.2 floor's behaviour in the row (5c's P5-72, applied as P5-97);OBS-19⏳ to phase 8c Tasks 7, 9 and 15 (the header-drop policy's subject is a transport that drops; P5-32);OBS-37⏳ post-v1 with the async adapters (docs/first-release.md'sOBS-32/OBS-37entry). Plus fourteen cross-reference rows (XCUT-19's five clauses,XCUT-20,XCUT-11,CFG-24/CFG-25,CFG-21,CFG-14,CFG-16,SEAM-25event shape only,BODY-19/BODY-22/BODY-34,BODY-20,OBS-23,PIPE-28,NFR-11) the way 4b, 4c, 5a and 5c carried theirs; what was built; the guards run red (48 mutations plus each review round's repair mutations, both interpreters); the audit groups run; thirty-one deviations from the plan's text (none lowers a gate); the findings routed; the postponed work; and one paragraph per review round recording its repair (the shape 5a's record used).URI#to_s;Instrumentation.diagnostic; the async step's scope and settlement shape; one redactor per logging path;Keys::MESSAGE; the floor's compaction and restore; the top-level doubles; the step's span naming; then the four review rounds' rows — userinfo on every header route, unmatchable bad names and opaque queries, the structural header-name gate, the proxy URL shown through the redactor, the reserved-key table over all threeOBS-5sources, leading OWS, the UTF-8 fallback for a charset with no converter, every//-authority scrubbed per run, per-value header redaction, the async settlement on the source future. Numbering follows the phase-5 blocks fixed at dispatch.docs/sdk-design-ruby/§8.1 and §10 are frozen; the consolidation of P5-16–P5-39 and P5-91–P5-109 and the addenda are a human's, stated in the roadmap note as every phase has. No frozen sentence is contradicted, so no C15.docs/sdk-documentation/logging-and-redaction.md— new as-built page, sixteen fences run top to bottom as one script on 4.0.6 and 3.2.11 (122 checks, 0 fails, no credential in any printed result);architecture.md, the core README,README.mdanddocs/README.mdupdated to point at it. Three earlier as-built pages had described this layer as unbuilt and are corrected in place:tracing-and-metrics.md("no pipeline step exists yet"),configuration.md(seven keys; "waits for that layer"),body.md("nothing in core constructs either").docs/first-release.md— one line corrected: theCTX-16entry said the step "probesrequest.respond_to?(:context)"; as built the step probes nothing and names its span by the method token (P5-99). Nothing else in the file changes.CLAUDE.md— the built-phases sentence now "Phases 0, 1, 2, 3a, 3b, 4a, 4b, 4c, 5a, 5b and 5c are built", the opening paragraph gains the logging layer, the lib-file count (126 → 141 underlib/dexpace/), the test-mirror exceptions (nine → eleven:instrumentation/render.rb,instrumentation/emitter.rb), the checklist count (eleven), and four lines in "Constraints that will bite" (redaction runs at#fieldand never at the sink; the sink is a duck type and core never requireslogger; the emit-once latch releases its mutex before the sink call; a log-emission site is contained and a tracer or meter call never is). The roadmap gains the phase-5b status note after 5c's, naming what phases 2, 3b and 4 postponed here that landed (close_quietly's second route — the phase-2 comment asserting the drop is what changed;Hooks.notify's diagnostic; the body-logging caps' two wirings completing the item 5a half-supplied; P5-8 discharged), what it half-supplies (SEAM-25's shape only), the four review rounds, and three new inbound bullets for phase 10 (the design's stale verified facts 6 and 13; the unused-block warning the one-process gate cannot see; the four 3b/4b ceiling-reading tests).Verification
bundle exec rakeon Ruby 4.0.6 at this tip: exit 0, all seventeen gates green (2,050 / 58,450, 99.98%, YARD 0 undocumented), honest RuboCop 401 files clean.ruby .claude/skills/housekeeping/probe.rb: no drift, all eight checks;verify_knowledge_structure.rbOK; the housekeeping and knowledge suites green.docs/product-spec/,docs/sdk-design-ruby/,docs/knowledge/,docs/deviations.md, every earlier phase's documents, the phase-5 charter, and 5a's and 5c's documents.CLAUDE.mdand the status note state against the tree (141 lib files besideversion.rb, 141 sig mirrors, eleven test-mirror exceptions, eleven checklists — the probe does not read the spelled-out lib count, so it was counted), read 28 checklist rows against their tests (26 proven;CFG-24/CFG-25andCFG-16partially — R3-1 and R3-2), confirmed the phase-8c tasks and thefirst-release.mdentries exist where cited, and ran every page fence by hand on both interpreters.Known follow-ups from the final review (not blocking)
CFG-24/CFG-25row's "the credential in neither channel" and P5-103's "the proxy URL through the redactor and never raw" hold for 30 of 38 spellings; R3-1's eight parsed-path spellings (on the code PR) need a ledger row beside P5-103/P5-107 and those two sentences re-scoped once the belt is per-run. R3-2's repair needs P5-109 and theAsyncStepclass comment's "the order the sync path emits" paragraph updated — true of the log event, not yet of the instruments atBODY.ArraySinkandCountingMeterrather than the suite's doubles, so a reader needs nothing fromtest/support/.