Module: Ksef::UPO::Validator
- Defined in:
- lib/ksef/upo/validator.rb
Overview
Offline XSD validation for a received UPO (docs/REFERENCE.md §12, §14.3).
Simpler than FA3::Validator and Auth::Validator in one respect: the
pinned upo-v4-3.xsd imports nothing and declares no remote schemaLocation, so it
compiles offline as shipped with no in-memory rewriting.
This is a diagnostic. It is never a gate.
There is deliberately no validate! here, unlike the other two validators, and the
omission is the design. A UPO is the legal proof that an invoice was received; whether
it satisfies a schema is interesting, but it is never a reason to discard the bytes or
to fail an operation that has already succeeded at the far end. Offering a raising
method would make gating the path of least resistance. A caller who genuinely wants to
raise can write raise unless result.valid? and own that decision explicitly.
Why the receiving-party mismatch is a warning
Measured on the pinned artifacts: all six of upstream's worked examples fail upstream's
own schema, each with exactly one error, and always the same one — the schema fixes
RECEIVING_PARTY_ELEMENT to "Ministerstwo Finansów" while the examples, all
captured on TEST, carry "Ministerstwo Finansów - środowisko testowe (TE)".
§14.3 permits either relaxing the constraint or reporting the mismatch as a warning.
This takes the second, because it keeps strictly more information: in production the
fixed value is presumably correct, so a mismatch there would be a genuine anomaly —
and relaxing the schema would mean never hearing about it. The observed value is read
from the document rather than scraped out of the error message, so it is reliable.
Why the signature is removed before validating
A real UPO is XAdES-signed by the Ministry (§12) — that is the entire point of the
document. upo-v4-3.xsd declares no ds:Signature element anywhere, so a genuine UPO
fails it with Element '{http://www.w3.org/2000/09/xmldsig#}Signature': This element is not expected, and validating one straight off the wire reports an error on every single
UPO KSeF has ever issued.
None of upstream's six worked examples reveals this, because all six are unsigned —
measured on the pinned fixtures, zero Signature elements between them. So the defect
was invisible offline and surfaced on the first live nightly, 2026-08-24 (§14.7).
The signature is stripped from a copy before the schema runs. That is not leniency: an enveloped signature is a wrapper around the business document, and the schema describes the business document. Removing it is what makes the remaining errors mean something — and every other violation, including §14.3's, still surfaces exactly as before. The caller's bytes are untouched, which §12 requires.
Constant Summary collapse
- SOURCE =
File.("schema/upo-v4-3.xsd", __dir__)
- SIGNATURE_NAMESPACE =
W3C XML Signature. The Ministry's enveloped signature lives in this namespace.
"http://www.w3.org/2000/09/xmldsig#"- FIXED_VALUE_COMPLAINT =
libxml2's wording for a
fixedviolation. Matched on the element name as well, so a fixed-value complaint about any other element stays an error. /#{Regexp.escape(RECEIVING_PARTY_ELEMENT)}.*fixed value constraint/m
Class Method Summary collapse
-
.receiving_party(xml) ⇒ String?
The receiving party as the document actually states it — which doubles as a way to tell which environment issued a UPO, since only production is expected to carry the bare
"Ministerstwo Finansów". -
.schema ⇒ Nokogiri::XML::Schema
Memoised; compiling is not free and a client archiving many UPOs should pay once.
-
.signed?(xml) ⇒ Boolean
Whether the document carries the Ministry's enveloped signature.
-
.valid?(xml) ⇒ Boolean
True when the document has no violation beyond the known upstream environment-marker defect.
- .validate(xml) ⇒ Validation
Class Method Details
.receiving_party(xml) ⇒ String?
The receiving party as the document actually states it — which doubles as a way to
tell which environment issued a UPO, since only production is expected to carry the
bare "Ministerstwo Finansów".
94 95 96 97 |
# File 'lib/ksef/upo/validator.rb', line 94 def receiving_party(xml) document = parse(xml) document.at_xpath("//upo:#{RECEIVING_PARTY_ELEMENT}", "upo" => NAMESPACE)&.text end |
.schema ⇒ Nokogiri::XML::Schema
Returns memoised; compiling is not free and a client archiving many UPOs should pay once.
65 |
# File 'lib/ksef/upo/validator.rb', line 65 def schema = @schema ||= Nokogiri::XML::Schema(File.read(SOURCE, encoding: "UTF-8")) |
.signed?(xml) ⇒ Boolean
Returns whether the document carries the Ministry's enveloped signature. A UPO fetched from KSeF does; upstream's published examples do not.
83 |
# File 'lib/ksef/upo/validator.rb', line 83 def signed?(xml) = !signatures(parse(xml)).empty? |
.valid?(xml) ⇒ Boolean
Returns true when the document has no violation beyond the known upstream environment-marker defect.
87 |
# File 'lib/ksef/upo/validator.rb', line 87 def valid?(xml) = validate(xml).valid? |
.validate(xml) ⇒ Validation
69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/ksef/upo/validator.rb', line 69 def validate(xml) document = parse(xml) = schema.validate(without_signature(document)).map(&:message) expected, real = .partition { || FIXED_VALUE_COMPLAINT.match?() } Validation.new( errors: real.freeze, warnings: expected.freeze, receiving_party: receiving_party(document) ) end |