Repository navigation
Phase 9: cross-cutting invariants — the suites' own suites, the three drivers and the gate fixtures - #100
Merged
Wahbeh-Mohammad merged 9 commits intoSep 24, 2026
Conversation
Phase 9's instrument. Four suites beside phase 8a's transport one, over one shared Runner so the five statuses are decided in one place: InvariantSuite (28 assertions over all twenty-four XCUT ids, ten private_constant groups), PackagingSuite (8 over NFR-1, 2, 3, 10, 11, 13, 14 and 15, read from published gem metadata rather than a source gemspec), CodecSuite (2 portable seam properties, re-derived from the requirement text rather than lifted out of dexpace-serde-json, whose files are untouched) and ExecutorSuite (7, SEAM-25's harness half among them, with ASYNC-3 written so it genuinely fails). Report gains .merge, #to_h and the MUST-level vacuity blocker: Levels carries every requirement id's normative level, generated from appendix C, and a vacuity on a MUST fails the run until an acceptance names that id with a citation. SharedInstance is XCUT-11's structural predicate, R8's disjunction, with the declaration supplied by the driver and never read off the subject. Aggregate gives a whole run one verdict and a preamble that prints what green does not prove; APPENDIX_B.md is the 61-row coverage map. Three repository-wide invariant scans become blocking gates over one RubyVM::AbstractSyntaxTree walker -- gates:cause_walk, gates:bounded_map and gates:seam_names -- taking the set to twenty-one, all in DEFAULT_GATES and all in ci.yml's gates job. gates:serde_boundary now asserts its PENDING list empty, which is the clause SSE-37 handed forward.
Review round 0, R0-1 and R0-2. R0-1. Three waits in the phase-9 suites carried no bound, so a subject that never answers parked the whole run instead of failing an assertion. ExecutorSuite::Shutdown#check_release opened with an unbounded `transport.entered.pop`: an executor that REFUSES the post -- ASYNC-2's saturated queue, or a closed pool -- pushes nothing there and the poster rescues the refusal, so the run hung and the ensure that frees the gate and closes the pool was never reached. The entry pop now carries the assertion's own bound and an expired one is :vacuous with its reason: with no unit started there is no "blocking task on a worker thread", which is the antecedent ASYNC-3's sentence opens with, and a MUST-level vacuity blocks the report anyway. The two `.each(&:join)` sites take the same treatment against a subject whose submission or whose shared call never returns -- ExecutorSuite's concurrent_post and InvariantSuite's drive_threads -- each as a Failure naming the clause, with the whole thread set sharing one budget so sixteen stuck threads cost one bound and not sixteen. That is 8a's rule for this gem, which await_closed_connection's `timeout:` fixed: an adapter that never releases fails the assertion instead of hanging. R0-2. PackagingSuite's NFR-14 assertion documented "with no source supplied it is :vacuous with that reason, never a pass, because 'nobody told us' is not evidence of a single source" and did not implement it -- with no `versions:` every want was nil, every unit was skipped and the check passed having proved nothing. It now raises the vacuity the comment names, and the comment says what a PARTIAL source means too.
Found by writing review round 0's R0-4 test, which drives the shipped DEFAULT_RESOLVE for the first time: every case in packaging_suite_test.rb supplies its own `resolve:`, so the lambda a real driver gets had never run. RubyGems raises Gem::MissingSpecError for a name it cannot resolve, and that descends from Gem::LoadError < LoadError < ScriptError -- it is not a StandardError. So `rescue ::StandardError` let it past, and it escaped Runner's own bare rescue too, aborting the whole run where the suite's contract is one :vacuous result naming the absent unit. Measured on 4.0.6: a Suite.run naming an uninstalled gem errored out of the first assertion instead of reporting eight vacuities. The rescue now names Gem::LoadError beside StandardError, and the comment says why the explicit class is load-bearing.
…oute InvariantSuite::Models#only_closes_what_it_created makes two Checks against one caller-supplied resource: it was not closed, and it still works. `Borrowed#usable?` was `!@closed`, the exact negation of the `closed?` the first Check already reads, so no subject the suite can build separated the two and replacing the second Check's condition with a literal `true` left the whole gem's suite green. The code comment and the phase checklist both claimed the opposite -- that a component tearing the resource down another way would pass the first alone. `Borrowed` now carries that other way. `#finish` is the adapter-side teardown reached without going through `#close` -- `Net::HTTP` spells it exactly that, a pooled client spells it "retire" -- and `usable?` reads both flags, so a holder that shuts a borrowed resource down without calling `#close` passes the first Check and fails the second. The comment is rewritten to say what the double now does and how the gap was measured.
Every assertion is driven against a deliberately non-conforming double before a conforming one makes it pass, which is what proves it can fail at all; the four suites' own suites are that, plus the runner's status loop, the report's blocker, the predicate's disjunction and the aggregate's declaration-keyed map. Three first-party drivers, one per suite with a subject in this repository: core's for InvariantSuite (fifteen declared shared instances and the six factories the suite cannot build), dexpace-serde-json's for CodecSuite, and dexpace-async-thread's for ExecutorSuite, which waives ASYNC-3 by id and then runs it unwaived in a second test that requires the failure, so the waiver cannot outlive the limitation it records. The three new gates get a fixture workspace under DEXPACE_GATE_ROOT and gates:serde_boundary's abort branch a RUBYOPT prelude, because neither had a failing-fixture route at all -- and building the first found that the three gates opened every file relative to the process's CWD.
Review round 0, R0-1 through R0-5, plus the defect R0-4's test found. R0-1. Three tests for the three waits that carried no bound, each driving ONE assertion: a subject that blocks or refuses everywhere is non-conforming in several ways and these are about the bound. An executor that refuses the post makes ASYNC-3 :vacuous; one whose #post blocks fails SEAM-12; a shared instance whose #call never returns fails XCUT-11's first clause. Each double reports its own parked threads, so the release joins every one and the thread count is where teardown expects it. R0-2. The NFR-14 case now asserts the :vacuous its own name always claimed, with the report's reason, and a second case covers the partial source the assertion's comment describes. R0-3. The interpreter-facts test asserted a warning COUNT of one and was red on the declared 3.2 floor, whose parser also reports `unused literal ignored` for the same source. The fact is which $VERBOSE value silences the class, so the assertion is presence and the comment records the measurement. R0-4. PackagingCase::DEFAULT_RESOLVE is public locked surface and ran in no test, because every case supplied its own `resolve:`. It does now -- and the first run of it found the defect the accompanying code commit fixes, which is the whole reason a shipped default needs a test. R0-5. RequirementLevels::ROW's nineteen-prefix restriction was asserted by nothing: loosening it to `[A-Z]+-\d+` left the map at 645 rows over this repository's own appendix C. A synthetic table holding an RFC number and an ISO date in the id column is what discriminates, matching the case the sibling scanner in tools/appendix_b.rb already carries.
Two shapes from the brief's own minimum mutation list survived round 1, and both survived because every double in the file emitted exactly one kind of payload or derived one observation from the other. ChattyPool logs a non-shutdown event payload and a bare String message at construction, beside its one shutdown on close. It is conforming, so the whole report stays green -- and replacing ExecutorCase#shutdowns' Keys::EVENT match with a bare `entries.count` now reddens SEAM-25, XCUT-13's second half and XCUT-22's no-shutdown half together, which is the difference between "one shutdown event" and "one sink write". TearingSeam tears its borrowed resource down through the teardown beside `#close` rather than through `#close`, so `closed?` stays false and only XCUT-22's second Check can report it. Replacing that Check's condition with a literal `true` now fails this test, where before it left the whole gem's suite green. The existing overreaching-holder test gains the message assertion its twin carries.
Review round 2 measured five whole InvariantSuite assertions that could be neutralised with `rake test:gems` still green: XCUT-1, XCUT-2, XCUT-6, XCUT-7 and XCUT-10. Nothing in the repository drove a subject that made any of their clauses fail, so each reported :passed because it could not report anything else -- the failure mode this phase's own R3 exists to prevent. The file's header made it worse by naming XCUT-6, XCUT-7 and XCUT-10 among the IDs it drove. Thirteen defective stand-in cores, in the shape the file already uses: a Policy whose cancellation? reads only the outermost error and one that calls a cancellation retryable (XCUT-1); one matching the cancellation type exactly, so a subtype escapes, and one telling a timeout apart by its message (XCUT-2); a capability query reading only the outermost error and one matching a concrete type (XCUT-6); a configured set ANDed with the baked classifier and a default set that is not the six RETRY-13 fixes (XCUT-7); a safety gate that re-sends a bare POST and one taking a failure parameter (XCUT-10). Two more close the same gap where the reviewer's sweep found only warning-artefact coverage: a redactor forwarding a URL's userinfo and a default-ALLOW header list (XCUT-19), and an error-body snapshot that multiplies its cap (XCUT-24). Measured: neutralising each of the seven assertions whole and warning-free -- `return nil if subject` as the body's first line, so no local is orphaned and NFR-6's fatal-warning hook cannot mask the result -- reddens exactly the new tests for that assertion, thirteen across the seven. Unmutated the file is 32 runs and 66 assertions, and the gem's own suite is 275 and 1,089. The header now names only the IDs the file really drives, and says why XCUT-3 and XCUT-12 are not among them: a double for either would park a thread past the assertion's own bound and leak it into the base case's thread count, so their guards stay the two subject mutations.
Review round 3 swept every phase-9 assertion body one at a time and found one that no document accounted for: XCUT-18's model-layer `header_syntax_validation` could be neutralised whole with the entire `rake test:gems` green, because both of its committed tests pin the model assertion `:passed` and drive only the call-site one. An audit phase ships the claim that its assertions discriminate, and an assertion nothing proves can fail makes that claim false while the report counts it as evidence. Two defective stand-in cores close it, in the shape this file already uses. A permissive validator that refuses nothing reddens the NAME clauses and every VALUE clause but one. A symmetric validator that applies the NAME rule to outbound values as well reddens exactly the HTAB clause, so the asymmetry that IS the requirement -- a name must reject horizontal tab, an outbound value must accept it -- has a control of its own rather than resting on the real subject. Proven by mutation: neutralising `header_syntax_validation` whole reddens both new tests, and deleting the HTAB-accepted check alone reddens only the second. The file is 34 runs / 74 assertions unmutated.
Wahbeh-Mohammad
changed the base branch from
9-phase-9-cross-cutting-invariants-and-conformance
to
main
September 24, 2026 20:56
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #33. Second PR of phase 9's stack — the suites' own suites, the three first-party drivers and the gate fixtures — on top of #99.
What lands
gems/dexpace-core/test/dexpace/cross_cutting_invariants_test.rb(theInvariantSuitedriver — the one file this phase adds undergems/dexpace-core, and it is a test),gems/dexpace-serde-json/…/conformance_test.rb(CodecSuite) andgems/dexpace-async-thread/…/conformance_test.rb(ExecutorSuite). Each calls its suite's.runand asserts on the report.phase9_ruby_facts,invariant_gates,requirement_levels,serde_boundary_pending,appendix_b) with their fixtures undertest/fixtures/gates/invariants/, including a fixture workspace — a miniaturegems/*/libtree — because theDEXPACE_GATE_ROOTroute was broken on arrival (P9-27).test/support/gate_warning_capture.rb, named to collide with nothing across the six suites, and deliberately not requiringdexpace_test_case: armingFatalWarningsfrom a support file armed it for the wholetest:gatesprocess and reddened four pre-existing gate tests (P9-24).What the review rounds added here
Review 3's
R3-1is the finding this phase existed to catch. A systematic sweep of all 46 assertion bodies in the four new suites — one at a time, with the subject kept referenced soNFR-6's fatal-warning hook could not masquerade as the catch — found 42 caught and 4 surviving. Two are named in the checklist as deliberately having no committed double (XCUT-3,XCUT-12), one is a private clause helper whose parent is caught, and the fourth —XCUT-18'sheader_syntax_validation— was named nowhere and could be neutralised whole with the full suite green. An audit phase ships the claim that its assertions discriminate; an assertion nothing proves can fail makes that claim false, and worse than a missing one, because the report counts it as evidence. The defective stand-in core that reddens it is in this PR.Verification
Full
bundle exec rakegreen on 4.0.6 at this tip — all twenty-one gates, 4,206 runs / 74,618 assertions / 0 failures / 0 errors / 9 skips at 99.82%, every skip named by ID against the base's seven. The matrix set green on 3.2.11, 3.3.12 and 3.4.10. Each new gate exits 1 against its fixture workspace, andgates:serde_boundary's abort branch exits 1 under itsRUBYOPTprelude. Three extra seeds identical.