From 0af92bcf8de6dbe7e2a99813f0d83e75ac32bd93 Mon Sep 17 00:00:00 2001 From: Mihaela Mihaljevic Date: Sat, 27 Jun 2026 23:45:40 +0200 Subject: [PATCH] fix(schema): apply cos-st-derived-ok to NameAndTypeOK over a union base (#359) A particle restriction whose restricting element has a union-typed base element was checked by structural member subsetting: PureXML accepted a separately declared union whenever its members were a subset of the base union's. That is not the spec's derivation relation. The NameAndTypeOK type clause (XSD 1.0 section 3.9.6) requires the restricting element's type to be validly derived from the base element's type per Type Derivation OK (Simple), cos-st-derived-ok. The restricting type is now valid over a union base only if it is the base type, derives from it through a declared restriction chain, or (clause 2.2.4) validly derives from one of the union's member types. Scoped to the element-restriction path, so no attribute or simple-type derivation check moved. Catches particlesIk011 (a fresh union(integer) is not a restriction of union(integer, string)): XSTS invalid-schemas-accepted 9 to 8, valid-schemas- rejected held at 0 over the full corpus (the cardinal no-over-rejection check). Disclosed: particlesIk025/Ik027, where the restricting type derives from a union member, are accepted per cos-st-derived-ok 2.2.4, a contested reading their XSTS invalid expectation does not share. An in-repo test that asserted the old structural-subset behavior is corrected. --- CHANGELOG.md | 4 ++++ Sources/Schema/ParticleRestrictionElement.swift | 13 +++++++++++-- Tests/XSDParticleRestrictionTests.swift | 10 +++++++--- Tests/XSTSSuiteTests.swift | 8 +++++++- 4 files changed, 29 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de205ee..67a437f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- XSTS invalid-schemas-accepted (#145): a particle restriction whose restricting element has a union-typed base element now applies Type Derivation OK (Simple) (`cos-st-derived-ok`) rather than structural member subsetting. The NameAndTypeOK type clause (ยง3.9.6) requires the restricting element's type to be validly derived from the base element's type; PureXML accepted a *separately declared* union whenever its member types were a subset of the base union's members, which is not the spec's derivation relation. The restricting type is now valid over a union base only if it is the base type, derives from it through a declared restriction chain, or (clause 2.2.4) validly derives from one of the union's member types. Catches particlesIk011 (a fresh `union(integer)` is not a restriction of `union(integer, string)`): invalid-schemas-accepted 9 to 8, with valid-schemas-rejected held at 0 (verified over the full XSTS; the change is scoped to the element-restriction path, so no attribute or simple-type derivation check moved). Disclosed divergence: particlesIk025/Ik027, where the restricting type derives from a union *member*, are accepted per `cos-st-derived-ok` clause 2.2.4, a contested reading their XSTS `invalid` expectation does not share. + ## [0.4.8] - 2026-06-27 ### Removed diff --git a/Sources/Schema/ParticleRestrictionElement.swift b/Sources/Schema/ParticleRestrictionElement.swift index 2be6d89..8a96b39 100644 --- a/Sources/Schema/ParticleRestrictionElement.swift +++ b/Sources/Schema/ParticleRestrictionElement.swift @@ -41,8 +41,17 @@ extension PureXML.Schema.ParticleRestriction { if let baseResolved = resolveType(baseTypeName, types), let derivedResolved = resolveType(restrictedTypeName, types) { switch (derivedResolved, baseResolved) { case let (.simple(derivedSimple), .simple(baseSimple)): - if case .union = baseSimple.variety { - return PureXML.Schema.XSDParser.isSimpleTypeRestrictionOK(derived: derivedSimple, base: baseSimple) + if case let .union(baseMembers) = baseSimple.variety { + // cos-st-derived-ok with a union base: the restricting type + // validly derives iff it is the base type, derives from it + // through a declared restriction chain (typeDerivesByRestriction + // returns true for an equal name), or (clause 2.2.4) validly + // derives from one of the union's member types. A separately + // declared union whose members merely subset the base union's is + // NOT a derivation (particlesIk011); structural member subsetting + // is not the spec's derivation relation. + if typeDerivesByRestriction(restrictedTypeName, baseTypeName, derivation, types) { return true } + return baseMembers.contains { PureXML.Schema.XSDParser.isSimpleTypeRestrictionOK(derived: derivedSimple, base: $0) } } case (.complex, .simple): return false diff --git a/Tests/XSDParticleRestrictionTests.swift b/Tests/XSDParticleRestrictionTests.swift index 4001ae6..2776e0e 100644 --- a/Tests/XSDParticleRestrictionTests.swift +++ b/Tests/XSDParticleRestrictionTests.swift @@ -303,14 +303,18 @@ struct XSDParticleRestrictionTests { // a union member (xs:string) validly restricts the union-typed base element. #expect(doc("xs:string", base: "u", types: userTypes) != nil) - // a union restricting another union (subset members): valid. let unions = """ """ - #expect(doc("U1", base: "U2", types: unions) != nil) - // a union restricting another union but adding/having unmatching members: invalid. + // A separately-declared union whose members merely subset the base union's + // is NOT a valid derivation: cos-st-derived-ok requires the restricting + // type to be the base, derive from it through a declared restriction chain, + // or derive from one of its members. Structural member subsetting is not + // the spec's derivation relation (cf. XSTS particlesIk011). + #expect(doc("U1", base: "U2", types: unions) == nil) + // The reverse (U2 has a member U1 lacks) is invalid for the same reason. #expect(doc("U2", base: "U1", types: unions) == nil) // a list type restricting a union type: invalid. #expect(doc("L1", base: "U1", types: unions) == nil) diff --git a/Tests/XSTSSuiteTests.swift b/Tests/XSTSSuiteTests.swift index 2327b9a..c99f623 100644 --- a/Tests/XSTSSuiteTests.swift +++ b/Tests/XSTSSuiteTests.swift @@ -44,7 +44,13 @@ struct XSTSSuiteTests { /// a base `required` attribute may not become `optional`) now also runs over a /// `simpleContent` restriction, not only `complexContent` (particlesZ030_d, /// which libxml2 also rejects). - private let knownSchemaInvalidAccepted = 9 + /// 9 -> 8, NameAndTypeOK over a union base now applies cos-st-derived-ok: a + /// separately-declared union whose members merely subset the base union's is + /// not a valid derivation (particlesIk011); a genuine restriction of the union + /// or of one of its members still passes. (Ik025/Ik027, where the restricting + /// type derives from a union member, are accepted per cos-st-derived-ok 2.2.4, + /// a disclosed divergence from their contested XSTS `invalid` expectation.) + private let knownSchemaInvalidAccepted = 8 private let knownInstanceValidRejected = 0 /// Instance bucket (133 -> 22), per-step deltas in CHANGELOG.md: xsi:type must derive from the declared /// type; anyType cannot stand in for anySimpleType; an untyped substitutionGroup member inherits its head's