Skip to content

feat(compat): DoclingDocument downgrade projector system - #703

Open
ceberam wants to merge 2 commits into
mainfrom
dev/doc-breaking-changes
Open

feat(compat): DoclingDocument downgrade projector system#703
ceberam wants to merge 2 commits into
mainfrom
dev/doc-breaking-changes

Conversation

@ceberam

@ceberam ceberam commented Jul 31, 2026

Copy link
Copy Markdown
Member

Summary

Introduces a server-side downgrade projector system so that a docling-serve instance running a newer docling-core can serve a DoclingDocument JSON that is still parseable by a client running an older version.

Closes #702

What changed

docling_core/compat.py (new)

  • register_projector(*, from_minor, to_minor) — decorator that registers a single-step downgrade function in a module-level registry.

  • project_to(doc, target_version) — walks the registry chain from the document's current schema version down to target_version, applying each projector in sequence, then returns a validated DoclingDocument with the version field restored to the target.

  • list_projectors() — returns sorted (from_minor, to_minor) pairs; useful for introspection and tests.

  • Five retroactive projectors covering every schema step from the current version back to the oldest v2.x schema:

    Step What it strips / remaps
    1.10 → 1.9 field_regions, field_items, 8 new DocItemLabel values, new CodeLanguageLabel values, new PictureClassificationLabel values, TableData.orientation, new BaseMeta sub-fields
    1.9 → 1.8 DocItem.comments, DocItem.source
    1.8 → 1.7 NodeItem.meta
    1.7 → 1.6 TableCell.fillable
    1.6 → 1.5 RichTableCell.ref

docling_core/types/doc/common/constants.py (modified)

  • Added SCHEMA_VERSION_HISTORY — ordered list of every schema version in the v2.x era with the first library release that introduced it.
  • Added _CURRENT_MINOR and FIRST_SUPPORTED_MINOR — derived constants consumed by the check script and tests to stay in sync with CURRENT_VERSION without duplication.

scripts/check_compat_projectors.py (new)

A standalone enforcement script run as a pre-commit hook and in CI.
Nine checks in a single pass:

  1. Projector count matches _CURRENT_MINOR - FIRST_SUPPORTED_MINOR.
  2. Every step N → N-1 has a registered function.
  3. SCHEMA_VERSION_HISTORY has the right number of entries.
  4. The last history entry matches CURRENT_VERSION.
  5. All projector keys are single-step downgrades.
    6–7. JSON Schema snapshots exist for every target minor version and for the current version; missing snapshots are auto-generated by copying docs/DoclingDocument.json (guaranteed to run before the docs hook regenerates it).
  6. Semantic schema diff — compares the live schema against the current-version snapshot and reports any of the four patterns that cause model_validate() to raise on old clients: new enum values, new fields on strict models (additionalProperties: false), new required fields, new anyOf union subtypes. If any are found without a matching CURRENT_VERSION bump, the hook fails with a precise diagnostic.

docs/schemas/ (new)

JSON Schema snapshots for every supported minor version (DoclingDocument_1_5.json through DoclingDocument_1_10.json). Versions 1.5–1.9 are bootstrap copies of the current schema (permissive superset); going forward each snapshot is captured automatically at the moment of the version bump, before generate_docs overwrites the live schema.

test/test_compat.py (new)

36 tests across 8 classes:

  • TestProjectorCoverage — parametrised count, step-coverage, history-length, history-sort, list_projectors sort, single-step-key, output-shape (every projector), and test-class-name presence checks.
  • TestProjector_1_{N}_to_1_{N-1} — one class per projector with at least one non-trivial field-level assertion.
  • TestChainProjection, TestNoOpProjection, TestErrorPaths.

CONTRIBUTING.md (modified)

New section "Breaking changes and schema bumps" covering:

  • Change classification table (what kind of change → what kind of bump).
  • 7-step checklist for contributors making a schema minor bump.
  • Automated-enforcement table explaining what each check catches.
  • Known blind spot: serialization-semantic changes not visible to static
    schema diff.
  • Server-side usage example for project_to().

.pre-commit-config.yaml (modified)

Added check-compat-projectors hook (runs before mypy, pytest, and docs) so the schema snapshot is captured before generate_docs overwrites docs/DoclingDocument.json.

How to use on the server side

from docling_core.compat import project_to

doc = converter.convert(pdf)

client_version = request.headers.get("Accept-Schema-Version")
if client_version:
    doc = project_to(doc, target_version=client_version)

return doc.model_dump_json()

What happens the next time a breaking change lands

A contributor adds a new field or enum value, then:

  1. The pre-commit check-compat-projectors hook detects the structural change and fails with a precise message identifying the affected $defs entry.
  2. The contributor bumps CURRENT_VERSION, appends a SCHEMA_VERSION_HISTORY entry, adds a @register_projector function, and adds a TestProjector_1_{N+1}_to_1_{N} test class.
  3. The hook re-runs, auto-captures the old schema snapshot, and passes.
    CI (pytest) enforces that the new test class exists and the projector output validates against the target-version snapshot.

ceberam added 2 commits July 31, 2026 10:55
Adds docling_core/compat.py with a register_projector decorator,
project_to() API, and five retroactive projectors (1.10→1.9→1.8→1.7→
1.6→1.5). Adds scripts/check_compat_projectors.py as a pre-commit hook
that detects unannounced structural breaking changes and enforces that
every schema minor bump ships a projector, a JSON Schema snapshot, and a
test class. 36 new tests in test/test_compat.py; CONTRIBUTING.md
documents the change-classification table and 7-step checklist.

Signed-off-by: Cesar Berrospi Ramis <ceb@zurich.ibm.com>
Signed-off-by: Cesar Berrospi Ramis <ceb@zurich.ibm.com>
@ceberam
ceberam requested review from cau-git and dolfim-ibm July 31, 2026 09:07
@ceberam ceberam added the enhancement New feature or request label Jul 31, 2026
@github-actions

Copy link
Copy Markdown
Contributor

DCO Check Passed

Thanks @ceberam, all your commits are properly signed off. 🎉

@mergify

mergify Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Merge Protections

🟢 All 2 merge protections satisfied — ready to merge.

Show 2 satisfied protections

🟢 Enforce conventional commit

Make sure that we follow https://www.conventionalcommits.org/en/v1.0.0/

  • title ~= ^(fix|feat|docs|style|refactor|perf|test|build|ci|chore|revert)(?:\(.+\))?(!)?:

🟢 Require two reviewer for test updates

When test data is updated, we require two reviewers

  • #approved-reviews-by >= 2

@PeterStaar-IBM
PeterStaar-IBM self-requested a review August 6, 2026 10:45

@PeterStaar-IBM PeterStaar-IBM left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm!

@dolfim-ibm dolfim-ibm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very nice and needed!

I just wonder if docling_core/compat.py will become a huge file, at some point we could split it apart, and maybe declare all of it as "internal".

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

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DoclingDocument schema changes silently break clients running older docling-core

3 participants