Skip to content

Phase 9: cross-cutting invariants — the suites' own suites, the three drivers and the gate fixtures - #100

Merged
Wahbeh-Mohammad merged 9 commits into
mainfrom
9-phase-9-cross-cutting-invariants-and-conformance-tests
Sep 24, 2026
Merged

Wahbeh-Mohammad merged 9 commits into
mainfrom
9-phase-9-cross-cutting-invariants-and-conformance-tests

Conversation

@Wahbeh-Mohammad

Copy link
Copy Markdown
Contributor

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

  • The three first-party drivers, each in the tree whose objects it drives, on 8a's precedent: gems/dexpace-core/test/dexpace/cross_cutting_invariants_test.rb (the InvariantSuite driver — the one file this phase adds under gems/dexpace-core, and it is a test), gems/dexpace-serde-json/…/conformance_test.rb (CodecSuite) and gems/dexpace-async-thread/…/conformance_test.rb (ExecutorSuite). Each calls its suite's .run and asserts on the report.
  • Nine suites for the suites, each driving a deliberately non-conforming double, because an assertion that has never been seen to fail is not evidence.
  • Five gate suites (phase9_ruby_facts, invariant_gates, requirement_levels, serde_boundary_pending, appendix_b) with their fixtures under test/fixtures/gates/invariants/, including a fixture workspace — a miniature gems/*/lib tree — because the DEXPACE_GATE_ROOT route was broken on arrival (P9-27).
  • test/support/gate_warning_capture.rb, named to collide with nothing across the six suites, and deliberately not requiring dexpace_test_case: arming FatalWarnings from a support file armed it for the whole test:gates process and reddened four pre-existing gate tests (P9-24).

What the review rounds added here

Review 3's R3-1 is 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 so NFR-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's header_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 rake green 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, and gates:serde_boundary's abort branch exits 1 under its RUBYOPT prelude. Three extra seeds identical.

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 Wahbeh-Mohammad added type:test Tests only spec:conformance Conformance-suite behavior, or a gap in the suite itself labels Sep 24, 2026
@Wahbeh-Mohammad
Wahbeh-Mohammad changed the base branch from 9-phase-9-cross-cutting-invariants-and-conformance to main September 24, 2026 20:56
@Wahbeh-Mohammad
Wahbeh-Mohammad merged commit a20f3aa into main Sep 24, 2026
2 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

spec:conformance Conformance-suite behavior, or a gap in the suite itself type:test Tests only

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant