Commit Graph

129 Commits

Author SHA1 Message Date
ec4b2aa80c fix(schema): enforce substitution group exclusions based on final 2026-06-15 20:51:43 +02:00
5a2e2f3a40 fix(schema): enforce targetNamespace matching on local references 2026-06-15 20:08:39 +02:00
614209e9c1 fix(schema): enforce variety constraints on list itemType and union memberTypes 2026-06-15 20:01:51 +02:00
0496c6948a fix(schema): implement namespace-aware reference resolution checks 2026-06-15 19:44:32 +02:00
ffff13da57 fix(schema): enforce simpleType restriction structure and QName prefix bindings 2026-06-15 19:22:10 +02:00
1dcff00874 fix(schema): reject local-use attributes on top-level group and attributeGroup definitions 2026-06-15 19:03:38 +02:00
9004efe400 fix(schema): inherit and prohibit attributes under complexContent restriction 2026-06-15 18:52:23 +02:00
80064be6b8 feat(schema): validate identity constraint keyref field arity mismatch 2026-06-15 18:23:45 +02:00
31fe3e7c22 feat(schema): enforce particle element type restriction and bypass prohibited attributes 2026-06-15 18:10:21 +02:00
b6575150e8 feat(schema): enforce attribute restriction type derivation (derivation-ok-restriction.1)
Require that any restricted attribute has a type validly derived by restriction from its base counterpart. Validates atomic built-in derivation, list item type restrictions, and union member/subset relationships recursively. Updates baseline to 362 accepted invalid schemas.
2026-06-15 17:46:15 +02:00
ef80934ff8 feat(schema): enforce top level names, restrict xs:anySimpleType, and validate simpleType facets
Reject top level declarations that omit a name attribute. Disallow derivation by restriction of xs:anySimpleType inside simpleType definitions. Require restriction facets length, minLength, maxLength, totalDigits, and fractionDigits to restrict or equal the base type facets. Updates the conformance baseline to 383 accepted invalid schemas.
2026-06-15 17:06:09 +02:00
f86a7c9ade feat(schema): enforce attribute fixed value constraint under restriction 2026-06-15 16:24:32 +02:00
b80e31d562 feat(schema): a restriction may not relax a required base attribute (cos-ct-derived-ok)
In a complexContent restriction, an attribute the restriction redeclares that the
base type marks use="required" may not be made optional or prohibited. Only
attributes explicitly redeclared that also exist in the base are checked; an
omitted attribute is inherited unchanged. The base reference must resolve to the
schema's own target namespace, and the check stands down for a schema that
composes other documents.

The matching fixed-value clause is deliberately not enforced: it requires
comparing the two fixed values in the attribute type's value space, not lexically
(a list-typed fixed value differing only in whitespace is the same value), so a
string comparison wrongly rejects valid schemas. That clause is left for a later
change (a disclosed under-rejection).

XSTS: invalid schemas accepted 394 to 390, no other bucket raised.
2026-06-15 16:09:02 +02:00
282ceb57fc feat(schema): a nested simpleType or attributeGroup may not be named (localSimpleType)
A simpleType nested inside a restriction/list/union/element/attribute is the
anonymous localSimpleType (no name), and a nested attributeGroup is always a
reference; named simpleType and attributeGroup definitions are top-level only (a
direct child of schema or redefine). A nested definition declaring a name was
accepted; it is now rejected. A nested anonymous simpleType, an attributeGroup
reference, and a top-level named definition are unaffected.

XSTS: invalid schemas accepted 403 to 394, no other bucket raised.
2026-06-15 15:41:48 +02:00
c4679acfd8 feat(schema): reject an all group extending a base with content (cos-all-limited.1.2)
An xs:all group in a complexContent extension of a base that already has element
content was accepted. The effective content model of such an extension is
sequence(base content, extension content), which nests the all and violates the
rule that an all group must be the whole content model. The base type is read
from the compiled model: rejected only when the base resolves, in the schema's
own target namespace, to a complex type with element-only or mixed content. An
explicitly-empty base (an empty model group with mixed=false) has effectively
empty content and may still be extended by an all (the all becomes the whole
content); a simple-content, empty, foreign, or unresolved base is left alone.
Checked for a self-contained schema.

XSTS: invalid schemas accepted 411 to 403, no other bucket raised.
2026-06-15 15:31:01 +02:00
ca1a7bdf3f feat(schema): enforce simpleType final for list and union derivation (st-props-correct)
A simple type whose final contains list (or #all) may not be a list itemType,
and one whose final contains union (or #all) may not be a union memberType.
finalRespected already covered restriction and extension; the list and union
directions were unchecked because the derivation-method set does not model
final="list"/"union", so the final attribute is read directly. The itemType /
memberTypes reference is matched only when it resolves to the schema's own target
namespace, so a list or union of a built-in (whose final is empty) is not
confused with a user type of the same local name. Checked for a self-contained
schema.

XSTS: invalid schemas accepted 413 to 411, no other bucket raised.
2026-06-15 15:12:11 +02:00
fbcfe758a5 feat(schema): whiteSpace facet may only strengthen the base (Part 2 4.3.6)
A whiteSpace facet that relaxed its base type's effective whiteSpace was
accepted; XSD Part 2 4.3.6 permits only strengthening, ordered
preserve < replace < collapse. A base fixed to collapse admits only collapse,
and a replace base admits replace or collapse; relaxing it (e.g. replace over a
collapse base, or restricting xs:token to replace) is now rejected. The base's
effective whiteSpace is its own facet falling back to its built-in intrinsic;
the check is atomic-only and, when a base's own whiteSpace has not yet resolved,
can only under-detect (an unresolved base defaults to preserve, the weakest), so
it never rejects a valid restriction.

XSTS: invalid schemas accepted 422 to 413, no other bucket raised.
2026-06-15 14:54:47 +02:00
44d37a1a5f feat(schema): top-level element may not have ref/form/minOccurs/maxOccurs (topLevelElement)
A top-level xs:element declaration (a direct child of schema, or of redefine) was
accepted with ref, form, minOccurs, or maxOccurs. A global element is a
declaration, never a particle or a reference, so those local-use attributes are
prohibited by the schema-for-schemas topLevelElement. The top-level-attribute
applicability scan is generalized into topLevelDeclarationErrors, driven by a
per-kind forbidden-attribute map, covering both attribute and element. A local
element particle inside a content model is unaffected.

XSTS: invalid schemas accepted 424 to 422, no other bucket raised.
2026-06-15 14:39:54 +02:00
7ed7059134 feat(schema): element ref excludes declaration-only attributes and inline type (src-element.2.2)
An element ref carrying nillable/default/fixed/form/block, or an element or
attribute ref carrying an inline type definition, was accepted. A ref is a use
of a global declaration, not a declaration, so beyond ref it admits only
minOccurs, maxOccurs, and id (src-element.2.2). The applicability check already
excluded name and type from a ref; this extends the exclusion to the remaining
declaration-only properties and to an inline simpleType/complexType child.
minOccurs/maxOccurs, a group/attributeGroup ref, and an attribute ref's
use/default/fixed (which override on the use) are unaffected.

XSTS: invalid schemas accepted 439 to 424, no other bucket raised. Disclosed
under-detection: a key/unique/keyref child under a ref is not yet flagged.
2026-06-15 14:29:13 +02:00
be5d260b52 feat(schema): top-level attribute may not have use/form/ref (topLevelAttribute)
A top-level xs:attribute declaration (a direct child of schema, or of redefine)
was accepted with use, form, or ref. Those belong to a local attribute use, not
a global declaration: the schema-for-schemas topLevelAttribute marks ref, form,
and use as prohibited, allowing only default, fixed, id, name, type and an inline
simpleType. The per-component applicability table is the permissive global/local
union, so it could not draw the distinction; a focused scan of the direct
attribute children of schema and redefine supplies the global-only constraint. A
local attribute use inside a complex type or attribute group is unaffected.

The existing test_validAttributeValues asserted a top-level attribute with use
compiles, which is itself invalid; corrected to a local attribute use.

XSTS: invalid schemas accepted 454 to 439, no other bucket raised.
2026-06-15 14:18:25 +02:00
aa6e76da88 feat(schema): validate value constraints against user types; fix inline restriction base
A default/fixed value on an element or attribute whose type names a user-declared
type in the schema's own target namespace must be a valid value of that type
(XSD 1.0 e-props-correct.2 / a-props-correct.2). The check runs after the named
types are compiled and validates with the compiled SimpleType validator (the one
instance validation uses), so whitespace and facet handling match. Built-ins are
handled at structure time; imported, foreign, and inline anonymous types are left
alone via a namespace gate.

This exposed a pre-existing compile defect: a simpleType restriction read its base
only from the base attribute, so an inline anonymous <simpleType> base (a list or
union with no base attribute) was dropped and the type collapsed to an atomic
string, making a length facet count characters instead of list items. The
restriction branch now resolves the inline child as its base, preserving list and
union variety. This corrects both schema-time value checking and instance
validation.

XSTS: invalid schemas accepted 462 to 454; invalid instances accepted 160 to 158;
no other bucket raised.
2026-06-15 14:06:26 +02:00
a087c022e9 feat(schema): attribute default requires use=optional (src-attribute.2)
An xs:attribute declaration carrying a default value constraint was accepted
alongside use="required" or use="prohibited"; XSD 1.0 src-attribute.2 requires
use to be optional when default is present (a default makes the attribute
optional). The check rides the existing value-constraint walk, gated to
XSD-namespace attribute declarations reading the unprefixed default/use, and is
per declaration. Only fixed (not default) may pair with use="required".

XSTS: invalid schemas accepted 469 to 462, no other bucket raised. Disclosed
under-rejection: a whitespace-padded use=" required " (NMTOKEN actual value
"required") is not flagged.
2026-06-15 13:44:21 +02:00
de82800525 feat(schema): reject both default and fixed on one declaration (src-element.1/src-attribute.1)
An xs:element or xs:attribute declaration carrying both a default and a fixed
value constraint was accepted; XSD 1.0 src-element.1 / src-attribute.1 make the
two mutually exclusive. The check rides the existing value-constraint structure
walk, gated to XSD-namespace declarations and reading the unprefixed
default/fixed (a foreign same-local-name attribute is not the value
constraint). It is per declaration, so a default on a global declaration and a
fixed on a reference to it (two separate items) remains valid.

XSTS: invalid schemas accepted 479 to 469, no other bucket raised.
2026-06-15 13:25:14 +02:00
d2f18a2685 feat(schema): substitution member type must derive from head (e-props-correct.4)
An element declaring substitutionGroup="head" whose own type is unrelated to
the head's type was accepted; XSD 1.0 e-props-correct.4 requires the member's
type be validly derived from the head's. The check runs after named types
resolve and reuses the restriction/extension + built-in-lattice derivation
oracle (an ur-type head admits any member).

It is conservative so it never rejects a valid member: it runs only for a
self-contained schema (no import/include/redefine, where the derivation table
is complete and single-namespace names are unambiguous), reads the member's
type from its own type attribute and the head's from the top-level element
declarations only (so a local element of the same name supplies neither), and
stands down when either type is a list or union (cos-st-derived-ok clauses
2.3/2.4 union-member derivation is not modelled).

The post-namedTypes consistency calls move into a postNamedTypeErrors
aggregator alongside consistencyErrors. XSTS: invalid schemas accepted
480 to 479, no other bucket raised.
2026-06-15 13:08:38 +02:00
ad110b101c fix(schema): reject an all-group referenced inside a compositor (cos-all-limited)
An xs:all group may only be the whole content model of a complex type. A direct
all nested in a sequence/choice was already rejected; this closes the case where
an all is reached through a <group ref> to a named group whose content is an all
and that reference appears inside a compositor.

The all-group pool is qualified by the defining schema's target namespace, and a
reference by its prefix binding, so an imported or redefined same-named group is
not confused with a local one (two over-rejections found by an adversarial
critic: a redefined all-group whose content is overridden, and an imported
all-group shadowing a local non-all group; both fixed and tested). A group
redefined to non-all content is exempt.

XSTS: invalid schemas accepted 481 to 480, no other bucket raised. Disclosed
under-rejections: a redefine that keeps the group an all, and cross-namespace
misplacement of an imported all-group.
2026-06-15 12:38:30 +02:00
4996d75879 fix(schema): an ID-derived attribute or element may not have a value constraint
An attribute or element whose type is or derives from xs:ID may not carry a
default or fixed value (a-props-correct.3 / e-props-correct.5). Such schemas
were accepted.

The check runs after named simple types are resolved, so a named user type
derived from xs:ID is recognized (not just the built-in and inline forms). The
type reference's namespace is resolved from its prefix: only an ID in the XSD
namespace is the built-in, and a user type is ID only when it resolves to one
whose base is xs:ID. An adversarial critic caught a namespace-blind over-
rejection (a user type named ID, or an imported foo:ID, mistaken for the
built-in); the namespace resolution fixes it, with a regression test.

XSTS: invalid schemas accepted 489 to 481 (attZ013a/b, elemZ032a/b,
valueconstraint01001m2/m3/m5/m6 now rejected), no other bucket raised.
2026-06-15 11:41:26 +02:00
0142ecb35e fix(schema): a type attribute and an inline anonymous type are mutually exclusive
An <xs:element> or <xs:attribute> may not carry both a type attribute and an
inline anonymous type definition (src-element.3 / src-attribute.3.1): an
attribute may have only an inline simpleType, an element a simpleType or
complexType. Such schemas were accepted.

Only an inline type in the XSD namespace counts; a foreign-namespace child whose
local name happens to be complexType/simpleType is not the component's own type
definition (foreign content is not structurally checked), so it is not flagged
(an adversarial critic's finding, fixed with the namespace guard and a test).

The attribute-applicability function and its allowed-attributes table were
extracted to SchemaAttributeApplicability.swift (a pure move) to keep
SchemaStructure.swift within the file-length limit.

XSTS: invalid schemas accepted 495 to 489 (addB081, attKa011, attKb014,
attKc014, typedef00501m2, typedef00502m2 now rejected), no other bucket raised.
2026-06-15 11:02:48 +02:00
067f04184f fix(schema): enforce attribute-use uniqueness and the single-ID rule
A complex type's or attribute group's attribute-use set (flattened through
attributeGroup references) may not contain two distinct declarations with the
same name and namespace (ct-props-correct.4 / ag-props-correct.2), nor more than
one attribute whose type is or derives from xs:ID (ct-props-correct.5).

Declarations are deduplicated by the identity of their declaring node, so the
same declaration reached by two attribute-group paths (a diamond) is one use,
not a clash. Names carry their resolved namespace (form / attributeFormDefault
for locals, the prefix binding for refs), so a local attribute and a reference
to a same-local-name attribute in an imported namespace do not collide. The
check runs after named simple types are resolved, so a named user type derived
from xs:ID is recognized, not just the built-in and inline forms.

XSTS: invalid schemas accepted 505 to 495 (attQ008/009/012/013, attgD006/007/
009/017, ctM003/004 now rejected), no other bucket raised. An adversarial critic
caught a false positive on same-local-name-different-namespace (fixed by
resolving the ref namespace) and a missed named-user-ID type (fixed by ordering
the check after type resolution). Disclosed under-rejections: the set inherited
through extension is not composed in; a duplicate needing an unprefixed
reference with no default namespace is not detected.
2026-06-15 10:29:40 +02:00
7c059e1355 fix(schema): reject circular model-group, attribute-group, and substitution-group references
A model group may not contain itself (mg-props-correct.2), an attribute group
may not reference itself transitively (ag-props-correct.3), and a substitution
chain may not loop (e-props-correct.6).

The model-group check follows references only within a definition's own
compositor tree, stopping at element and type boundaries: a group referenced
inside an element's content is a recursive data structure (permitted), not the
group containing itself (particlesZ010, addB077). A reference is followed only
when it resolves to the schema's own target namespace; a group or attribute
group redefined in xs:redefine ends the chain (its single self-reference is the
legal redefinition).

XSTS: invalid schemas accepted 516 to 505 (attgC010/020/031, attgD015,
groupB012-015, addB180, xsd009.e, xsd010.e now rejected), no other bucket
raised. A genuine cycle introduced across two redefined definitions is a
disclosed under-rejection (an adversarial critic's only finding).
2026-06-15 01:50:35 +02:00
96ec9cd1e0 fix(schema): reject circular type derivation (ct-props-correct.3 / st-props-correct.2)
A complex or simple type may not derive, transitively through its base, from
itself. Such schemas were accepted. The check walks the base chain built from
each type's complexContent/simpleContent extension/restriction base (and a
simpleType's restriction base), following an edge only when the base resolves to
the schema's own target namespace.

Three exceptions keep it from over-rejecting: element content that recurses (an
element of the type's own type) is not a derivation and is not followed; a type
redefined in xs:redefine legitimately names itself as its base, so redefined
types end the chain; and the ur-types (anyType/anySimpleType/anyAtomicType,
which the schema-for-schemas bootstraps with self-referential definitions) end
the chain. The target-namespace gate prevents a local type whose base is a
same-local-name type in another namespace from being mistaken for a self-cycle
(found by an adversarial critic; the engine resolves by local name).

XSTS: invalid schemas accepted 519 to 516 (addB101, elemM003, xsd007.e now
rejected), no other bucket raised. Union/list and genuine cross-namespace
derivation cycles are a disclosed under-rejection.
2026-06-15 01:26:37 +02:00
119894a5a2 fix(schema): implement MapAndSum (sequence restricting a choice) in particle restriction
The W3C "Particle Valid (Restriction)" MapAndSum rule (XSD 1.0 §3.9.6): a derived
sequence restricting a base choice is valid iff each derived particle restricts
some base branch AND the derived sequence's occurrence count times its number of
particles lies within the base choice's occurrence range.

PureXML applied the same-compositor occurrence subsumption (the sequence's own
range against the choice's), which both rejected a valid restriction
(sequence{2,4}(e1,e2) of choice{3,9}(e1,e2): 4..8 elements fit 3..9) and accepted
an invalid one (a two-member sequence restricting a single-occurrence choice,
count-product 2 > 1). The count-product is the spec's measure, matched to the
reference implementation (checkMapAndSum/checkOccurrenceRange).

The (sequence, choice) branch in groupValid is retained: it is reached by the
(element, group) RecurseAsIfGroup path, which wraps an element as a one-particle
sequence against a base choice.

XSTS: valid schemas rejected 2 to 1, invalid schemas accepted 520 to 519, no
other bucket raised. The remaining valid-rejected case (particlesZ001) is a
documented spec ambiguity (RecurseAsIfGroup), recorded in docs/xsts-deviations.md.
2026-06-14 23:46:37 +02:00
9a1bb652a7 fix(schema): content-free restriction of emptiable base, substitution-group declaration order
Two more Particle Valid (Restriction) over-rejections.

A derived particle that can never contribute a child (its own maxOccurs="0", or
a group whose every member is content-free) accepts only the empty sequence, so
it validly restricts any emptiable base. Checked before the structural cases,
which previously recursed into the members of a maxOccurs="0" group and rejected
it for their own occurrences (particlesW006).

Substitution-group expansion now lists members in declaration order: the closure
walk used a popLast() stack that reversed them, so a reference to a head expanded
to choice(head, m2, m1) and an order-preserving RecurseLax restriction by
choice(m1, m2) could not map (elemZ027a). The choice/choice restriction stays
order-preserving, as the W3C suite requires (choice(b|a) is not a valid
restriction of choice(a|b), particlesT002); only the expansion order is fixed.
The expansion is shared with instance validation and the determinism check, both
order-neutral.

XSTS: valid schemas rejected 4 to 2, no other bucket raised. The pinned XSTS
baselines (never asserted under a plain swift test, the suite being opt-in) are
corrected to the measured 2/520/180/160.
2026-06-14 22:48:12 +02:00
828ed195f0 fix(schema): resolve element-ref namespaces by prefix binding, not target namespace
An `<xs:element ref="p:foo">` had its prefix stripped and the element assumed to
be in the schema's target namespace. That mis-qualified every reference to an
imported element: a restriction of an `##other` wildcard by such a reference was
rejected (the element looked like the target namespace, which `##other` forbids),
and the same mis-qualification rejected valid instances whose elements were
correctly in the imported namespace.

The reference's QName is now resolved by standard XML namespace rules against the
schema root's xmlns bindings: a bound prefix gives its namespace (an imported
element lands in the imported namespace), an unprefixed name the default
namespace, or no namespace when none is declared. Substitution-group members
stay in the target namespace (this schema's own globals).

The content model is shared between schema-restriction checks and instance
validation, so both improve. XSTS: valid schemas rejected 32 to 4, invalid
schemas accepted 526 to 520, valid instances rejected 214 to 180, invalid
instances accepted held at 160.

Disclosed limitations: a prefix re-bound on an inner element is not honored,
cross-namespace substitution-group members are not modeled, and a dangling
reference is not yet flagged.
2026-06-14 17:50:26 +02:00
aba91a168f fix(schema): normalize content model, scope occurrence subsumption in particle restriction
Particle Valid (Restriction) derives its pairwise check over the spec's
normalized content model: pointless particles are eliminated (maxOccurs=0
members dropped, a {1,1} single-member group unwrapped, a nested
same-compositor {1,1} group spliced), all language-preserving. The outer
occurrence-range check was wrongly applied to the cross-kind pairings, where
it compares a derived particle's own range against a base group's or
wildcard's. It is now applied only to the same-kind pairings (element/element,
wildcard/wildcard, group/group) and element/wildcard; element/group
(RecurseAsIfGroup), group/element, and group/wildcard match occurrence inside
the case, against a member or as an effective total range.

A group restricting a wildcard (NSRecurseCheckCardinality) is valid when every
leaf is admitted and the effective total occurrence range is within the
wildcard's; a group restricting a single element is bounded the same way, by
that element's name, type derivation, and occurrence range.

XSTS: valid schemas rejected 45 to 32, invalid schemas accepted 528 to 526,
no other bucket raised.
2026-06-14 17:18:38 +02:00
87cc83d0ce fix(schema): stop Particle Valid (Restriction) over-rejecting valid schemas
The restriction subset check rejected 72 valid schemas (#148). Three
language-preserving corrections:

- Remove pointless particles (maxOccurs=0) from both the derived and base
  content before mapping, so they no longer consume a base particle and starve
  the particles after them (e.g. (e1 maxOccurs=0, e2) restricting (any)).
- A content-free restriction (an empty sequence, or a group whose members all
  cannot occur) is a valid restriction of an EMPTY base: both accept only the
  empty sequence.
- A derived group that reduces to empty after that removal validly restricts its
  base only when the base is itself emptiable (emptiable(base) accounts for the
  base's own occurrence), closing the vacuous-acceptance gap the removal would
  otherwise open in the MapAndSum and choice paths.

XSTS: valid schemas rejected 72 to 45. invalid schemas accepted 527 to 528 from
addB32, whose only invalidity is a malformed imported document not yet loaded
(#161); the restriction itself is valid, so accepting it is correct.
2026-06-14 15:56:04 +02:00
dd10f32abb feat(schema): a simpleType must have exactly one of restriction/list/union
Per XSD 1.0 Datatypes a simpleType's content is
(annotation?, (restriction | list | union)). The membership table admitted each
name without the cardinality, so an empty simpleType or one combining two
derivations (two unions, a union and a list, two restrictions) was accepted.
The exactly-one rule is now enforced, mirroring the named-group compositor check.

XSTS: invalid schemas accepted 537 to 527, valid schemas rejected held at 72,
instance buckets unchanged.
2026-06-14 14:30:25 +02:00
11c4eacdff feat(schema): validate final/block derivation-control value space
The final and block attributes are #all or a whitespace list of method tokens,
but the admitted tokens depend on the component: element final is
extension/restriction, element block adds substitution, complexType final/block
are extension/restriction, simpleType final is list/union/restriction. The
tokens were parsed leniently (unknown ignored, substitution admitted
everywhere), so final="substitution" on an element, a misspelled or miscased
token, or #all mixed with other tokens was accepted.

The list is validated against the component's exact set; finalDefault/
blockDefault (the wider schema-level value space) and foreign attributes are
left alone.

XSTS: invalid schemas accepted 580 to 537, valid schemas rejected held at 72,
instance buckets unchanged.
2026-06-14 14:19:10 +02:00
6a7fb1d594 feat(schema): enforce simpleContent derivation content order
A simpleContent restriction is (annotation?, (simpleType?, facet*),
(attribute|attributeGroup)*, anyAttribute?) and an extension is
(annotation?, (attribute|attributeGroup)*, anyAttribute?): no model group, no
facets/simpleType in an extension, attributes before a single anyAttribute. The
membership table admitted the names but not the order or the model-group ban, so
a model group inside simpleContent, a facet in an extension, a misordered facet
or attribute, or a second anyAttribute (the ctD family) was accepted.

The derivation's children are matched against the context's ordered slot model,
reusing the slot walk built for complexContent. The structure walk's
per-component dispatch is factored into a helper to keep its complexity bounded.

XSTS: invalid schemas accepted 594 to 580, valid schemas rejected held at 72,
instance buckets unchanged.
2026-06-14 14:05:49 +02:00
68273205f6 feat(schema): full NameAndTypeOK type derivation in element restriction
Complete the element-vs-element type clause of Particle Valid (Restriction):
the restricting element's type must be, or derive by restriction from, the base
element's type. Previously only names were compared, so an unrelated, wider, or
ur-type element type was accepted under restriction.

Enrich the content-model particle: Term.element carries the element type's
derivation identity (the resolved type name, anyType for an absent type, nil for
an inline anonymous type). Record named simpleType restriction derivations in the
derivation table alongside complex ones. The element-vs-element check walks the
user restriction chain and then the built-in lattice to decide derivation by
name; an ur-type base, a union base (membership is not a restriction chain), and
an inline anonymous type are permitted, so no valid restriction is rejected.

XSTS: invalid schemas accepted 616 to 594, invalid instances accepted 165 to 160
(more invalid schemas rejected up front), valid schemas rejected held at 72,
valid instances rejected held at 214.
2026-06-14 13:47:18 +02:00
711a50b8ea feat(schema): reject ur-type widening in element restriction
Extend NameAndTypeOK: restricting a concrete-typed element to an ur-type
(xs:anyType / xs:anySimpleType / xs:anyAtomicType) is widening, hence an invalid
restriction. The element-vs-element check now rejects it before the atomic
derivation check.

The ur-type is recognised by a type reference whose key is anyType /
anySimpleType / anyAtomicType AND which is absent from the named-type table, so
a user type that merely shares the local name (in another namespace) is not
mistaken for the ur-type. The reverse (an ur-type base narrowed by the
restriction) stays valid.

XSTS: invalid schemas accepted 620 to 616, valid schemas rejected held at 72,
instance buckets unchanged.
2026-06-14 13:07:31 +02:00
7a80410945 feat(schema): check element type in Particle Valid (Restriction)
When a restriction maps a same-named element onto a base element, the
restricting type must derive from the base's (NameAndTypeOK). Renaming an
xs:string element to xs:int under restriction was accepted because the
element-vs-element check compared only names.

ParticleRestriction now threads the compiled type table through its recursion;
the element-vs-element case resolves both element types (following a type
reference through the table) and, when both are atomic simple types, requires
the restricting type's built-in base to derive from the base's. A complex,
list, union, untyped, or unresolvable type is left alone, so no valid
restriction is rejected.

XSTS: invalid schemas accepted 621 to 620, valid schemas rejected held at 72,
instance buckets unchanged.
2026-06-14 12:29:23 +02:00
3e3b937e89 feat(schema): validate built-in-typed default/fixed values
An element/attribute default/fixed value must be a valid value of its type
(Attribute/Element Locally Valid, XSD 1.0). A fixed="abc" on an xs:integer or a
default="1 2" on an xs:int was accepted because the value constraint was never
checked at compile time.

The value is now validated against its type when the type resolves to a
built-in datatype (namespace-aware: only a type in the XSD namespace),
normalising whitespace per the type so a default=" 5 " on an integer stays
valid. A named or inline user type is left to its own rules, so no valid value
constraint is rejected.

XSTS: invalid schemas accepted 638 to 621, valid schemas rejected held at 72,
instance buckets unchanged.
2026-06-14 11:55:55 +02:00
0143381f67 feat(schema): enforce facet applicability to simple-type variety
A constraining facet must apply to its simple type's variety (XSD 1.0 Part 2):
a list may be constrained only by length/minLength/maxLength, pattern,
enumeration, whiteSpace; a union only by pattern and enumeration. A value-bound
facet (maxInclusive, totalDigits, ...) constrains an atomic value space, so
restricting a list of integer by maxInclusive was wrongly accepted.

The base's variety is resolved from the raw tree (this runs before the types
are compiled), namespace-aware so a user type sharing a built-in's local name
does not flip the variety: a base in the XSD namespace is a built-in (only the
list datatypes are list-variety), a base in the schema's own target namespace
resolves to a declared global, and an external or unresolvable base is skipped.

XSTS: invalid schemas accepted 664 to 638, valid schemas rejected held at 72,
instance buckets unchanged.
2026-06-14 11:36:21 +02:00
9d4ce304c2 feat(schema): enforce complexType shorthand content order
A complexType's shorthand content (no simpleContent/complexContent) has the
model (annotation?, (group|all|choice|sequence)?, (attribute|attributeGroup)*,
anyAttribute?). The existing content-shape check caught the wrong number of
model groups and content-spec exclusivity, but not the order, so a model group
after an attribute, an attribute after anyAttribute, or a second anyAttribute
(the ctB family) was accepted.

The complexType's direct children, excluding annotation and the content specs,
are matched against the same ordered slot model used for a complexContent
derivation.

XSTS: invalid schemas accepted 687 to 664, valid schemas rejected held at 72,
instance buckets unchanged.
2026-06-14 11:15:24 +02:00
e016775a82 feat(schema): validate wildcard namespace constraint value space
An any/anyAttribute namespace constraint is ('##any' | '##other') standing
alone, or a list of namespace URIs and the ##targetNamespace/##local tokens.
The wildcard parser mapped any unrecognised token to a literal URI, so a
misspelled token (##all, ##target), an ##any/##other inside a list, or any
other ##-prefixed token was accepted.

The unprefixed namespace value is now tokenised and checked against that
grammar, with ##any/##other admitted only as the whole value. A prefixed
(foreign) attribute sharing the local name is the author's own and is not
checked, so no valid schema is rejected.

XSTS: invalid schemas accepted 736 to 687, valid schemas rejected held at 72,
instance buckets unchanged.
2026-06-14 11:00:45 +02:00
cfaf9b7dd1 feat(schema): validate identity-constraint selector/field XPath subset
An identity constraint's selector and field xpath must be in the XSD 1.0
restricted-XPath subset (Part 1, 3.11.6), far narrower than full XPath: a
leading optional .//, then /-separated steps, a step being '.', a name test
(QName, '*', or NCName:*), or a child:: axis; a field path may end in an
attribute step (@x or attribute::x). No predicates, no other axes, no absolute
or interior //. The xpath was stored raw and compiled lazily with try?, so a
malformed one was silently accepted.

It is now lexed into a whitespace-tolerant token stream and parsed against the
grammar: whitespace between tokens is allowed (child :: a, . //.) while // must
be adjacent and a name test's own ':' may not be spaced. The name lexer uses
the XML NameChar set so a non-ASCII NCName is accepted.

The two identity tests that fed a malformed xpath and expected a
validation-time located error now assert the stronger compile-time rejection.

XSTS: invalid schemas accepted 785 to 736, valid schemas rejected held at 72,
instance buckets unchanged.
2026-06-14 10:30:06 +02:00
8a7445c5bb feat(schema): enforce complexContent derivation content order
A restriction or extension inside complexContent has the content model
(annotation?, (group|all|choice|sequence)?, (attribute|attributeGroup)*,
anyAttribute?). The allowedChildren membership table admitted the right child
names but not their order or cardinality, so a second model group, a model
group after an attribute, an attribute after anyAttribute, or a second
anyAttribute was accepted.

A forward-only walk matches the children against an ordered slot model (one
model group, unbounded attributes, one anyAttribute). The check runs only in
the complexContent context, where a model group belongs to the model, so a
simpleContent derivation (facets, no model group) keeps its own rules and no
valid schema is rejected.

XSTS: invalid schemas accepted 864 to 785, valid schemas rejected held at 72,
instance buckets unchanged.
2026-06-14 09:56:04 +02:00
90ae16cba1 feat(schema): enforce Unique Particle Attribution (cos-nonambig)
Reject ambiguous content models at compile time via a Glushkov position
automaton over a literal, QName-resolved view of the raw schema tree, built
just for this rule and independent of the instance-validation content model.

The literal view resolves a ref's QName through the schema's prefix bindings
(not the blanket target namespace) and does not expand substitution groups,
removing the two false-overlap sources a check over the compiled model
produced. Determinism is per particle: a particle competing with a repetition
of itself is never a violation, so a fixed count {n,n} unrolls into copies in
sequence while a true range or unbounded repeat uses a self-loop, and only two
distinct source particles with overlapping labels conflict. The overlap test
is QName-only, an under-rejection that can never over-reject.

A position cap bounds pathological nesting (skipping the check, never
over-rejecting). The element-declarations-consistent test loses its
incidentally-ambiguous (a?, a) case in favor of a deterministic (a, b, a).

XSTS: invalid schemas accepted 884 to 864, valid schemas rejected held at 72,
instance buckets unchanged.
2026-06-14 09:35:33 +02:00
42a87b132c feat(schema): resolve keyref refer to a declared key or unique
A keyref's refer must name a key or unique constraint. A refer pointing at a
non-existent constraint was accepted. keyrefReferErrors collects all key/unique
names and all keyref refers across the document (so a forward refer, textually
before its key, is handled), then flags any refer with no matching name. Matched
by local name, like the rest of the compiler. Skipped when the document declares
import/include/redefine, whose externals the default compile does not load.

XSTS: invalid schemas accepted 891 to 884 (-7); valid schemas rejected held at
72; instance buckets unchanged.
2026-06-14 08:04:34 +02:00
5ebf3b861c feat(schema): enforce component-name uniqueness
Within a schema, global type names (simpleType and complexType share one symbol
space), global element names, global attribute names, named model-group names,
and named attribute-group names must each be unique, and identity-constraint
names must be unique across the document. A duplicate was accepted, with the
later definition silently overwriting the earlier one.

Only the document's own globals are examined (xs:redefine children and unloaded
included documents are not direct globals), so a redefinition is not mistaken
for a clash; the symbol spaces are kept separate, so a type and an element may
share a name.

XSTS: invalid schemas accepted 909 to 891 (-18); valid schemas rejected held at
72; instance buckets unchanged.
2026-06-14 02:28:29 +02:00