Skip to content

feat(tdf): spec-compliant container — manifest.json entry, payload from payload.url - #188

Open
arkavo-com wants to merge 9 commits into
b-long:mainfrom
arkavo-org:tdf-container-spec-compliance
Open

arkavo-com wants to merge 9 commits into
b-long:mainfrom
arkavo-org:tdf-container-spec-compliance

Conversation

@arkavo-com

Copy link
Copy Markdown
Collaborator

Summary

Brings the TDF zip container into compliance with the OpenTDF spec.

  • Write: manifest entry is now manifest.json at the archive root (was 0.manifest.json). payload.url is derived from the same constant as the payload zip entry (0.payload), so the two cannot diverge.
  • Read: manifest resolved as manifest.json first, then legacy 0.manifest.json (accepted permanently). Payload entry located from manifest.payload.url, falling back to 0.payload only when the url is empty; a url naming a missing entry is an error quoting the url; unsafe urls (.. segment, leading /, backslash) are rejected before any lookup. is_tdf no longer requires exactly two entries.
  • Version: Manifest.spec_version() resolves schemaVersion > tdf_spec_version > payload.tdf_spec_version; both tdf_spec_version placements are accepted on read and never serialized.
  • CLI: supports spec-container returns 0 for the cross-SDK conformance harness.
  • Non-UTF-8 manifest bytes now raise SDKException from TDFReader instead of a bare UnicodeDecodeError.

The feat(tdf)!: commit carries a BREAKING CHANGE footer for release-please; README gains a "TDF container format" section.

Test plan

  • uv run pytest tests/ -m "not integration": 266 passed
  • uv run ruff check . && uv run ruff format --check . clean
  • Cross-SDK stage-1 harness (opentdf-tests): python↔rust round-trips green; new test_container_layout green for python
  • Integration tests against a live platform (tests/integration/test_cli_tdf_validation.py updated to accept both names for otdfctl-produced archives)

Interop note

Files written by this version use the spec name manifest.json. Released otdfctl, upstream opentdf/platform Go, Java, and JS SDKs still look for 0.manifest.json and will not open them until they gain a read fallback (upstream reader-fallback PR planned). Files written by older versions remain readable. schemaVersion is unchanged (4.3.0); tdf_spec_version is now read at either placement but never written.

Spec: opentdf/spec schema/OpenTDF/README.md, manifest.md, payload.md.

🤖 Generated with Claude Code

arkavo-com and others added 9 commits September 16, 2026 19:41
… fallback

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…e ValueError

UnicodeDecodeError is a ValueError subclass, so the __init__ passthrough
guard (meant only for the resolvers' own missing-manifest/payload and
unsafe-url ValueErrors) let a corrupt manifest entry escape unwrapped,
contradicting the docstring's promise of SDKException on read failures.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…nt, add spec_version()

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…gration helper

_validate_tdf_zip_structure is shared by the otdfctl-produced and
Python-produced TDF checks; otdfctl still writes 0.manifest.json, so
require resolve_manifest_name() there instead of the spec-only name.
Enforce the spec name specifically in test_python_encrypt, which only
exercises the Python writer.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
BREAKING CHANGE: TDF archives are now written with the spec-mandated manifest.json entry instead of 0.manifest.json. Readers accept both names. Files written by this version cannot be opened by released otdfctl, upstream Go, Java, or JS SDKs until they add a manifest.json fallback.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ds manifest.json

Released otdfctl still looks for the legacy 0.manifest.json zip entry,
while this branch writes the spec-mandated manifest.json entry (see
README "TDF container format"). That is an accepted, documented interop
break pending an upstream otdfctl/platform reader-fallback PR.

_run_otdfctl_decrypt() now accepts an opt-in expect_failure_reason: when
set and otdfctl exits nonzero, the step calls pytest.xfail() instead of
failing, with diagnostic stdout/stderr still printed. Wired into the
three otdfctl-decrypts-Python-output scenarios in
test_cli_tdf_validation.py (test_python_encrypt,
test_cross_tool_compatibility, test_different_content_types). All other
assertions, including that Python output must contain manifest.json (not
0.manifest.json), and the otdfctl-encrypts/Python-decrypts scenarios,
are untouched. Once otdfctl gains the fallback, the branch is never
taken and these tests report a normal pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@arkavo-com

Copy link
Copy Markdown
Collaborator Author

CI note: the three integration failures on the first run (test_python_encrypt, test_cross_tool_compatibility, test_different_content_types) were all the otdfctl decrypt step on Python-produced archives failing with tdfReader.Manifest failed: zip: file not found. That is the documented interop cost of writing the spec-mandated manifest.json entry: released otdfctl still looks for 0.manifest.json.

8632c9f gates exactly those otdfctl-decrypts-Python-output steps with pytest.xfail (reason: upstream reader-fallback pending). Python-produced archives are still hard-asserted to contain manifest.json, and otdfctl-encrypt → Python-decrypt remains a hard assertion. Once otdfctl/platform gains the manifest.json fallback the gated steps pass normally with no cleanup needed.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant