Skip to content

fix(sdk)!: name the TDF manifest entry manifest.json per spec - #405

Open
pflynn-virtru wants to merge 1 commit into
mainfrom
fix/manifest-json-entry-name
Open

pflynn-virtru wants to merge 1 commit into
mainfrom
fix/manifest-json-entry-name

Conversation

@pflynn-virtru

@pflynn-virtru pflynn-virtru commented Sep 15, 2026

Copy link
Copy Markdown
Member

Refs opentdf/platform#3513. Follows opentdf/platform#4049 (go) and unblocks the java cells of opentdf/tests#600.

Warning

Breaking file-format change. Archives written by this SDK now name the manifest entry manifest.json. Readers that look it up by exact name cannot read them until they accept the spec name — including this SDK's own earlier releases.

Problem

The OpenTDF spec names the manifest entry in a .tdf archive manifest.json:

The manifest.json file MUST be in JSON format and reside within the root of the OpenTDF Zip archive.

This SDK writes and reads 0.manifest.json. On the read side TDFReader does an exact containsKey with no fallback, so a TDF produced by any implementation written against the published spec is rejected with tdf doesn't contain a manifest before any schema check runs. SDK.isTDF carries its own copy of the literal.

Every SDK agreed on the same wrong name, so nothing caught it — which is exactly the third-party incompatibility opentdf/platform#3513 reports.

The 0. prefix is a holdover from an early design that anticipated several payload/manifest pairs per archive. That design never shipped.

Change

WriteTDFWriter.TDF_MANIFEST_FILE_NAME is now manifest.json; the off-spec name moves to a new TDF_MANIFEST_FILE_NAME_OFFSPEC constant that only readers consult.

ReadTDFReader prefers manifest.json and falls back to 0.manifest.json, so archives written by earlier releases keep working. Preference matters rather than first-match: an archive carrying both must not have its conformant entry passed over for the superseded one.

SniffingSDK.isTDF accepts either name. It also no longer requires the archive to hold exactly two entries. That check was flagged in opentdf/tests#600 and is worth dropping on its own terms: the spec fixes where the manifest lives, not what else the archive may hold, and the count made isTDF stricter than the TDFReader it screens for — a three-entry archive would be rejected by the sniffer and then read fine by the reader.

0.payload is untouched. It is recorded in the manifest's payload.url, so renaming it would alter manifest contents rather than just archive layout, and neither the spec nor opentdf/platform#4049 fixes it.

scripts/test-mlkem.sh and scripts/test-hybrid-pqc.sh already match the entry by the manifest\.json$ suffix and read back whatever name they find, so they need no change.

Downstream impact

Consumer Site
java-sdk < this release TDFReader.java exact containsKey; SDK.isTDF
web-sdk getManifest(cd, '0.manifest.json') in tdf3/src/tdf.ts, tdf3/src/client/index.ts
client-cpp sdk_constants.h: kTDFManifestFileName
opentdf/docs quickstart tells users to unzip -p example.tdf 0.manifest.json

go (opentdf/platform#4049) and xtest (opentdf/tests#600) are in flight.

Testing

Written test-first. Each behavioral test was watched failing against unmodified production code, and the failure was the expected one:

Test Failure without the change
TDFReaderTest.readsManifestUnderTheSpecName IllegalArgumentException: tdf doesn't contain a manifest
TDFReaderTest.readsThePayloadAlongsideASpecNamedManifest same
TDFReaderTest.prefersTheSpecNameWhenAnArchiveCarriesBoth returned the off-spec manifest
TDFWriterTest.writesTheManifestUnderTheSpecEntryName entry named 0.manifest.json
SDKTest.testExaminingTDFWithSpecManifestName false
SDKTest.testExaminingTDFWithAnExtraEntry false

prefersTheSpecNameWhenAnArchiveCarriesBoth files a different manifest under each name, so it cannot pass by reading whichever entry the reader happened to pick.

Four tests guard behavior that must not change and pass both before and after: TDFReaderTest.readsManifestUnderTheOffspecName, TDFReaderTest.rejectsAnArchiveWithNoManifestUnderEitherName, SDKTest.testExaminingZipWithNoManifest, SDKTest.testExaminingZipWithNoPayload. The pre-existing SDKTest.testExaminingValidZTDF and testExaminingManifest run against the checked-in sample.txt.tdf, which is an off-spec-named archive from before this change, so backward compatibility stays under test with a real fixture rather than a synthetic one.

$ mvn -pl sdk -am test -Dtest='TDFReaderTest,TDFWriterTest,SDKTest'
Tests run: 19, Failures: 0, Errors: 0, Skipped: 0

CI

mavenverify is green — the full suite, both the fips and non-fips runs, including TDFRootSignatureTest and the zip/fuzzing suites. Platform Integration and Platform Integration (FIPS) also pass.

I could not run any of that locally: codegen needs buf against the BSR, and this machine is unauthenticated and hard rate-limited (resource_exhausted: too many requests), so mvn install never got past generate-sources. Only the three targeted test classes above ran locally.

xtest is red, as expected

Both java@pull-405 cells fail: 93 failed, 5 passed — the same tally, from the same four causes, that opentdf/platform#4049 reported for its own xct cells.

Cause Count Whose bug
KeyError: There is no item named '0.manifest.json' in the archive 46 xtest — manifest(), validate_manifest_schema()
FileNotFoundError: .../unzipped/0.manifest.json 32 xtest — update_manifest()
CalledProcessError on decrypt 12 readers that predate the spec name
assert b'segment' in ... 3 same — test_tdf_altered_payload_end gets a manifest-lookup failure instead of a segment-integrity message

78 of 93 are xtest's own hardcoded 0.manifest.json, which opentdf/tests#600 fixes.

Of the decrypt failures, 8 are sdk/java/dist/v0.18.0/cli.sh raising tdf doesn't contain a manifest against an archive written by this branch. That is this PR's documented breaking change observed directly, not a defect: v0.18.0's reader predates the fallback added here. Every current reader in the matrix handles it.

xct (main, go@main) passes, confirming the failures are confined to cells that build java from this branch.

This PR should stay draft until opentdf/tests#600 lands; merging sooner leaves xtest red.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 6bdbb252-7af8-4a94-ba39-9f089becfc25

📥 Commits

Reviewing files that changed from the base of the PR and between 7191d05 and 446d8ec.

📒 Files selected for processing (6)
  • sdk/src/main/java/io/opentdf/platform/sdk/SDK.java
  • sdk/src/main/java/io/opentdf/platform/sdk/TDFReader.java
  • sdk/src/main/java/io/opentdf/platform/sdk/TDFWriter.java
  • sdk/src/test/java/io/opentdf/platform/sdk/SDKTest.java
  • sdk/src/test/java/io/opentdf/platform/sdk/TDFReaderTest.java
  • sdk/src/test/java/io/opentdf/platform/sdk/TDFWriterTest.java

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The SDK now writes manifest.json, reads both spec and legacy manifest names, prefers the spec name when both exist, and recognizes archives with additional entries when they contain a manifest and 0.payload.

Changes

Manifest compatibility

Layer / File(s) Summary
Spec manifest output
sdk/src/main/java/io/opentdf/platform/sdk/TDFWriter.java, sdk/src/test/java/io/opentdf/platform/sdk/TDFWriterTest.java
TDFWriter now emits manifest.json and exposes 0.manifest.json as the legacy constant. Tests verify the archive entry names.
Manifest reading compatibility
sdk/src/main/java/io/opentdf/platform/sdk/TDFReader.java, sdk/src/test/java/io/opentdf/platform/sdk/TDFReaderTest.java
TDFReader prefers manifest.json, falls back to 0.manifest.json, and preserves missing-manifest and missing-payload validation.
TDF archive detection
sdk/src/main/java/io/opentdf/platform/sdk/SDK.java, sdk/src/test/java/io/opentdf/platform/sdk/SDKTest.java
SDK.isTDF accepts either manifest name with 0.payload and ignores additional entries. Tests cover valid and incomplete archives.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Suggested reviewers: mkleene

Merge Risk: ⚪ Minimal · up to 446d8

The manifest-name compatibility change preserves legacy reads and accepts spec-compliant archives without an identified actionable regression.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 20 functions across 6 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary breaking change: renaming the TDF manifest entry to the spec-compliant "manifest.json".
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/manifest-json-entry-name

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

A rabbit packs the manifest bright
manifest.json now marks the rite
Old names still open every door
Extra entries matter no more
Payloads hop in files just right
The SDK thumps its feet tonight

Comment @coderabbitai help to get the list of available commands.

@pflynn-virtru
pflynn-virtru marked this pull request as ready for review September 15, 2026 19:04
@pflynn-virtru
pflynn-virtru requested review from a team as code owners September 15, 2026 19:04
The OpenTDF spec puts the manifest at the archive root under
`manifest.json`. This SDK wrote and read `0.manifest.json`, an exact
lookup with no fallback, so a TDF produced by any implementation written
against the published spec was rejected outright.

Write: `TDFWriter` emits `manifest.json`.

Read: `TDFReader` accepts either name, preferring `manifest.json` when an
archive carries both, so archives from earlier releases keep working.

`SDK.isTDF` accepts either name too, and no longer requires the archive
to hold exactly two entries -- the spec fixes where the manifest lives,
not what else the archive may hold, and the count check made isTDF
stricter than the reader it screens for.

`0.payload` is unchanged: it is recorded in the manifest's payload URL
field, so renaming it would alter manifest contents rather than just
archive layout.

Refs opentdf/platform#3513
Follows opentdf/platform#4049

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Paul Flynn <pflynn-virtru@users.noreply.github.com>
@pflynn-virtru
pflynn-virtru force-pushed the fix/manifest-json-entry-name branch from 446d8ec to 9503fa7 Compare September 15, 2026 19:06
@pflynn-virtru pflynn-virtru changed the title fix(sdk)!: name the ZTDF manifest entry manifest.json per spec fix(sdk)!: name the TDF manifest entry manifest.json per spec Sep 15, 2026
@github-actions

Copy link
Copy Markdown
Contributor

X-Test Failure Report

@sonarqubecloud

Copy link
Copy Markdown

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