design: validate-on-serialise turns some bundles' metadata routes into 500s — escape hatch, or not?

Split out of #9 (closed), where it was raised alongside an unrelated performance bug and would have got lost.

The report

Porting cpt-sdmx-api from sdmxlib 0.48 to sdmxlib-rest 0.5.0 (sdmxlib 0.63), the 2021 CSIP census bundle carries content that sdmxlib 0.62's validate-on-serialise rejects:

  • MetadataStructure attributes with no ConceptIdentity
  • Metadataflows with no targets
  • a MetadataSet with version 1.0, which SDMX-JSON metadata rejects because it requires semver

So the /metadata/* routes and the JSON / 3.0 metadataflow and metadatastructure listings now return 500. #9 (closed) suggested two options: the producer emits valid content (the right fix, already being looked at), or a library switch — e.g. RestSettings(validate_on_serialise=...) passed through to sl.serialize(validate=...) — as a migration escape hatch.

Framing worth getting right

This is not a regression in this library or in sdmxlib. It is the same class of problem sdmxlib-rest's own 0.2.0 port found in its test fixture: components with no ConceptIdentity, a MetadataSet naming its MSD rather than its flow with no target, and a non-semantic version. 0.48 wrote that content without complaint; 0.62 stopped letting it through. What changed is that invalid content became visible, not that valid content stopped working.

The switch would be technically trivial — to_json already takes validate= — which is exactly why it is worth deciding on purpose rather than by default.

The case against a global switch

  1. It moves the failure rather than fixing it. Strict SDMX clients reject these documents too. Turning validation off swaps our 500 for a parse failure in the client, which is harder to diagnose and does not show up in our logs.
  2. It is too broad. The semver rule is SDMX 3.0 / JSON only; 1.0 is a valid version in 2.1 XML. A global switch turns validation off for every format, including the ones where this content is perfectly valid.
  3. Escape hatches tend to stay. A switch added for a migration is rarely removed, and keeps the producer defect invisible after it is no longer needed.

Options, roughly in the order I'd reach for them

  1. The producer emits valid content. The real fix; already in progress.
  2. Make the 500 say which artefact failed and which rule it broke. Today it is an opaque failure. Naming the artefact URN and the validation rule is cheap, catalog-agnostic, makes no content decision on anyone's behalf, and directly speeds up option 1. Worth doing regardless of the rest.
  3. Skip and log invalid artefacts in listings, so one bad MetadataSet does not take down a listing of fifty valid ones. Better than a global switch, but it silently drops data from a response, so it would want a header or annotation saying the listing is partial.
  4. A narrow switch, if one is truly needed: per-format rather than global, defaulting to validation on, and logging every artefact it lets through. Last resort.

Recommendation

Do 2 now, rely on 1, and revisit 3 or 4 only if the producer fix stalls. Not a global switch.

Open question for whoever picks this up

Which of the three failures are format-dependent? The semver one clearly is. If the missing ConceptIdentity and missing targets also only fail for some formats, the per-format switch in option 4 becomes more defensible — that should be measured, not assumed.