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.expand_path("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 fixed violation. 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

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".

Returns:

  • (String, nil)


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.

Returns:

  • (Nokogiri::XML::Schema) —

    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.

Returns:

  • (Boolean) —

    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.

Returns:

  • (Boolean) —

    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

Parameters:

  • xml (String, Nokogiri::XML::Document, Document)

Returns:



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)
  messages = schema.validate(without_signature(document)).map(&:message)
  expected, real = messages.partition { |message| FIXED_VALUE_COMPLAINT.match?(message) }

  Validation.new(
    errors: real.freeze,
    warnings: expected.freeze,
    receiving_party: receiving_party(document)
  )
end