Skip to content

Unevaluated properties#157

Open
wol-soft wants to merge 22 commits into
jsonSchemaDraft2019from
unevaluated-properties
Open

Unevaluated properties#157
wol-soft wants to merge 22 commits into
jsonSchemaDraft2019from
unevaluated-properties

Conversation

@wol-soft

Copy link
Copy Markdown
Owner

No description provided.

wol-soft and others added 22 commits May 28, 2026 23:19
Introduces the scaffolding needed for unevaluatedProperties/unevaluatedItems
tracking without changing any existing validation behaviour:

- AbstractComposedPropertyValidator: add trackEvaluation flag (getter/setter)
  so later phases can gate _compositionEvaluations cache emission per validator.
- Schema: add postCompositionValidators list (addPostCompositionValidator /
  getPostCompositionValidators) for validators that must run after all
  composition validators complete.
- Model.phptpl: extend executeBaseValidators with a third bucket that iterates
  schema.getPostCompositionValidators(); update both guard conditions to trigger
  the method when either the base-validator list or the post-composition list is
  non-empty. Template variables are not used for the new bucket — the schema
  object is already in context and the method is called directly.
- UnevaluatedPropertiesPostProcessor (new internal post processor): activation
  walk via DFS over composition branches and nested schemas, breaking cycles
  through a file+pointer seen-set; sets trackEvaluation on matching validators
  and emits the _compositionEvaluations cache property when activation triggers.
- ModelGenerator: register UnevaluatedPropertiesPostProcessor immediately after
  CompositionValidationPostProcessor.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
# Conflicts:
#	tests/AbstractPHPModelGeneratorTestCase.php
#	tests/PostProcessor/EnumPostProcessorTest.php
When unevaluatedProperties or unevaluatedItems is reachable from a schema,
each composition validator now records the per-branch evaluation outcome in
a typed _compositionEvaluations slot: null when the branch failed, true when
the branch evaluated every property (non-false additionalProperties), an
array of evaluated property names for an inline branch, or the instantiated
branch object for a nested-schema branch. The shared evaluation logic moved
to the production-library CompositionEvaluationTrait so the slot writes
don't duplicate the same loop across every generated class.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three improvements layered on the Phase 2 tracking infrastructure:

Pre-decode patternProperties patterns at generation time. CompositionPropertyDecorator
now exposes getBranchDeclaredPropertyNamesPhpLiteral() and
getBranchPatternPropertyPatternsPhpLiteral() returning var_export'd PHP array
literals ready for direct template embedding. Templates drop the per-name
quoting foreach loop, and the trait no longer calls base64_decode on every
preg_match. var_export also handles all string escaping, so property names
containing single quotes or backslashes no longer produce broken generated
code.

Skip the tracking write on a cache hit. ComposedItem.phptpl initializes
$shouldTrack = true per branch iteration; the mutable-validator cache-hit
branch sets it to false so the success-tracking block is bypassed and the
slot value from the previous run is preserved. On a cache hit $value still
holds the pre-validator array rather than the nested-schema object, so the
old unconditional write would overwrite a valid object slot with null.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
# Conflicts:
#	src/Model/Validator/ConditionalPropertyValidator.php
#	src/Templates/Model.phptpl
#	tests/Basic/PhpAttributeTest.php
Adds the runtime validator for `unevaluatedProperties: false` and
`unevaluatedProperties: <schema>` on top of the existing composition
evaluation tracking. The validator iterates the model keys left over
after subtracting the local `properties`/`patternProperties` set plus the
contributions of every successful sibling composition branch recorded in
`_compositionEvaluations`.

The factory skips emitting any validator when the same schema declares a
non-false `additionalProperties`: that keyword already claims every
remaining key, so unevaluatedProperties would be a no-op.

The cross-schema modification needed for nested branch classes
(getEvaluatedProperties) is now applied during `process()`. To keep that
visible at render time, `RenderQueue::execute` runs every job's
`process()` pass before any `render()` begins. The shared
evaluated/unevaluated computation lives on `CompositionEvaluationTrait`
in the production library; both validator templates collapse to a single
trait-method call.

Adds `RenderHelper::varExportPcrePatterns()` to wrap raw
patternProperties keys with `/`-delimiters and escape embedded `/`. The
existing patternProperties consumers (`AdditionalPropertiesValidator`,
`AdditionalPropertiesPostProcessor`, `CompositionPropertyDecorator`) are
migrated to it, fixing a latent crash on patterns containing slashes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
# Conflicts:
#	tests/PostProcessor/AdditionalPropertiesAccessorPostProcessorTest.php
#	tests/PostProcessor/PatternPropertiesAccessorPostProcessorTest.php
# Conflicts:
#	src/Model/Validator/AbstractComposedPropertyValidator.php
#	src/Model/Validator/ComposedPropertyValidator.php
#	src/Templates/Validator/ComposedItem.phptpl
The dev-jsonSchemaDraft2019 production library widened MaxItemsException
to require an actual count alongside the maxItems limit. The items: false
branch of ItemsValidatorFactory still passed only the maxItems literal
(via [0]), producing a 3-arg call that broke against the 4-arg constructor.
Mirror the working pattern from MaxItems / MinItems factories: define
$count inside the validation expression and pass it as the fourth
constructor argument by reference.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Introduces UnevaluatedPropertiesAccessorPostProcessor, an opt-in post
processor that exposes the keys claimed by `unevaluatedProperties` via a
single getter on the model: `$model->unevaluatedProperties()` returns either
the bare production-library accessor (untyped schema) or a generated
companion class `{ModelName}UnevaluatedProperties` with narrowed get/set/
getAll/remove signatures (typed schema). Private mutator shims
`_setUnevaluatedProperty` / `_removeUnevaluatedProperty` host the runtime
guard and per-call validation; the accessor's closures bind to them via
first-class callable syntax. The shim guard rejects keys that match a
directly-declared property name, a local `patternProperties` pattern, or
any property name/pattern declared inside a composition branch (allOf /
anyOf / oneOf / if / then / else, walked recursively) — those keys belong
to a different contract and routing them through the unevaluated bucket
would silently bypass the typed validator.

Accessor emission honours the keyword's reachability at this schema level:
when `additionalProperties: false` rejects every extra or
`additionalProperties: {schema}` claims every extra, the unevaluated bucket
is unreachable and nothing is emitted (no backing field, accessor method,
shims, companion, or collect flag) — the validator continues to run as a
pure assertion.

UnevaluatedPropertiesValidator gains a `setCollectUnevaluatedProperties`
flag (mirroring the additional-properties pattern) so the validator writes
into `$this->_unevaluatedProperties` after per-key validation succeeds,
with a rollback on whole-validator failure to preserve the prior bucket
state. SerializableTrait in the production library learns to flatten
`_unevaluatedProperties` back into `toArray()` / `toJSON()` output via a
new `_serializeUnevaluatedProperties()` method.

Populate.phptpl's hardcoded rollback / accessor-cache property lists are
replaced with two registries on Schema (`addRollbackProperty`,
`addAccessorCacheProperty`). Existing internal post processors register
their fields into the registries; the new accessor registers
`_unevaluatedProperties` and `_unevaluatedPropertiesAccessor`. The change
is a no-op for any schema that does not opt into the unevaluated accessor.

Test suite (UnevaluatedPropertiesAccessorPostProcessorTest, 15 tests, all
Drafts 2019-09 and 2020-12): typed round-trip; constraint-violation
rejection in direct-exception mode with rollback verification; runtime
guards (declared property, composition-branch property); companion-class
naming and method surface; immutable read-only variant; serialization
round-trip; dead-code suppression; collect-errors-mode setter isolation
(proves the per-call error-registry reset is load-bearing); coexistence
matrix asserting the additionalProperties × unevaluatedProperties emission
policy when both accessor post processors are configured simultaneously.

CLAUDE.md gains an expanded patterns list and recovery procedure for the
"no implementation-plan references in code" rule so that section numbers,
decision identifiers, and similar artifacts of working notes can be
caught before they land.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Splits _executeBaseValidators into two phase methods so the constructor,
setters, and populate can invoke whichever phases they need against
whichever input they need. The schema-level validators that depend on the
merged state of every property — currently just unevaluatedProperties — now
live in _executePostCompositionValidators, and the property-level plus
composition validators stay in _executeBaseValidators.

Constructor calls each method in turn against the raw input. Setters call
_executePostCompositionValidators against a candidate raw input (current
state with the new value plugged in) before commit, so a setter that flips
a oneOf discriminator and would orphan a key from the previous branch's
contract is rejected before the model state changes. populate() runs the
delta check via _executeBaseValidators (which no longer includes the
post-composition phase) and then runs _executePostCompositionValidators
against the would-be merged state; failure rolls every committed property
field back and leaves _rawModelDataInput untouched.

The accessor's _setUnevaluatedProperty shim now calls
_executePostCompositionValidators directly: a single dynamic key only ever
needs the post-composition phase, not the schema's full base + composition
chain. Each call resets its own error registry so a caller catching a
failed set() can continue to use the model.

Test class UnevaluatedPropertiesMutabilityTest covers the setter rollback
path on each composition shape (oneOf, anyOf overlap, anyOf sole-coverer,
if/then/else), the no-op early-return path, populate's merged-state
rollback, the collect-errors mode collation of property-validator failures
together with unevaluated-properties failures in a single registry, and
the cross-state revalidation reading through nested-branch class instances.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Wire cross-state revalidation into _setAdditionalProperty and
_removeAdditionalProperty so dynamic-key mutations see the post-mutation
state. Set runs base validators followed by _executePostCompositionValidators
against a candidate raw input. Remove additionally re-runs every composition
validator against the post-removal raw input so _compositionEvaluations
reflects the new branch outcomes, then runs the post-composition phase. Both
share a single _errorRegistry across phases in collect-errors mode and
snapshot/rollback _additionalProperties, _rawModelDataInput,
_compositionEvaluations, and _propertyValidationState on rejection.

Fix the PHP 8.4 dynamic-property deprecation triggered when a composition
branch declares additionalProperties:{schema}: setupBranchDefaultHelpers no
longer propagates internal properties from a branch's nested schema into
allBranchDefaultAttributeMap. Internal properties have no getter and the
parent never declares the corresponding field, so the propagation loop's
$this->$attr = ... writes were creating dynamic properties on the parent.

Emit a generation-time warning when unevaluatedProperties is suppressed
because a sibling additionalProperties is non-false (the dead-code cell from
the §4.1 matrix). Gate the warning on isOutputEnabled() for parity with the
rest of the warning channel; gate the previously-unconditional echo in
EnumPostProcessor::filterValuesByDeclaredType the same way; let the test
infrastructure respect the caller's outputEnabled so warning-assertion tests
can opt in.

Test coverage added for §5.1.20 (required + unevaluated error precedence in
both direct and collect modes), §5.1.A1 (defaults are not part of the
evaluated set), §5.1.A3/A4 (branch-level additionalProperties per-key
validity), §5.1.25 (pattern + unevaluated bucket coexistence), §5.1.27
(empty allOf), §5.1.M33-34 (additionalProperties accessor under outer
unevaluatedProperties), §5.1.M37 (remove clears backing field plus raw
input), §5.1.M42 (claimed-via-unevaluated key persists after sibling later
covers), the dead-code suppression warning, the Set cross-state revalidation
rejection path, and the dual-error collect-mode aggregation for both Set
(pattern + unevaluated, paired with the per-key validity fix on the
production library) and Remove (min-property + unevaluated via composition
revalidation flipping an anyOf branch). Both dual-error tests assert the
full ErrorRegistryException message via heredoc.

CLAUDE.md adds a rule requiring multi-line exception-message assertions to
use heredoc inlined into the assertSame call.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
A composition branch with its own unevaluatedProperties:{schema} now
contributes the keys it evaluates against that schema to an enclosing
unevaluatedProperties through the same nested-class read-through the trait
already uses for declared properties.

UnevaluatedPropertiesPostProcessor adds an internal _evaluatedPropertyKeys
field to any schema carrying the schema-form UnevaluatedPropertiesValidator.
UnevaluatedProperties.phptpl writes the key into this field on per-key
validation success, with snapshot/rollback mirroring the existing
_unevaluatedProperties discipline.

Nested branch classes gain an _getEvaluatedProperties() method (underscore
prefix + #[Internal]) returning the union of declared properties present in
the raw input and keys recorded in _evaluatedPropertyKeys. The underscore
sidesteps a name collision with a user-declared evaluatedProperties
property's auto-generated getter and the attribute documents the role.

testNestedUnevaluatedInBranchPropagatesClaimsToOuter runs in collect-errors
mode so a single ErrorRegistryException carries the full chain: the outer
allOf failure, the nested class's invalid-unevaluated-property cause, and
the outer unevaluatedProperties: false rejecting the orphaned foo and bar.
The assertion uses heredoc to compare the full message, with an inline
comment explaining why foo appears as an orphan despite passing the
branch's local properties.foo validator — failed composition branches
contribute no annotations to the outer accumulator under JSON Schema
2019-09's applicator rules.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Introduces UnevaluatedItemsValidator and NoUnevaluatedItemsValidator on the
array side, mirroring the property-side flavours. The validator runs at
priority 101 on the array property's validator chain (after composition at
100), wrapping its check in is_array($value) so it short-circuits cleanly
for union types like ["array", "null"]. Both flavours render through a
new collectUnevaluatedIndices() trait method that reads
\$this->_evaluatedItemIndices defensively (the field arrives in a later
stage) and walks \$this->_compositionEvaluations slots filtered by
'kind' => 'array' so the property-side and array-side rebuilds cannot
contaminate each other on mixed-type oneOf.

UnevaluatedItemsValidatorFactory skips emission for absent and true
keywords, throws a SchemaException for non-bool/non-object values, and
warns through the existing echo channel for the three array-side dead-code
shapes — items:false, items:{schema}, and additionalItems:false with tuple
items — that make the keyword unreachable. The property-side factory gains
the same SchemaException guard for non-bool/non-object unevaluatedProperties
values (an oversight that the array-side test surfaced).

Draft_2019_09 registers the factory on the array type. The unevaluated post
processor's needsActivation walk now inspects each property's own JSON
before recursing into its nested schema, so a schema like
{type: object, properties: {tags: {type: array, unevaluatedItems: false}}}
activates the outer object's evaluation tracking (array properties carry
no nested schema, so the previous loop missed them).

The production library gains UnevaluatedItemsException and
InvalidUnevaluatedItemsException. The false-form message reports indices
with the # prefix used by the rest of the array-side family
(InvalidItemException, InvalidTupleException); the schema-form wrapper
preserves the per-index inner exceptions verbatim, so consumers catching
the wrapper can still surface the underlying InvalidTypeException reason.

CLAUDE.md adds two enforcement rules. "Always review the diff against the
repo rules before staging" prescribes a grep recipe for plan/stage/
phase/section references and applies symmetrically to coordinated
production-library checkouts. "Pre-existing rule violations in touched
files" requires sweeping every visible CLAUDE.md violation in any file
edited as part of a task, with one escape hatch when cleanup would balloon
into an unrelated refactor.

UnevaluatedItemsValidatorTest covers the bare false and schema forms
(per-index # notation), uniqueItems-precedence ordering, the four dead-code
warning shapes via expectOutputRegex, and the SchemaException path for
non-bool/non-object values on both keywords. The schema-form test asserts
the full multi-line exception message via heredoc using two failing indices
and pins getInvalidItems() to confirm each failing index's
InvalidTypeException survives the wrapper.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Property-level allOf/anyOf/oneOf/if-then-else compositions on array
properties now contribute their successful branches' claimed indices to a
sibling unevaluatedItems validator. The bridge is a single transient
_compositionAnnotated field indexed by slot key, written wholesale at end
of each composition IIFE, read by the unevaluatedItems template via the
prod-lib trait method.

The activation walk in UnevaluatedPropertiesPostProcessor descends
recursively through composition branches' wrapped properties (so nested
compositions get tracking too) and flags any ArrayContains validators
inside tracked branches with trackBranchMatches, which makes the contains
template export a per-index match map captured by the surrounding
composition body.

Sharp edges fixed along the way:
  * ExtractedMethodValidator method-name hash mixed in spl_object_hash so
    two compositions on the same property no longer collide into one
    method body
  * Activation walk reads source validators via getWrappedProperty()
    because PropertyProxy::getOrderedValidators() returns fresh
    withProperty() clones on every call — flag mutations on the clones
    never reached the rendered instance
  * Composition IIFE leaves $value untouched when slotKey is set so a
    failing composition does not null the caller's array value and
    suppress the downstream unevaluatedItems check in collectErrors mode
  * activateArrayComposition has a hash-keyed cycle guard so a self-
    referencing schema does not recurse indefinitely through $ref-induced
    composition cycles

Requires php-micro-template 1.11.0 for the {# ... #} template-time
comment syntax used by ComposedItem.phptpl. CLAUDE.md adds two rules
surfaced by the work: never prefix local variables with underscore (the
prefix is reserved for class members); when a bug is found, write a
reproducing test before fixing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The post processor previously added `_compositionEvaluations` and
`_compositionAnnotated` unconditionally on every activation-triggering
schema, justified by a comment claiming the trait reads them on
no-composition schemas and PHP 8.2 would deprecate the undeclared-
property access. Both halves of that justification were wrong: the
trait's reads go through `?? []`, which does not trigger the dynamic-
property deprecation, and the read sites only execute when the caller
passes composition slot keys — empty on no-composition schemas. The
fields were padding every activation-triggering generated class with
two unused arrays.

Each field now follows the precedent set by `addEvaluatedPropertyKeysField`
and is declared only when something will write to it. The two activation
passes have been extracted into `activateSchemaLevelTracking` (handles
the root-level composition activation and `_compositionEvaluations`) and
`activateArrayPropertyTracking` (handles the array-property walk plus
`_compositionAnnotated` and `_evaluatedItemIndices`); `process()` now
just orchestrates them.

- `_compositionEvaluations` — only when at least one
  `AbstractComposedPropertyValidator` at the schema level had evaluation
  tracking enabled.
- `_compositionAnnotated` — only when the array-property activation walk
  actually enabled tracking on a property-level composition.
- `_evaluatedItemIndices` — new field, declared only when at least one
  array property carries `unevaluatedItems` (the future write site is in
  `UnevaluatedItems.phptpl`; the field is empty until then).

`UnevaluatedItemsMutabilityTest` exercises what Stage 5.3's transient-
field design relies on without any new validator wiring: the whole-array
setter rebuilds composition annotations against the candidate array and
re-runs `unevaluatedItems` against it (rejecting an array whose tail
index no branch claims, preserving pre-setter state on rejection);
`populate()` with array data of a different length flows through
`PopulatePostProcessor`'s revalidation hook and behaves the same. Both
tests use `setCollectErrors(false)` so the raw `UnevaluatedItemsException`
surfaces directly, mirroring the existing `UnevaluatedPropertiesMutability
Test` pattern.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The unevaluatedItems template now writes _evaluatedItemIndices per
successfully-validated index and snapshots/restores the field around
the IIFE so a nested unevaluatedItems inside a composition branch
contributes its claims to the enclosing accumulator on success and
leaves no partial writes behind on failure.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Verifies the activation walk already handles $ref-resolved schemas through
the property tree it traverses (via getNestedSchema on properties and
composition branches). No generator changes required.

Coverage:
- unevaluatedProperties expressed as {$ref: "#/$defs/foo"}
- $ref-resolved allOf branch contributes annotations to outer accumulator
- Recursive self-$ref terminates and enforces unevaluatedProperties at
  every nesting depth (validated with full-message assertions unwrapping
  through NestedObjectException::getNestedException)
- External $ref to ExternalSchema placeholder used as composition branch:
  markTestIncomplete, blocked by pre-existing composition-processor
  requirement that every composed branch surface a nested schema (same
  limitation as the array-side self-referencing test)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…uated-properties

# Conflicts:
#	src/Model/Validator/Factory/Arrays/ItemsValidatorFactory.php
The merge from jsonSchemaDraft2019 landed pointer stamping on every
validator factory but surfaced two follow-up gaps in the unevaluated
work.

ConditionalPropertyValidator did not update
templateValues['compositionValidator'] at render time.
withJsonPointer() clones the validator, but the clone's template
still pointed at the pre-clone original whose
evaluationTrackingEnabled flag is false. Every if/then/else
composition consequently emitted no _compositionEvaluations writes,
so an if-branch that claimed a property left the outer
unevaluatedProperties accumulator empty and the key was reported as
unevaluated. Late-bind in getCheck() the same way
ComposedPropertyValidator already does.

Neither unevaluated factory (object nor array) chained
->withJsonPointer() on the emitted validator, so
UnevaluatedItems / UnevaluatedProperties / InvalidUnevaluated*
exceptions carried an empty pointer. SetUnevaluatedProperty.phptpl's
two RegularPropertyAsUnevaluated throw-sites emitted no pointer at
all. Wire real pointers through three shapes:

- Factory-level: /path/to/property/unevaluatedItems and
  /unevaluatedProperties, matching the sibling stamping in
  additional/pattern factories.
- Object-property harvest (root declaration): resolved via each
  property's synthesized #[JsonPointer] attribute, matching how
  AdditionalPropertiesAccessorPostProcessor resolves it.
- Composition-branch harvest (inline branch declaration): walked
  recursively with per-branch pointers, so a property declared
  inside /allOf/0 gets /allOf/0/properties/<name>. Pattern-based
  entries carry their /patternProperties/<encoded-regex> pointer
  verbatim.

Pointer utilities extracted to Utils\JsonSchema. encodePointer and
decodePointer moved off the Model\SchemaDefinition\JsonSchema data
class where they had been static bystanders; resolvePrimaryJsonPointer
extracted from the two accessor post processors that had duplicated
it. All call sites aliased-import as JsonSchemaUtil to avoid a name
clash with the model class.

RenderHelper::varExportPcrePatternMap added so a template can
foreach over a regex => pointer map when the pattern-guard needs
both fields at runtime.

Pointer assertions added to the acceptance tests that already caught
the message text and payload. The pattern-focused accessor test's
fixture regex changed from `^x_` to `^s/~` so the emitted pointer
becomes `/patternProperties/^s~1~0`, exercising both RFC 6901
replacements and their ordering in a single assertion. The three
RegularPropertyAsUnevaluatedPropertyException catches now also assert
the full "Couldn't add regular property … as unevaluated property to
object …" message.

Requires the coordinating production-library commit 7bfa040
(jsonPointer arg added to UnevaluatedItems / UnevaluatedProperties /
RegularPropertyAsUnevaluatedProperty / MinContains / MaxContains).
Push that commit and \`composer update\` to lock the new SHA.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
UnevaluatedPropertiesValidatorFactory now consolidates all four
sibling shapes that leave the unevaluatedProperties bucket
permanently empty into a single deadCodeReason() helper:

- additionalProperties: true — every extra flows to the model but
  the accumulator does not credit it, so the validator would still
  fire and defeat the intent of `additionalProperties: true`.
- additionalProperties: {schema} — every extra is claimed and
  validated by additionalProperties; the unevaluated set is empty.
- additionalProperties: false — every extra is rejected before the
  post-composition phase, so the unevaluated validator never runs.
- denyAdditionalProperties() generator flag with additionalProperties
  absent — synthesises the same false shape at configuration time.

Each cell emits a distinct warning via the existing echo channel
so build-output greps can identify the specific dead shape, and
skips validator emission entirely.

Tests added:

- Object-side dead-code data provider covering all four shapes plus
  the denyAdditionalProperties() variant. Rows pass the
  GeneratorConfiguration directly (setOutputEnabled(true) on the
  base, chained with setDenyAdditionalProperties(true) on the deny
  row) so the test signature drops the closure/fallback boilerplate.
- testAdditionalFalseRejectsExtrasEvenWhenUnevaluatedSchemaWouldAccept
  proves the factory suppressed the validator: an extra that would
  satisfy the unevaluated integer schema is still rejected because
  additionalProperties: false runs first.
- testContradictoryInnerSchemaThrowsSchemaExceptionPointingAtFile
  pins that an unevaluatedProperties schema with contradictory allOf
  types surfaces via the existing "conflicting types" SchemaException
  path, with the file identifier preserved.
- testPropertyNamesRejectionPrecedesUnevaluated data-provider variant
  covering both direct-exception and error-collection modes. The
  registry message pin includes the base-phase propertyNames block
  followed by the post-composition InvalidUnevaluatedPropertiesException
  block; template uses `{className}` + str_replace for interpolation.
- testDependentSchemasContributionCreditsDependentPropertiesToAccumulator
  is skipped (dependentSchemas applicator not yet implemented) but
  asserts both the intended success path (kind + covered `extra`)
  and the failure path (kind + non-covered `stray`) so removing the
  skip line makes the applicator's contract live.
- Array-side testContainsWithMinContainsZeroCreditsMatchedIndicesOnly
  and testOneOfBranchesOfDifferentTupleLengthsControlEvaluatedSet
  cover contains + minContains: 0 and mixed-tuple-length composition.

Object-side dead-code fixtures cover all six sibling shapes;
denyAdditional variant paired with the config row.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@coveralls

Copy link
Copy Markdown

Coverage Status

Coverage is 98.076%unevaluated-properties into jsonSchemaDraft2019. No base build found for jsonSchemaDraft2019.

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.

2 participants