Skip to content

v0.1.1

Choose a tag to compare

@clemlesne clemlesne released this 06 Aug 13:57
· 19 commits to main since this release

What's new

CI, tests, vendored vectors and documentation. No library code changed.

git diff v0.1.0 v0.1.1 -- src/ is empty: same API, same wire format, same bytes. The v0.1.0 notes describe what the package does.

v0.1.0 never published. Its Release run failed at the attestation step, so nothing reached PyPI and that page carries no assets. v0.1.1 is the first version you can install. Release run.

$ pip install c2patxt

Python 3.10 – 3.14, CPython. One runtime dependency: cryptography~=48.0.

Read VALID correctly. The credential in the quickstart is self-signed and this package ships zero trust anchors, so a correct mark verifies as VALID carrying signingCredential.untrusted. C2PA 14.3.5 defines Valid without requiring trust; 14.3.6 adds trust separately. Treating that as an error is the mistake this package most expects.

Breaking changes

None in the package, and nothing to upgrade from. One for anyone vendoring the conformance vectors: tests/vectors/third_party/ had no PROVENANCE.md and no licence notice, while the vectors README stated CC0 with no scope qualifier over a directory holding MIT and Apache-2.0 files that do carry attribution obligations.

Path Licence Attribution obligation
A8ConformanceTest-*.txt, SHA256SUMS CC0 1.0 none
cbor/ (cbor-wg) BSD-2-Clause none
cose/ (cose-wg) Unlicense none
third_party/ (EncypherAI, writerslogic) MIT, Apache-2.0 yes

The test enforcing provenance iterated (COSE, CBOR) — the two permissive corpora — and skipped the encumbered one. It now covers all three, and requires a commit-shaped pin and both licence names. Each directory's PROVENANCE.md records its licence and the upstream commit it was taken at.

Why the first release failed

actions/attest picks its mode from its inputs. predicate-type plus predicate selects Custom mode, which takes the predicate verbatim, so predicate: "{}" uploaded a SLSA v1 statement with no buildDefinition and the API rejected it — "Failed to persist attestation: Invalid Argument - build definition is nil". The run logged Attestation type: Custom, which is the tell. actions/attest-build-provenance builds the predicate from the workflow run instead, so it can name the builder, the invocation and the commit.

A release event reads the workflow from the tagged commit, not from main. A fix landed on main afterwards does nothing, and re-running re-reads the same broken file. Move the tag, or cut the next version. Now in CONTRIBUTING.

The release notes are the changelog

CHANGELOG.md is deleted. A changelog file and a release page are one document maintained twice, and the file is the copy that goes stale — it carried ## [0.1.0] — unreleased while the release was being published. pyproject.toml already pointed Changelog at /releases. Its two sections with no other home, the versioning caveat and "Evaluated but rejected", are in the v0.1.0 notes. The sdist no longer ships it, and docs/release-scope.md no longer links it.

So this page is load-bearing. test_the_published_size_figures_are_still_true holds the numbers printed here, in the README and in the platform handoff: store exactly 1,797 B, inflation 3.89–3.91 against the README's 3.90, 6,900–7,100 B per mark against the handoff's 7,001. The last two are measured under the pinned context, not a real UUID and clock. They went stale silently once, when the manifest gained the c2pa.metadata assertion the conformance rubric requires and three documents kept quoting pre-metadata figures. A deliberate size change now fails the test, forcing the documents to move in the same commit.

Claims the first release falsified

Found by grepping for statements a published, public repository makes untrue:

  • README's "not published yet" blockquote, and the PLANNED qualifier on SBOMs and provenance.
  • SECURITY.md's "not published and this repository is internal" block, and its "No release exists yet" supply-chain paragraph.
  • codspeed.yml's reasoning that OIDC is needed because CodSpeed's tokenless path is public-repositories-only.

SECURITY.md also claimed private vulnerability reporting was enabled. It was not. It is now, so the advisory link in the shipped policy resolves. Two live channels: GitHub Security Advisories, or security@duale.ai.

A test that had no oracle: CBOR major type 7

cbor-wg publishes mt7-float and mt7-simple in the tests/rfc8949-appendixA/ directory we fetch mt0–mt6 from. Both landed in e7ba3eb5 on 2026-01-21, the same commit that added mt0–mt6. We fetched six of seven major types out of a directory holding all seven; three places in the suite then asserted type 7 had no corpus and worked around it.

The corpus goes 42 → 70 items across nine files, all eighteen .cbor and .edn bytes identical to upstream at 001eb684. Nine are refused, every refusal correct: six non-shortest-form floats and non-canonical NaNs that RFC 8949 4.2.1 and 4.2.2 forbid — upstream's own descriptions read "Infinity coded as f32 instead of f16" — and three simple values this package does not carry. Determinism enforcement is now asserted against somebody else's published bytes rather than vectors written here, and the accepted/refused test has both arms populated for the first time, 61 against 9.

The new items exposed a latent bug in that test. writer_refuses() returns True both when dumps refuses a value and when loads never produced one, and the classifier then called loads a second time unguarded. Unreachable while the corpus had no mt7.

make download-vectors-cbor fetches both files, and SHA256SUMS covers them.

Documentation corrections

Each was contradicted by a file already in the tree.

We published True
eddsa-* use Ed25519 with alg = -8 eddsa-sig-02 is Ed448, vendored because 13.2.1 forbids it
Each file carries intermediates.ToBeSign_hex eddsa-01 is a COSE_Sign, the multi-signer structure, not a COSE_Sign1: it carries intermediates.signers, which puts its Sig_structure one level down. Eleven of the twelve publish the top-level field; this is the one that does not
sign1-* add three passing and six failing cases All nine from upstream's sign1-tests/sign-pass-01…03, sign-fail-01…04, 06, 07 — are ES256 over P-256, outside our narrowing. Useless as signature oracles, valuable as Sig_structure oracles, which is algorithm-independent
Upstream last pushed 2024-03-13 (cose/), 2026-02-22 (cbor/) A push date cannot tell a maintainer whether the vectors moved. cbor/PROVENANCE.md and cose/PROVENANCE.md now record the upstream commit and the date the vendored directory last changed

sign-fail-05 does not exist upstream. The gap in the numbering is theirs, not a vendoring error.

The push-date rule is the same mutable-reference rule CLAUDE.md applies to GitHub Actions.

Evaluated and rejected

  • Re-running the failed v0.1.0 run, or moving its tag. Re-running re-reads the same broken workflow; moving a published tag makes the release page describe bytes it did not build. Cutting v0.1.1 leaves the record intact.
  • Filtering the vendored third-party vectors. Two of EncypherAI's fifteen records are A.8 unstructured text — ascii_small and unicode_all_bytes; the other thirteen cover formats this package does not implement. Vendored whole so the file stays diffable against upstream: a filtered copy cannot be checked for drift.
  • Counting refused corpus items in aggregate. As one number the assertion was carried entirely by tags, and mutating dumps to emit float64 — a MAJOR wire change — left it green. Counted by reason, each half says what it checks.

Verify this release yourself

$ gh attestation verify ./c2patxt-0.1.1-py3-none-any.whl -R dualeai/c2patxt
$ pypi-attestations verify pypi --repository https://github.com/dualeai/c2patxt <url>

The first proves artifact → commit; the predicate names refs/tags/v0.1.1 and commit 632dbf3029b1e9a83a6c06f4ebe82243e81ef4ef. The second proves artifact → publisher (PEP 740) and needs no GitHub account.

Three attestations sign the same wheel and sdist digests — https://slsa.dev/provenance/v1, https://cyclonedx.org/bom, https://spdx.dev/Document/v2.3. Both SBOMs are attached here, and either can be checked alone with --predicate-type https://cyclonedx.org/bom.

In PyPI's own words: "An attestation will tell you where a PyPI package came from, but not whether you should trust it." Neither pip nor uv gates installation on attestations.

Versioning

The public API stays unstable until 1.0.0; anything outside c2patxt.__all__ is private. Any change to the bytes we emit is a MAJOR version of both this package and the conformance vector file, because text marked by an older version must keep verifying.

What ships and what deliberately does not: release scope · where we disagree with other implementations, and why: known divergences

Full Changelog: v0.1.0...v0.1.1