Skip to content

XSD: pin accepted schema dependency sets and gate parser-contract drift #315

Description

@LouisDeconinck

Context

#286 now provides XSD-derived XML parser contracts and records the exact XSD bytes used in run provenance.

Recent work in 4ca1cc6 also resolves multi-file schema sets through:

xs:include
xs:import
xs:redefine

and records every resolved schema document as a hashed input artifact in the signed run manifest.

That gives excellent after-the-fact provenance.

The remaining gap is before-the-run enforcement.

Problem

A publisher can replace the bytes behind an unchanged path or URL:

https://registry.example/schema/company.xsd

while the Duckle pipeline itself remains unchanged.

The next run then derives a different parser contract.

Today the run manifest can tell us afterwards:

previous XSD hash != current XSD hash

but the production ingestion may already have parsed and published data using the new contract.

For recurring registry feeds, I would like a mode where changing the authoritative parser contract requires explicit acceptance.

Desired behavior

Treat the complete resolved XSD dependency set as one versioned parser contract.

Conceptually:

root XSD
    ↓
resolve include/import/redefine closure
    ↓
canonical dependency set
    ↓
hash/fingerprint
    ↓
compare with accepted parser contract

A useful policy could be:

xsd:
  uri: schemas/company.xsd
  changePolicy: fail

with possible values:

allow
warn
fail

Backward-compatible default behavior can remain allow.

Dependency-set identity

The fingerprint should cover the resolved schema closure, not only the root document.

For example:

canonical URI/path
SHA-256
resolved namespace, where relevant

for every document.

This catches:

root schema changed
import changed
include changed
dependency added
dependency removed
dependency graph changed

even when the configured root URI remains identical.

Fail before parsing the large source

With:

changePolicy: fail

the check should happen after the schema set is resolved and hashed but before Duckle starts parsing the large XML input or performs downstream side effects.

For a 30 GB registry feed the desired flow is:

resolve XSD set
    ↓
compare contract fingerprint
    ↓
changed?
    yes -> stop
    no  -> start large XML parse

A parser-contract mismatch should not advance watermarks, baselines, publication state, or other deferred-success state.

Explain the change

Machine-readable output should identify what changed.

For example:

{
  "code": "xsd_contract_changed",
  "root": "schemas/company.xsd",
  "previous": "...",
  "current": "...",
  "changes": [
    {
      "uri": "schemas/common-types.xsd",
      "change": "modified",
      "previous_sha256": "...",
      "current_sha256": "..."
    },
    {
      "uri": "schemas/new-types.xsd",
      "change": "added",
      "sha256": "..."
    }
  ]
}

Where Duckle can cheaply derive it, also showing the resulting column/schema difference would be useful:

+ legal_form_code VARCHAR
turnover DECIMAL(18,2) -> DECIMAL(20,2)
address required -> optional

but dependency-level drift detection is sufficient for v1.

Explicit acceptance

Provide an explicit way for an operator or authorized agent to make a changed parser contract the new accepted version.

Conceptually:

duckle schema inspect <pipeline>
duckle schema accept <pipeline>

or XSD-specific equivalents.

The exact command naming is not important.

Important semantics:

  • acceptance is explicit;
  • acceptance is audited;
  • a failed run never automatically accepts the observed schema;
  • production policy can forbid agents from accepting a new parser contract;
  • the previously accepted fingerprint remains available for comparison.

This is similar in spirit to manually accepting a new qa.baseline.

Run provenance

A run should record both:

accepted parser-contract fingerprint
observed/resolved XSD dependency hashes

so it is possible to prove that the run used exactly the contract that was approved.

The existing signed artifact records can remain the source of the individual XSD hashes.

Remote/offline behavior

This should compose with #288 and the network policy.

In a pinned/offline production environment:

accepted XSD dependency set
    -> exact known bytes

should be usable without silently fetching a new version from the publisher.

A remote schema changing should never be automatically accepted merely because the URL remained the same.

Scope

This does not require:

  • full XSD validation;
  • implementing the complete XML Schema standard;
  • semantic compatibility analysis between arbitrary XSD versions;
  • a hosted schema registry.

A first implementation can simply detect:

accepted dependency fingerprint != observed dependency fingerprint

and apply allow | warn | fail.

That already provides the important safety property.

Why this matters

For recurring official-register ingestion, the XSD is effectively part of the parser implementation.

The production invariant should therefore be:

Pipeline code and parser contract cannot change independently without the change being visible and, when configured, explicitly approved.

That makes registry ingestion considerably more reproducible and safer for unattended or agent-managed pipelines.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions