Skip to content

1.0 readiness: the review findings of 2026-09-18 (0.15.0) - #130

Merged
rasuvaeff merged 11 commits into
masterfrom
fix/1-0-readiness
Sep 19, 2026
Merged

rasuvaeff merged 11 commits into
masterfrom
fix/1-0-readiness

Conversation

@rasuvaeff

Copy link
Copy Markdown
Owner

The 1.0-readiness review of 2026-09-18, every open finding of this package: #117#129. Released as 0.15.0 (minor on 0.x — ResponseMaterializer's constructor narrows, the OperationPropertyFailed factories take one more argument, the RequestCaseData/NegativeRequestCaseData psalm aliases are gone, case-shape errors are a type of their own).

Fixed

Contract

Dependencies

openapi-contract ^0.12 (the directional rewrite is the contract's; the request body and responses arrive typed) and property-testing-core ^0.11 accepted.

Zoo and corpus

labels.get, sparse.create, amounts.create, settings.create, precise.get; corpus re-recorded, 435 → 515 cases: openapi-contract#152 (the contract's suite is green against it).

Fixes #117
Fixes #118
Fixes #119
Fixes #120
Fixes #121
Fixes #122
Fixes #123
Fixes #124
Fixes #125
Fixes #126
Fixes #127
Fixes #128
Fixes #129

Validation

$ composer build       PASS (930 tests)
$ composer rector      PASS
$ bin/package-audit    0 errors, 0 warnings
$ make mutation        (running; result posted below)

…s directional rewrite

Require rasuvaeff/openapi-contract ^0.12. Operation::$serverBases is gone
(the first server's base already carried it), so a hand-built operation
without servers is materialized against the root. The request body and
responses are typed shapes now, so the guards that re-checked them are
removed along with the tests that fed hand-built shapes the type excludes.

The directional schema rewrite is delegated to SchemaCheck::effective():
this package carried its own copy, which never recursed into
additionalProperties. The contract's view keeps a readOnly property
declared and typed (only its required entry goes), so whether such a
property is generated is now the compiler's decision: given a direction,
an object never emits a property the other direction owns and never
generates an undeclared member under its name either, because the contract
would type-check that member as the declared property. The negative body
witnesses skip those properties for the same reason.

A JSON media type without a schema, or with the true schema, is generated
unconstrained, as the contract reads it. The dead JsonException branch in
WitnessCheck goes.
… a JSON multipart part

A float went on the wire through (string), which rounds to precision=14
and put a different number there for any value that needed more digits;
it is spelled as json_encode spells it now. The multipleOf generator also
rounded k * m back to the decimal it meant, which from about 64 upwards is
one ulp away from the product the validator computes and tolerates — the
decimal spelling is kept only where the validator agrees with it (#117).

+ leaves RESERVED_RAW: a raw plus in a query is a space to the validator
and to every SAPI (#119).

A multipart part declared application/json carries the JSON encoding of
its value; text/* and application/octet-stream stay verbatim, and any
other part media type fails closed (#122).
…ced in their operation, redaction reachable from the suite

OpenApiPropertyTestingException is implemented by every exception the
package throws. A case that does not have the shape the package writes is
InvalidCase, not UnsupportedGeneration: the latter is a document limitation,
the former a caller error. Psr15Transport's configuration error is a
SuiteConfigurationError. A schema refusal names the operation and the
parameter or body it was compiling (#127).

ContractSuite::redaction() applies a policy to both the curl reproducer
and the minimal case OperationPropertyFailed prints; the counterexample
keeps the case as generated (#124).

CheckFailed::$result is readonly, assigned through the constructor (#126).
ResponseMaterializer builds its @internal collaborators itself (#125).
…s out of missing-required

Every integer is also a number, and JSON Schema reads 1.0 as an integer,
so oneOf over the two was never a choice between disjoint branches: half
the draws matched both and the contract rejected them. The number branch
is generated without integral values, the integer branch keeps only what
the number branch's bounds and multiple refuse, and a number branch that
admits every value outside the integer branch's reach fails closed (#121).

A required path parameter cannot be omitted into invalidity: dropping it
leaves the template literal in the request target, which a string schema
accepts. It is out of the missing-required category and of
negativeCoverage(), and an operation with no other required component
names the reason (#118).
…r member as the wire does

A header value is judged the way the validator and RFC 9110 read it:
obs-text is a field value (a UTF-8 enum member travels), whitespace at
either end is stripped by the reader and so refused, an interior space is
read as sent and so kept — enum: ["New York"] is generated and accepted
(#129). A header const or enum member no field value can carry is refused
when the operation is compiled, not discovered as an exhaustion.

A path or header pattern none of whose strings survives the wire is probed
at compile time and refused by name. An object meets minProperties and
maxProperties by construction — an optional past the ceiling is left out,
one needed for the floor brought in — instead of drawing independent
presence choices and filtering three runs in four away. uniqueItems over a
bounded integer domain smaller than minItems fails closed (#123).
A form property with an object schema and explode: true is written as flat
member=value pairs, and the contract claims only the declared members for
it: an undeclared member the generator added landed as a top-level member
of the body, where it collided with a declared property or violated
additionalProperties: false. Such an object is compiled without extras, and
one whose minProperties its declared properties cannot meet fails closed
when the operation is compiled (#120).
CaseData and its parts (ParameterMap, PartData, BodyData, MisuseData) live
on ContractSuite and are imported everywhere else; the RequestCaseData and
NegativeRequestCaseData aliases and the inline copies in the materializer
and the reproducer are gone. checkValid(), checkNegative(), reproduce(),
redact() and RequestMaterializer::materialize() refuse a case missing a
key with InvalidCase naming it — a hand-written case without misuse used
to raise a warning and pass checkValid() (#128).
…les; zoo for the five new rules; 0.15.0

Requires openapi-contract ^0.12 and accepts property-testing-core ^0.11.
Five zoo operations exercise the header-member judgement, object
cardinality by construction, oneOf over integer and number, the nested
exploded form object and numbers at full precision beside allowReserved;
the contract's generated corpus is re-recorded from them
(openapi-contract#152).
@coderabbitai

coderabbitai Bot commented Sep 19, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: ea6b3594-524c-4658-ac1c-58a646752303


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

The compile-time probes catch GenerationExhaustedException by name, which
exists since the core's 0.10 contract freeze, so ^0.5–^0.9 are no longer
accepted (Prefer lowest was red on the symbol).

Testo maps mutants by #[Covers]: WireAgreementTest now covers the
composition, scalar and parameter-schema compilers it exercises, and the
new behaviour is pinned where the coverage is — object cardinality by
construction, the bounded integer domain, every integer/number oneOf
branch outcome (an empty branch is dropped, the union refused only when
both are), the comma as a separator only in a list or object header, the
reproduce() policy precedence, the case-shape check at every entry point.
…1.96% on 4040 mutants

The wave added ~630 mutants. What is killable is killed (a disjoint branch
declared first, a bounded number item, a domain away from zero, a scalar
and a non-exploded object before the refused one, an exploded object with
neither floor nor members); what remains is recorded by class in AGENTS.md,
and the gate sits below the score again, as its own comment requires.
@rasuvaeff
rasuvaeff merged commit ed13cb3 into master Sep 19, 2026
11 checks passed
@rasuvaeff
rasuvaeff deleted the fix/1-0-readiness branch September 19, 2026 12:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment