Repository navigation
feat(xtest): cross-SDK TDF validity suite (DSPX-5349) - #677
Draft
dmihalcik-virtru wants to merge 6 commits into
Draft
dmihalcik-virtru wants to merge 6 commits into
dmihalcik-virtru wants to merge 6 commits into
Conversation
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
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. Comment |
Ask every SDK's `validate` command the same question about the same bytes, and hold each answer to one checked-in baseline. - tdfs.py: a `tdf_validity` feature, `SDK.validate()` and `run_bounded()`, which runs the shim in its own process group and kills the group on a timeout, so a hang is reported rather than wedging the run. - cli.sh (go, java, js): `validate <file>` passes the CLI's stdout and exit status through unchanged; `supports tdf_validity` looks for the command in the CLI's help, so released CLIs skip. Contract: one JSON line, exit 0 valid, 3 invalid, anything else could not run. - tdfvalidity_corpus.py: 236 static cases built in Python -- the 42 manifest cases and the archive cases from web-sdk#1104, Go's IsValidTdf fixtures, truncations at every structural boundary, malformed archives, the DSPX-5348 divergences, container-profile questions, and one-keyword-at-a-time lax-schema cases that kill every non-equivalent schema mutant. - tdfvalidity.py: the strict contract parser, the baseline, judging (including the reason's layer, where an SDK reports one), the tdf-validity.json report, and a refresh command. - tdfvalidity_baseline.json: go and js seeded from the libraries their CLIs wrap (platform/sdk v0.35.0 IsValidTdf; web-sdk#1119 validateTdf); every cell that differs from the agreed answer names its ticket. java is pending until DSPX-5352 ships a validate command. - test_tdf_validity.py: the static corpus (no platform) and round trips for every encrypt SDK x validator, including X-Wing, secp+ML-KEM and ML-KEM (a known divergence until DSPX-4388). - test_tdf_validity_units.py: offline tests of all of the above, including the shims against stand-in CLIs. - spec/tdf-validity.md: what "valid" means, the contract, the classification and how to refresh the baseline. Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
- xtest.yml: test_tdf_validity.py beside test_tdfs.py and test_policytypes.py in both standard-xtest steps; a step after the extra KAS instances start for the PQ round trips (marker tdf_validity_pqc), with its own report file; upload test-results/**/*.json so tdf-validity*.json survives the run. - check.yml: run test_tdf_validity_units.py with the other offline harness tests. Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
dmihalcik-virtru
force-pushed
the
DSPX-5349-xtest-tdf-validity-suite
branch
from
October 23, 2026 14:32
26bc6b4 to
eadaa65
Compare
X-Test Failure Report |
All three validate CLIs now exist as PRs, so build and run them rather than only the libraries behind them: - java: seeded by running `java -jar cmdline.jar validate`, built from java-sdk#428 (8e9221d), over the corpus with the refresh command. Its divergences carry tickets: STORED and size checks (DSPX-4924), a stored size that runs into the central directory (DSPX-5103), and mlkem-wrapped (DSPX-4388). Its duplicate-name rejections sit at the archive layer. - go and js: otdfctl from platform#4254 (e33f3cb) and @opentdf/ctl from web-sdk#1119 (d293e509) match every cell of the existing baseline, reasons included. - java's shim asks the jar `supports tdf_validity`, as it does for dpop. - A json/number-10001-digits case: java rejects it, go and js do not; RFC 8259 allows the limit, so it is pinned as unspecified. - Keep the start of a reason, not the end: that is where each SDK says which layer rejected the file. Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
SonarCloud flags random.Random as a weak PRNG (python:S2245), which failed the security rating on new code. The corpus only needs deterministic, random-looking bytes, so expand SHA-256 in counter mode instead. The payload and zip/random bytes change; all three validate CLIs still match every cell of the baseline. Also use [[ ]] in the new validate branches of the go and java shims (shelldre:S7688). Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
X-Test Failure Report |
…simplify it (DSPX-5349) Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
…ps (DSPX-5349) Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



What and why
DSPX-5349, under the TDF Validity epic (DSPX-5348).
This adds
xtest/test_tdf_validity.py. It asks every SDK'svalidatecommand the same question about the same bytes: is this a valid TDF? Each answer is held to one checked-in baseline,xtest/tdfvalidity_baseline.json. Any difference fails the cell, in either direction. A validator that gets more lenient fails, and so does a known divergence that quietly stops reproducing.Today each SDK carries its own copy of these fixtures; web-sdk#1104 alone has 48 archives and a Go verifier script. Those copies are how the SDKs drifted apart.
spec/tdf-validity.mdhas the details.CLI contract (all three SDKs)
otdfctl validate <file>@opentdf/ctl validate <file>java -jar cmdline.jar validate <file>{"valid":true}or{"valid":false,"reason":"..."}.The suite uses 3 for "invalid", not the 1 the tickets say: cobra, Node and picocli all exit 1 on an unhandled error, so 1 would make a crash look like a verdict. This is written down in the shim headers, in
tdfvalidity.pyand in the spec. The parser is strict: if stdout does not agree with the exit status, the result counts as an error, not a verdict.What's here
xtest/tdfs.pytdf_validityfeature.SDK.validate(path), which returns a parsedValidateResult(valid, invalid with a reason, or error).run_bounded(), which runs the shim in its own process group and kills the whole group on a timeout (XT_VALIDATE_TIMEOUT, default 60 s).xtest/sdk/{go,java,js}/cli.shvalidate <file>hands off to the CLI withexec, so stdout and the exit status come through unchanged. It passes no platform or log-level flags.supports tdf_validitydetects the command, so released CLIs skip. The go shim looks for the command's usage line inhelp validate, because cobra prints the root usage and exits 0 for an unknown help topic. The js shim looks for it inopentdf help. The java shim asks the jar itself (supports tdf_validity), as it already does for dpop.module@versiononce and runs the binary, instead of usinggo run.go runturns exit 3 into "exit status 3" plus its own exit 1.xtest/tdfvalidity_corpus.py: 237 static cases, generated in Python, byte-identical for every SDK:TestNew_ShouldValidateStandardTdf,TestNew_ShouldNotValidateBadStandardTdfand related fixtures, verbatim, intdfvalidity_fixtures.py;manifest-lax.schema.json;xtest/goldenfiles;xtest/tdfvalidity.py: the contract parser, the baseline model and its consistency checks, judging, thetest-results/tdf-validity.jsonreport, and the baseline refresh command. Judging includes the reason's layer: archive, missing manifest, JSON, or schema, matched by per-SDK patterns.xtest/test_tdf_validity.py:--dist loadscopecan spread it across workers.tdf_validity_pqc. ML-KEM is recorded as a known divergence (DSPX-4388), not skipped.xtest/test_tdf_validity_units.py: 125 offline tests. They cover:zipfileandzipinspect;jsonschemarun of the lax schema;run_boundedgroup cleanup, baseline parsing and consistency, judging, the report and refresh;cli.shshims, run against stand-in CLIs.xtest.yml: addstest_tdf_validity.pynext totest_tdfs.py/test_policytypes.pyin both standard steps. Adds a step after the extra KAS instances start, for the PQ round trips (they need km1), with its own report file. Uploadstest-results/**/*.json.check.yml: runs the new unit tests.spec/tdf-validity.md: what "valid" means, the contract, the classification, current results, and how to refresh or seed the baseline.Baseline: how it was seeded
Every SDK's baseline comes from its real
validateCLI, built locally from the CLI PRs and run over this exact corpus through the shims in this PR:otdfctlfrom feat(cli): add otdfctl validate command (DSPX-5350) platform#4254 (e33f3cb). It reports platform/sdk v0.35.0IsValidTdfwitherr.Error()as the reason.@opentdf/ctlfrom feat(cli): add validate command (DSPX-5351) web-sdk#1119 (d293e509, stacked on #1118, #1117 and #1104). Its verdicts match #1104'sisValidTdfon every case.cmdline.jarfrom feat(sdk): add isValidTdf with Go lax-schema validation, and tdf validate (DSPX-5352) java-sdk#428 (8e9221d, stacked on fix(xtest): Enable extended EC tests for java >= 0.13.0 #418). It was seeded with the documentedtdfvalidity.py ... --seed javarefresh, then I added the tickets.All 711 static cells (237 × 3) match, reason layers included. Every cell that differs from the agreed answer names its ticket:
mlkem-wrappedmanifests and the ML-KEM round trip0.payloadentryjava's STORED-entry cells carry DSPX-4924 because there is no java ticket for them yet. java-sdk#424 would make java read deflated entries, the opposite of what DSPX-5354 asks of Go, so the container-profile decision has to come first. The ML-KEM round-trip cells are inferred, not observed: all three SDKs vendor the same lax schema, and the
writer/*-mlkem-wrappedcases show each one rejectingmlkem-wrapped.Things this turned up:
golden/xstext-java-v0.7.5-*omitsmethod.isStreamable, so all three validators reject it, yet every SDK still decrypts it (test_legacy.py). "Valid" is stricter than "readable" there.Base branch
Based on main, not stacked on #624. #624's
zipmutate.pyrebuilds only the trailer of an existing TDF, and it hardcodes STORED entries with equal sizes. It can't produce most of this corpus: from-scratch archives, deflate and size mismatches, local-header lies, truncation, or data-descriptor and ZIP64 entries. Its runner is a private, decrypt-specific helper in a test module, andzipreport.pyreports decrypt conformance, not validity. #624 is also 27 commits behind main. This PR follows #624's patterns instead:user_propertiescollected in conftest, JSON plus a step summary, and a process-group timeout. It reuseszipinspect.pyas the independent oracle in the unit tests. Expect small conflicts with #624 inconftest.py(both add apytest_runtest_logreportand a call inpytest_sessionfinish) and in thextest.ymltest lists.#600 rewrites how
tdfs.pyreads the manifest. The changes here are additive and stay away from those functions.Boundedis aNamedTuplespecifically so the import block doesn't touch the line #600 edits.How to test
What I ran locally:
ruff check,ruff format --check: clean.pyright: 0 errors.check.ymloffline list, includingtest_tdf_validity_units.py(125 tests): 479 passed.shellcheck v0.8.0andshfmt v3(via docker) on the three shims: clean.go@pr4254,js@pr1119andjava@pr428.pytest test_tdf_validity.py --sdks "go@pr4254 js@pr1119 java@pr428" -k TestCorpus -n 6: 711 passed, every cellmatch. That run covers the shims,supports tdf_validitydetection,SDK.validate, judging with reason layers, the conftest report hook andtdf-validity.json. Against a deliberately wrong baseline, the drift and reason failures fired as intended.go installfallback against released otdfctl v0.38.1:supports tdf_validitysays no, andvalidatereports an error.xctjobs are green, andscriptcheckand SonarCloud pass. In every installed build (go/java/js, released andmain), the validity cells skip with "doesn't yet support [tdf_validity]": 1,470 skipped in the standard step, and 18 skipped in the new PQ step.Not run: the platform round trips (
test_roundtrip*). They need a platform and KAS, and the CLIs need to be in the builds xtest installs. They collect correctly (--collect-only). Until #4254, #1119 and java-sdk#428 land and reach those builds, every cell in CI skips through thetdf_validitygate, so this PR turns nothing red. To try a CLI branch sooner, useotdf-sdk-mgr install tip --ref ...plusXT_FORCE_SUPPORTS=tdf_validity.Known gaps
xtest/manifest.schema.json(the strict schematest_tdfs.pychecks writer output against) lists onlywrapped,remoteandec-wrappedkey access types, with nohybrid-wrappedormlkem-wrapped. This PR doesn't change it.archive/zip64and by every web-sdk-written file."go:java".Related
check-go.gocan shrink to a smoke test (DSPX-5358).