From 98ccd6de63d89eca9c23f27658f6b29fe99daffc Mon Sep 17 00:00:00 2001 From: Michael Stattmann Date: Wed, 21 Oct 2026 17:05:50 +0200 Subject: [PATCH] chore(release): prepare signer-aligned 1.0.9 dependency on unchanged native 6021100c Select the release identity 0.37.13+signer.1.0.9 (intended tag castlabs-v0.37.13+signer.1.0.9) for signer 1.0.9. Only the version label and its release metadata change: the Python runtime, native castlabs/c2pa-rs 6021100cc5060b06061ca15b634281cfeb97c718, Cargo.lock SHA-256 ff5cf2de..., Rust 1.96.0, revision 3 / mask 63 and all release controls are unchanged from the published 0.37.13+signer.1.0.8. - Package, c2pa.py header, lock, input/evidence schemas, release helper and body, escaped tag trigger, concurrency group, wheel/bundle names, expected versions and the release job tag gate select 1.0.9. - Tests reject the published 1.0.8 identity as a tag ref, dist-info, functional build version or workflow reference. - Docs record 1.0.8 as published and add 1.0.9 preparation notes. No tag, release or publication. --- .github/workflows/castlabs-vsi-release.yml | 36 +++++++++---------- .github/workflows/trusted-vsi-paired.yml | 6 ++-- README.md | 32 +++++++++-------- docs/release-notes.md | 35 +++++++++++++++--- docs/trusted-vsi-python-contract.md | 17 ++++----- pyproject.toml | 2 +- release/castlabs-release-evidence.schema.json | 2 +- release/castlabs-vsi-inputs.lock.json | 4 +-- release/castlabs-vsi-inputs.schema.json | 4 +-- scripts/castlabs_release.py | 5 +-- src/c2pa/c2pa.py | 2 +- tests/test_castlabs_release_smoke.py | 2 +- tests/test_castlabs_release_tooling.py | 23 +++++++----- tests/test_trusted_vsi_build.py | 17 +++++---- 14 files changed, 114 insertions(+), 73 deletions(-) diff --git a/.github/workflows/castlabs-vsi-release.yml b/.github/workflows/castlabs-vsi-release.yml index 079a7d43..47d1abc2 100644 --- a/.github/workflows/castlabs-vsi-release.yml +++ b/.github/workflows/castlabs-vsi-release.yml @@ -3,11 +3,11 @@ name: Castlabs VSI release on: push: tags: - - 'castlabs-v0.37.13\+signer.1.0.8' + - 'castlabs-v0.37.13\+signer.1.0.9' workflow_dispatch: inputs: source_sha: - description: Full Python SHA (feat/trusted-vsi-functional tip for 0.37.13+signer.1.0.8; paired source for trusted_vsi_only) + description: Full Python SHA (feat/trusted-vsi-functional tip for 0.37.13+signer.1.0.9; paired source for trusted_vsi_only) required: true type: string trusted_vsi_only: @@ -17,7 +17,7 @@ on: default: false concurrency: - group: castlabs-vsi-release-0.37.13+signer.1.0.8 + group: castlabs-vsi-release-0.37.13+signer.1.0.9 cancel-in-progress: false permissions: @@ -195,7 +195,7 @@ jobs: test "$(find wheelhouse -maxdepth 1 -name "*.whl" | wc -l)" -eq 1 WHEEL=$(find wheelhouse -maxdepth 1 -name "*.whl") case "$(basename "$WHEEL")" in - c2pa_python-0.37.13+signer.1.0.8-py3-none-manylinux_2_28_x86_64.whl) ;; + c2pa_python-0.37.13+signer.1.0.9-py3-none-manylinux_2_28_x86_64.whl) ;; *) echo "unexpected Linux wheel: $WHEEL" >&2; exit 1 ;; esac /opt/python/cp310-cp310/bin/python -m auditwheel show "$WHEEL" @@ -215,7 +215,7 @@ jobs: SOURCE_DATE_EPOCH: ${{ needs.prepare.outputs.source_epoch }} run: | cd c2pa-python - BUNDLE=release-out/c2pa-python-0.37.13+signer.1.0.8-native-linux-x86_64.tar.gz + BUNDLE=release-out/c2pa-python-0.37.13+signer.1.0.9-native-linux-x86_64.tar.gz python3 scripts/castlabs_release.py pack --output "$BUNDLE" \ --epoch "$SOURCE_DATE_EPOCH" \ --member x86_64-unknown-linux-gnu/libc2pa_c.so=release-out/libc2pa_c.so @@ -351,7 +351,7 @@ jobs: if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } $wheels = @(Get-ChildItem dist/*.whl) if ($wheels.Count -ne 1) { throw "expected exactly one Windows wheel" } - if ($wheels[0].Name -ne "c2pa_python-0.37.13+signer.1.0.8-py3-none-win_amd64.whl") { + if ($wheels[0].Name -ne "c2pa_python-0.37.13+signer.1.0.9-py3-none-win_amd64.whl") { throw "unexpected Windows wheel $($wheels[0].Name)" } python -m twine check $wheels[0].FullName @@ -371,7 +371,7 @@ jobs: $ErrorActionPreference = "Stop" $PSNativeCommandUseErrorActionPreference = $true Push-Location c2pa-python - $bundle = "release-out/c2pa-python-0.37.13+signer.1.0.8-native-windows-x86_64.tar.gz" + $bundle = "release-out/c2pa-python-0.37.13+signer.1.0.9-native-windows-x86_64.tar.gz" python scripts/castlabs_release.py pack --output $bundle ` --epoch $env:SOURCE_DATE_EPOCH ` --member x86_64-pc-windows-msvc/c2pa_c.dll=release-out/c2pa_c.dll @@ -424,7 +424,7 @@ jobs: - name: Test installed wheel in a clean environment shell: bash env: - CASTLABS_RELEASE_EXPECTED_VERSION: 0.37.13+signer.1.0.8 + CASTLABS_RELEASE_EXPECTED_VERSION: 0.37.13+signer.1.0.9 CASTLABS_RELEASE_SMOKE_REQUIRED: "1" C2PA_SOURCE_BUILD_VERSION: 0.92.0-dev run: | @@ -466,7 +466,7 @@ jobs: - name: Test installed wheel in a clean environment shell: pwsh env: - CASTLABS_RELEASE_EXPECTED_VERSION: 0.37.13+signer.1.0.8 + CASTLABS_RELEASE_EXPECTED_VERSION: 0.37.13+signer.1.0.9 CASTLABS_RELEASE_SMOKE_REQUIRED: "1" C2PA_SOURCE_BUILD_VERSION: 0.92.0-dev run: | @@ -490,7 +490,7 @@ jobs: release: needs: [prepare, linux, windows, test-linux-wheel, test-windows-wheel] - if: github.event_name == 'push' && github.ref == 'refs/tags/castlabs-v0.37.13+signer.1.0.8' + if: github.event_name == 'push' && github.ref == 'refs/tags/castlabs-v0.37.13+signer.1.0.9' runs-on: ubuntu-24.04 timeout-minutes: 30 environment: castlabs-vsi-release @@ -525,7 +525,7 @@ jobs: LINUX_WHEEL=$(find collected/linux -maxdepth 1 -name "*.whl") WINDOWS_WHEEL=$(find collected/windows -maxdepth 1 -name "*.whl") test "$(find collected/linux collected/windows -name "*.whl" | wc -l)" -eq 2 - BUNDLE=assets/c2pa-python-0.37.13+signer.1.0.8-py3-none-wheels.tar.gz + BUNDLE=assets/c2pa-python-0.37.13+signer.1.0.9-py3-none-wheels.tar.gz python3 c2pa-python/scripts/castlabs_release.py pack \ --output "$BUNDLE" --epoch "$SOURCE_DATE_EPOCH" \ --member "$(basename "$LINUX_WHEEL")=$LINUX_WHEEL" \ @@ -542,13 +542,13 @@ jobs: python3 c2pa-python/scripts/castlabs_release.py checksum \ --artifact "$BUNDLE.evidence.json" cp "$LINUX_WHEEL" "$WINDOWS_WHEEL" assets/ - cp collected/linux/c2pa-python-0.37.13+signer.1.0.8-native-linux-x86_64.tar.gz* assets/ - cp collected/windows/c2pa-python-0.37.13+signer.1.0.8-native-windows-x86_64.tar.gz* assets/ + cp collected/linux/c2pa-python-0.37.13+signer.1.0.9-native-linux-x86_64.tar.gz* assets/ + cp collected/windows/c2pa-python-0.37.13+signer.1.0.9-native-windows-x86_64.tar.gz* assets/ python3 c2pa-python/scripts/castlabs_release.py validate-evidence \ - --evidence assets/c2pa-python-0.37.13+signer.1.0.8-native-linux-x86_64.tar.gz.evidence.json \ + --evidence assets/c2pa-python-0.37.13+signer.1.0.9-native-linux-x86_64.tar.gz.evidence.json \ --artifact-dir assets --source-sha "${{ needs.prepare.outputs.source_sha }}" python3 c2pa-python/scripts/castlabs_release.py validate-evidence \ - --evidence assets/c2pa-python-0.37.13+signer.1.0.8-native-windows-x86_64.tar.gz.evidence.json \ + --evidence assets/c2pa-python-0.37.13+signer.1.0.9-native-windows-x86_64.tar.gz.evidence.json \ --artifact-dir assets --source-sha "${{ needs.prepare.outputs.source_sha }}" python3 c2pa-python/scripts/castlabs_release.py validate-evidence \ --evidence "$BUNDLE.evidence.json" --artifact-dir assets \ @@ -562,7 +562,7 @@ jobs: - name: Require immutable release tag shell: bash run: | - TAG=castlabs-v0.37.13+signer.1.0.8 + TAG=castlabs-v0.37.13+signer.1.0.9 git -C c2pa-python fetch origin "refs/tags/${TAG}:refs/tags/${TAG}" test "$(git -C c2pa-python rev-list -n 1 "$TAG")" = \ "${{ needs.prepare.outputs.source_sha }}" @@ -578,7 +578,7 @@ jobs: GH_TOKEN: ${{ github.token }} run: | set -euo pipefail - TAG=castlabs-v0.37.13+signer.1.0.8 + TAG=castlabs-v0.37.13+signer.1.0.9 SOURCE_SHA="${{ needs.prepare.outputs.source_sha }}" RELEASE_NAME=$(python3 c2pa-python/scripts/castlabs_release.py release-value name) RELEASE_BODY=$(python3 c2pa-python/scripts/castlabs_release.py release-value body) @@ -604,7 +604,7 @@ jobs: } discover_release() { gh api --paginate repos/castlabs/c2pa-python/releases \ - --jq '.[] | select(.tag_name == "castlabs-v0.37.13+signer.1.0.8")' > "$1" + --jq '.[] | select(.tag_name == "castlabs-v0.37.13+signer.1.0.9")' > "$1" return 0 } query_release_id() { diff --git a/.github/workflows/trusted-vsi-paired.yml b/.github/workflows/trusted-vsi-paired.yml index a9a347d1..91ab34b7 100644 --- a/.github/workflows/trusted-vsi-paired.yml +++ b/.github/workflows/trusted-vsi-paired.yml @@ -35,9 +35,9 @@ jobs: C2PA_REQUIRE_SIGN_LADDER: "1" C2PA_REQUIRE_FRAGMENTED_FILES: "1" C2PA_FRAGMENT_OFFSET_REQUIRED: "1" - FUNCTIONAL_BUILD_VERSION: 0.37.13+signer.1.0.8 + FUNCTIONAL_BUILD_VERSION: 0.37.13+signer.1.0.9 # The release smoke asserts the installed distribution version. - CASTLABS_RELEASE_EXPECTED_VERSION: 0.37.13+signer.1.0.8 + CASTLABS_RELEASE_EXPECTED_VERSION: 0.37.13+signer.1.0.9 steps: - name: Require a full Python source SHA shell: bash @@ -52,7 +52,7 @@ jobs: with: repository: castlabs/c2pa-rs # Revision-3 trusted VSI plus offset-aware fragment verification: - # the same native commit the 0.37.13+signer.1.0.8 release lock pins. This job + # the same native commit the 0.37.13+signer.1.0.9 release lock pins. This job # is qualification-only and never produces release artifacts. ref: 6021100cc5060b06061ca15b634281cfeb97c718 path: paired-rust diff --git a/README.md b/README.md index dda46091..6243e3de 100644 --- a/README.md +++ b/README.md @@ -101,29 +101,33 @@ make build-from-source C2PA_RS_PATH=$C2PA_RS_PATH ## Castlabs VSI release process -### Signer 1.0.8 preparation only +### Signer 1.0.9 preparation only -The candidate package identity is `0.37.13+signer.1.0.8`, with intended tag -`castlabs-v0.37.13+signer.1.0.8`. This is preparation, not a qualified or +The candidate package identity is `0.37.13+signer.1.0.9`, with intended tag +`castlabs-v0.37.13+signer.1.0.9`. This is preparation, not a qualified or published candidate. The signer-aligned version is a PEP 440 local version for a final, immutable **GitHub-only** release (`prerelease=false`), never PyPI. The next development series is future work after publication; it is not selected or applied in this candidate. -The candidate selects the combined #27/#28/#29 native source +Only the release identity changes from the published immutable +`0.37.13+signer.1.0.8` (tag `castlabs-v0.37.13+signer.1.0.8`, source +`abc9a034ec82ba54552f9927d6affc993b25b6c1`, release ID `415634231`), which stays +unchanged. The Python runtime and the combined #27/#28/#29 native source `6021100cc5060b06061ca15b634281cfeb97c718`, with `Cargo.lock` SHA-256 -`ff5cf2de2403ff491957fc8a2206c289203d2799a30425bd615f7c9e69974a8f` consistently -in the lock, schemas, helper, workflows, tests and current docs. Native bundle -and Python/native qualification remain publication/adoption gates. Integrate -and review the Python source on `feat/trusted-vsi-functional` and record CI -evidence against its final full SHA before any tag or publication. The local -preparation branch `release/signer-1.0.8` is not an authorized artifact-release source. +`ff5cf2de2403ff491957fc8a2206c289203d2799a30425bd615f7c9e69974a8f`, are the same +in the lock, schemas, helper, workflows, tests and current docs. Python/native +qualification of this exact identity remains a publication/adoption gate, and +1.0.8 assets or evidence must not be relabeled. Integrate and review the Python +source on `feat/trusted-vsi-functional` and record CI evidence against its final +full SHA before any tag or publication. The local preparation branch +`release/signer-1.0.9` is not an authorized artifact-release source. ### Unreleased trusted-processor API qualification The [trusted-processor API](docs/usage.md#trusted-processor-vsi-unreleased-functional-api) is separate from complete-buffer VSI and immutable dev5. The current prepared -release identity is `0.37.13+signer.1.0.8`. Its probes are true only on a native library reporting trusted +release identity is `0.37.13+signer.1.0.9`. Its probes are true only on a native library reporting trusted contract revision 3 and capability mask 63; otherwise every probe is false and session construction/operations fail before side effects. Expert signing accepts only exact `Sig_structure` bytes and returns @@ -170,7 +174,7 @@ imports (`python setup.py egg_info`, without a native download or wheel build). ### Immutable Castlabs VSI release process -The dedicated [`Castlabs VSI release`](.github/workflows/castlabs-vsi-release.yml) workflow is prepared to build version `0.37.13+signer.1.0.8` for Linux x86-64 and Windows x86-64. It does not call `scripts/download_artifacts.py`: both native libraries are compiled with Cargo `--locked` and `CARGO_BUILD_JOBS=1` from the exact [Castlabs c2pa-rs](https://github.com/castlabs/c2pa-rs) commit in [`release/castlabs-vsi-inputs.lock.json`](release/castlabs-vsi-inputs.lock.json). The lock also fixes the Rust 1.96.0 toolchain coordinated with c2pa-rs qualification, checksum-verified rustup installers, no-default-feature set, target set, and the Linux manylinux container digest. The legacy `build.yml` explicitly excludes `castlabs-v*` tag pushes and guards its jobs and PyPI publisher against manual dispatch on those tags; the dedicated workflow alone owns them. Ordinary non-Castlabs tag releases retain the legacy behavior, while manual legacy publication additionally requires `refs/heads/main` and a final `X.Y.Z` package version. +The dedicated [`Castlabs VSI release`](.github/workflows/castlabs-vsi-release.yml) workflow is prepared to build version `0.37.13+signer.1.0.9` for Linux x86-64 and Windows x86-64. It does not call `scripts/download_artifacts.py`: both native libraries are compiled with Cargo `--locked` and `CARGO_BUILD_JOBS=1` from the exact [Castlabs c2pa-rs](https://github.com/castlabs/c2pa-rs) commit in [`release/castlabs-vsi-inputs.lock.json`](release/castlabs-vsi-inputs.lock.json). The lock also fixes the Rust 1.96.0 toolchain coordinated with c2pa-rs qualification, checksum-verified rustup installers, no-default-feature set, target set, and the Linux manylinux container digest. The legacy `build.yml` explicitly excludes `castlabs-v*` tag pushes and guards its jobs and PyPI publisher against manual dispatch on those tags; the dedicated workflow alone owns them. Ordinary non-Castlabs tag releases retain the legacy behavior, while manual legacy publication additionally requires `refs/heads/main` and a final `X.Y.Z` package version. The immutable `castlabs-v0.37.8.dev1` tag records failed prerelease workflow run `34030865864`. Linux compiled successfully but its combined DynamicAssertion-plus-VSI smoke used a 5-byte callback result for a 64-byte reservation and later failed with `assertion.bmffHash.mismatch`; Windows compiled successfully and failed only in platform-sensitive wheel metadata parsing. The Linux failure was an undersized callback contract violation, not an inherent incompatibility between DynamicAssertions and VSI. No dev1 draft or GitHub release was created, and the tag remains unchanged. @@ -184,7 +188,7 @@ The immutable `castlabs-v0.37.13.dev1` tag (source `78771fa`, native `3328b2b4`) Release controls: -- Before publication, configure `castlabs-v0.37.13+signer.1.0.8` as a protected tag and require approval for the `castlabs-vsi-release` environment. The workflow's single-quoted tag filter escapes the literal `+` as `\+`; wheel filenames and dist-info directories retain `+`, and upload query parameters percent-encode it as `%2B`. +- Before publication, configure `castlabs-v0.37.13+signer.1.0.9` as a protected tag and require approval for the `castlabs-vsi-release` environment. The workflow's single-quoted tag filter escapes the literal `+` as `\+`; wheel filenames and dist-info directories retain `+`, and upload query parameters percent-encode it as `%2B`. - A tag run accepts only that exact tag. A manual run accepts only a full 40-character SHA equal to the tip of `feat/trusted-vsi-functional` and is build/test-only; draft release creation and provenance signing require the exact protected tag-triggered run. - Source checkout URLs, full SHAs, package/native versions, and the pinned `Cargo.lock` SHA-256 are asserted before each build. - The Linux wheel is built and repaired in the digest-pinned `manylinux_2_28_x86_64` image. Every wheel is tested without capability skips on clean Python 3.10 through 3.13; Python 3.10 also runs the broader unit and fragmented-file suites. @@ -194,7 +198,7 @@ Release controls: Intermediate Actions artifacts expire after seven days. A draft resume always rebuilds both platforms and requires byte-identical output for every already-uploaded asset; rerunning only an expired release job cannot recover its inputs, so rerun the complete tag workflow. If reproducibility or an operator mistake leaves a mismatched prior draft, the workflow preserves it and reports failure. Inspect it manually before any operator-authorized deletion of that exact draft and rerun the complete protected-tag workflow. Never delete a published release or use clobber to repair a mismatch. -`tests/test_castlabs_release_smoke.py` is intentionally gated for ordinary contributors because upstream native binaries do not provide every Castlabs capability. Bare pytest skips that module unless `CASTLABS_RELEASE_SMOKE_REQUIRED=1` is set. The dedicated wheel-test jobs always set it; with the gate enabled, package version, dynamic assertions, fragmented files, offset-aware fragment verification, VSI callback signing, recovery, explicit time, and MFHD probing are hard assertions with no capability skip. Contributors with a qualified installed wheel can run the same gate explicitly with `CASTLABS_RELEASE_SMOKE_REQUIRED=1 python -m pytest -q tests/test_castlabs_release_smoke.py`. `CASTLABS_RELEASE_EXPECTED_VERSION` exists only for testing an already-built prior candidate during release-pipeline repair; the dedicated tagged workflow explicitly fixes it to `0.37.13+signer.1.0.8`. +`tests/test_castlabs_release_smoke.py` is intentionally gated for ordinary contributors because upstream native binaries do not provide every Castlabs capability. Bare pytest skips that module unless `CASTLABS_RELEASE_SMOKE_REQUIRED=1` is set. The dedicated wheel-test jobs always set it; with the gate enabled, package version, dynamic assertions, fragmented files, offset-aware fragment verification, VSI callback signing, recovery, explicit time, and MFHD probing are hard assertions with no capability skip. Contributors with a qualified installed wheel can run the same gate explicitly with `CASTLABS_RELEASE_SMOKE_REQUIRED=1 python -m pytest -q tests/test_castlabs_release_smoke.py`. `CASTLABS_RELEASE_EXPECTED_VERSION` exists only for testing an already-built prior candidate during release-pipeline repair; the dedicated tagged workflow explicitly fixes it to `0.37.13+signer.1.0.9`. At the pinned c2pa-rs commit, VSI initialization signs through `Builder::from_shared_context`, so it inherits DynamicAssertions registered on the Context's claim signer. The common Python callback bridge enforces `reserve_size` as the exact serialized CBOR size for every signing path. The earlier `assertion.bmffHash.mismatch` came from returning only 5 bytes for a 64-byte reservation; it was not an inherent DynamicAssertion-plus-VSI incompatibility. The release smoke now returns valid exact-size CBOR, requires clean Reader validation for both standalone image and combined VSI init signing, and exercises combined media signing and recovery without an xfail. diff --git a/docs/release-notes.md b/docs/release-notes.md index 41dd9949..42ee6724 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,12 +1,37 @@ # Release notes -## Version 0.37.13+signer.1.0.8 (Preparation Only) +## Version 0.37.13+signer.1.0.9 (Preparation Only) -The updated signer `1.0.8` dependency uses the prepared package version -`0.37.13+signer.1.0.8` and intended tag `castlabs-v0.37.13+signer.1.0.8`. +The signer `1.0.9` dependency uses the prepared package version +`0.37.13+signer.1.0.9` and intended tag `castlabs-v0.37.13+signer.1.0.9`. This is not a qualified or published Python release. No tag or publication is part of this preparation; paired Linux/Windows CI must qualify the exact sources. +- Only the signer-aligned release identity changes. The Python runtime API, + native `castlabs/c2pa-rs` `6021100cc5060b06061ca15b634281cfeb97c718`, + `Cargo.lock` SHA-256 + `ff5cf2de2403ff491957fc8a2206c289203d2799a30425bd615f7c9e69974a8f`, Rust + `1.96.0`, native `0.92.0-dev`, trusted contract revision 3, capability mask 63, + feature selection and hermetic/evidence/ownership controls are unchanged from + `0.37.13+signer.1.0.8`. The base stays `0.37.13` because neither line changed. +- The package, lock, schemas, release helper, tag trigger (literal `+` escaped), + wheel/bundle names, tests and current docs select the new identity. The + workflows no longer reference the published 1.0.8 identity. +- The published `0.37.13+signer.1.0.8` tag, release, assets and evidence stay + unchanged and are not relabeled; the 1.0.9 assets must be built, validated and + attested from their own tagged bytes. GitHub only, never PyPI. + +## Version 0.37.13+signer.1.0.8 + +Published immutable GitHub-only release `castlabs-v0.37.13+signer.1.0.8` (release +ID `415634231`, `prerelease=false`), tagged at Python source +`abc9a034ec82ba54552f9927d6affc993b25b6c1` and built by tagged run `39010024287`. +It was not published to PyPI. The preparation notes below describe its original +inputs. + +The updated signer `1.0.8` dependency used the package version +`0.37.13+signer.1.0.8` and tag `castlabs-v0.37.13+signer.1.0.8`. + - Native inputs select `castlabs/c2pa-rs` `6021100cc5060b06061ca15b634281cfeb97c718`, with `Cargo.lock` SHA-256 `ff5cf2de2403ff491957fc8a2206c289203d2799a30425bd615f7c9e69974a8f`. @@ -32,10 +57,10 @@ part of this preparation; paired Linux/Windows CI must qualify the exact sources ### Signer-aligned version policy -Decided 2026-10-16 and applied here to the updated signer `1.0.8` dependency. +Decided 2026-10-16 and applied to the signer `1.0.8` and `1.0.9` dependencies. Such releases use a PEP 440 local version naming the signer version, `+signer.[.]`, for -example `0.37.14+signer.1.0.9`, tagged `castlabs-v0.37.14+signer.1.0.9`. They are +example `0.37.13+signer.1.0.9`, tagged `castlabs-v0.37.13+signer.1.0.9`. They are final, immutable GitHub releases (`prerelease=false`), not published to PyPI (local versions are rejected there). Development builds keep `.devN` versions, and moving the development line to the next `.devN` series is follow-up work diff --git a/docs/trusted-vsi-python-contract.md b/docs/trusted-vsi-python-contract.md index 51127cfa..5cb6e421 100644 --- a/docs/trusted-vsi-python-contract.md +++ b/docs/trusted-vsi-python-contract.md @@ -1,14 +1,15 @@ # Trusted VSI Python Contract -Status: signer 1.0.8 candidate preparation requiring native trusted-VSI +Status: signer 1.0.9 candidate preparation requiring native trusted-VSI contract revision 3 (state version 3 and typed expert VSI payloads), following `c2pa-rs` `docs/trusted-vsi-native-contract.md`. The prepared Python identity is -`0.37.13+signer.1.0.8`, with intended tag `castlabs-v0.37.13+signer.1.0.8` for a +`0.37.13+signer.1.0.9`, with intended tag `castlabs-v0.37.13+signer.1.0.9` for a final, immutable GitHub-only release (`prerelease=false`), not PyPI. This is preparation only, not Python qualification or publication. Native inputs select -`6021100cc5060b06061ca15b634281cfeb97c718`; immutable historical release inputs and -artifacts are unchanged. Its `Cargo.lock` SHA-256 is +`6021100cc5060b06061ca15b634281cfeb97c718`, unchanged from the published +`0.37.13+signer.1.0.8`; immutable historical release inputs and artifacts are +unchanged. Its `Cargo.lock` SHA-256 is `ff5cf2de2403ff491957fc8a2206c289203d2799a30425bd615f7c9e69974a8f`. The combined native #27/#28/#29 source passed focused CI; complete native bundle and Python/native artifact qualification remain release gates. The paired @@ -249,7 +250,7 @@ CBOR, BMFF or validation results. The paired workflow requires all probes true against its exact native library, then builds a NON-PUBLISHING wheel and sdist in a temporary source copy retaining -the source version `0.37.13+signer.1.0.8`. It stages only the paired native into +the source version `0.37.13+signer.1.0.9`. It stages only the paired native into that copy, not the checkout or a release artifact directory. Its version variables and the release lock/tooling match this identity. `C2PA_SOURCE_BUILD_VERSION=0.92.0-dev` identifies the native library only; it does not attest a commit or qualification. @@ -263,7 +264,7 @@ series is future work after publication. Historical dev5 and dev2 releases/tags/evidence are unchanged. `scripts/qualify_trusted_vsi_functional.py --wheel --venv ---version 0.37.13+signer.1.0.8` installs the wheel into an isolated venv, strips +--version 0.37.13+signer.1.0.9` installs the wheel into an isolated venv, strips `PYTHONPATH`/`C2PA_LIBRARY_NAME`, and runs `test_trusted_vsi_api.py`, `test_fragmented_files.py`, `test_sign_ladder.py`, `test_native_ownership.py` and `test_native_ownership_opaque.py` with `C2PA_TRUSTED_VSI_ABI_REQUIRED=1`, @@ -277,7 +278,7 @@ Paired tests require the full native library and FAIL under skip only in ad-hoc local runs. `.github/workflows/trusted-vsi-paired.yml` builds the native with Rust 1.96.0 from castlabs/c2pa-rs `6021100cc5060b06061ca15b634281cfeb97c718`, the same commit as the -`castlabs-v0.37.13+signer.1.0.8` preparation lock (trusted contract revision 3, mask 63, +`castlabs-v0.37.13+signer.1.0.9` preparation lock (trusted contract revision 3, mask 63, offset-aware fragment exports). It sets `C2PA_FRAGMENT_OFFSET_REQUIRED=1`, so a native library without the fragment-offset probe fails rather than skips. Older pins (`6b506352`, `203dc08d`, `5c186c07`) emitted state version 2 and lacked the @@ -297,7 +298,7 @@ The candidate remains on hold until CI qualifies the exact Python/native pair and its installed wheels. Selecting native `6021100c` does not establish that paired qualification. Artifact builds still require `feat/trusted-vsi-functional` (manual runs must use its full tip SHA and ref) or the exact aligned tag; the -preparation branch `release/signer-1.0.8` is not an allowed artifact-release source. +preparation branch `release/signer-1.0.9` is not an allowed artifact-release source. The non-publishing paired path can test a reviewed preparation SHA before that integration. A maintainer must review the head, retain the existing author/label gate, and confirm required jobs actually ran. This preparation does not tag or diff --git a/pyproject.toml b/pyproject.toml index ca9441b5..e7bdd13f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "c2pa-python" -version = "0.37.13+signer.1.0.8" +version = "0.37.13+signer.1.0.9" requires-python = ">=3.10" description = "Python bindings for the C2PA Content Authenticity Initiative (CAI) library" readme = { file = "README.md", content-type = "text/markdown" } diff --git a/release/castlabs-release-evidence.schema.json b/release/castlabs-release-evidence.schema.json index 419b7d41..abc8877c 100644 --- a/release/castlabs-release-evidence.schema.json +++ b/release/castlabs-release-evidence.schema.json @@ -13,7 +13,7 @@ "properties": { "type": {"enum": ["native-bundle", "wheel-bundle"]}, "package": {"const": "c2pa-python"}, - "version": {"const": "0.37.13+signer.1.0.8"}, + "version": {"const": "0.37.13+signer.1.0.9"}, "file": {"type": "string", "minLength": 1}, "size": {"type": "integer", "minimum": 1}, "sha256": {"$ref": "#/$defs/sha256"}, diff --git a/release/castlabs-vsi-inputs.lock.json b/release/castlabs-vsi-inputs.lock.json index 0e47f4e3..0fd76327 100644 --- a/release/castlabs-vsi-inputs.lock.json +++ b/release/castlabs-vsi-inputs.lock.json @@ -2,13 +2,13 @@ "schemaVersion": 1, "package": { "name": "c2pa-python", - "version": "0.37.13+signer.1.0.8" + "version": "0.37.13+signer.1.0.9" }, "pythonSource": { "repository": "castlabs/c2pa-python", "url": "https://github.com/castlabs/c2pa-python.git", "releaseBranch": "feat/trusted-vsi-functional", - "releaseTag": "castlabs-v0.37.13+signer.1.0.8" + "releaseTag": "castlabs-v0.37.13+signer.1.0.9" }, "rustSource": { "repository": "castlabs/c2pa-rs", diff --git a/release/castlabs-vsi-inputs.schema.json b/release/castlabs-vsi-inputs.schema.json index 7095c214..f7d435e6 100644 --- a/release/castlabs-vsi-inputs.schema.json +++ b/release/castlabs-vsi-inputs.schema.json @@ -30,7 +30,7 @@ "package": { "type": "object", "additionalProperties": false, "required": ["name", "version"], - "properties": {"name": {"const": "c2pa-python"}, "version": {"const": "0.37.13+signer.1.0.8"}} + "properties": {"name": {"const": "c2pa-python"}, "version": {"const": "0.37.13+signer.1.0.9"}} }, "pythonSource": { "type": "object", "additionalProperties": false, @@ -39,7 +39,7 @@ "repository": {"const": "castlabs/c2pa-python"}, "url": {"const": "https://github.com/castlabs/c2pa-python.git"}, "releaseBranch": {"const": "feat/trusted-vsi-functional"}, - "releaseTag": {"const": "castlabs-v0.37.13+signer.1.0.8"} + "releaseTag": {"const": "castlabs-v0.37.13+signer.1.0.9"} } }, "rustSource": { diff --git a/scripts/castlabs_release.py b/scripts/castlabs_release.py index 02a4ffe3..c0f22f30 100644 --- a/scripts/castlabs_release.py +++ b/scripts/castlabs_release.py @@ -26,7 +26,7 @@ LOCK_PATH = ROOT / "release" / "castlabs-vsi-inputs.lock.json" SHA256_RE = re.compile(r"^[0-9a-f]{64}$") SHA_RE = re.compile(r"^[0-9a-f]{40}$") -RELEASE_VERSION = "0.37.13+signer.1.0.8" +RELEASE_VERSION = "0.37.13+signer.1.0.9" RUST_COMMIT = "6021100cc5060b06061ca15b634281cfeb97c718" CARGO_LOCK_SHA256 = "ff5cf2de2403ff491957fc8a2206c289203d2799a30425bd615f7c9e69974a8f" RUST_TOOLCHAIN = "1.96.0" @@ -46,7 +46,8 @@ "crJSON 2.4 output with checked export, caller-controlled validation time, " "offset-aware single-file fMP4 fragment verification and the default EKU " "trust-policy fix, generic BMFF hash exclusions, ZIP 9.0.4 remediation and " - "verified VSI init refreshes. Signer-aligned 1.0.8 release, GitHub only (not PyPI). See " + "verified VSI init refreshes. Signer-aligned 1.0.9 release, GitHub only (not PyPI); " + "Python runtime and native source are unchanged from 0.37.13+signer.1.0.8. See " "the attached schema-2 evidence and SHA-256 sidecars." ) diff --git a/src/c2pa/c2pa.py b/src/c2pa/c2pa.py index 6c8c0f2f..c1f35b13 100644 --- a/src/c2pa/c2pa.py +++ b/src/c2pa/c2pa.py @@ -11,7 +11,7 @@ # specific language governing permissions and limitations under # each license. -# Version: 0.37.13+signer.1.0.8 +# Version: 0.37.13+signer.1.0.9 import ctypes import enum diff --git a/tests/test_castlabs_release_smoke.py b/tests/test_castlabs_release_smoke.py index 0c10862f..92e01dc2 100644 --- a/tests/test_castlabs_release_smoke.py +++ b/tests/test_castlabs_release_smoke.py @@ -47,7 +47,7 @@ FIXTURES = Path(__file__).parent / "fixtures" EXPECTED_VERSION = os.environ.get( - "CASTLABS_RELEASE_EXPECTED_VERSION", "0.37.13+signer.1.0.8" + "CASTLABS_RELEASE_EXPECTED_VERSION", "0.37.13+signer.1.0.9" ) REQUIRED_CAPABILITIES = { "dynamic assertions": has_dynamic_assertions, diff --git a/tests/test_castlabs_release_tooling.py b/tests/test_castlabs_release_tooling.py index 180022ad..8432a422 100644 --- a/tests/test_castlabs_release_tooling.py +++ b/tests/test_castlabs_release_tooling.py @@ -53,8 +53,8 @@ def _posix_bash() -> str: def test_signer_aligned_release_version_is_consistent(): # The release identity is consistent across the tooling, lock and source, # while validate-sources retains the strict release-branch/tag policy. - assert release.RELEASE_VERSION == "0.37.13+signer.1.0.8" - assert release.RELEASE_TAG == "castlabs-v0.37.13+signer.1.0.8" + assert release.RELEASE_VERSION == "0.37.13+signer.1.0.9" + assert release.RELEASE_TAG == "castlabs-v0.37.13+signer.1.0.9" assert release.load_lock()["package"]["version"] == release.RELEASE_VERSION assert "immutable release" in release.RELEASE_IDENTITY_TEXT assert "not PyPI" in release.RELEASE_BODY @@ -199,7 +199,7 @@ def test_release_workflows_are_pinned_bounded_and_do_not_drift_from_helper( in release_workflow ) assert ( - "c2pa_python-0.37.13+signer.1.0.8-py3-none-manylinux_2_28_x86_64.whl" in release_workflow + "c2pa_python-0.37.13+signer.1.0.9-py3-none-manylinux_2_28_x86_64.whl" in release_workflow ) assert release_workflow.count("verify-wheel-native") == 2 assert release_workflow.index("--only-plat --plat manylinux_2_28_x86_64") < ( @@ -462,6 +462,10 @@ def test_release_workflows_are_pinned_bounded_and_do_not_drift_from_helper( # The unpublished dev1 tag is never a trigger, gate or source ref again. assert "0.37.13.dev1" not in release_workflow assert "0.37.13.dev1" not in pypi_workflow + # The published immutable 1.0.8 identity is never a trigger, gate, + # artifact name or qualification version of the 1.0.9 workflows. + assert "signer.1.0.8" not in release_workflow + assert "signer.1.0.8" not in paired_workflow readme = (ROOT / "README.md").read_text(encoding="utf-8") assert "failed prerelease workflow run `34030865864`" in readme assert "No dev1 draft or GitHub release was created" in readme @@ -478,7 +482,8 @@ def test_release_workflows_are_pinned_bounded_and_do_not_drift_from_helper( release_notes = (ROOT / "docs" / "release-notes.md").read_text( encoding="utf-8" ) - assert "## Version 0.37.13+signer.1.0.8 (Preparation Only)" in release_notes + assert "## Version 0.37.13+signer.1.0.9 (Preparation Only)" in release_notes + assert "## Version 0.37.13+signer.1.0.8\n" in release_notes assert "### 0.37.13.dev1 (not released)" in release_notes assert "Dev4 and earlier accepted\n undersized callback outputs" in release_notes assert "breaking strictness\n change" in release_notes @@ -507,10 +512,12 @@ def test_upload_query_preserves_local_version_identifiers(): @pytest.mark.parametrize("event, ref", [ - ("manual", "refs/heads/release/signer-1.0.8"), + ("manual", "refs/heads/release/signer-1.0.9"), ("manual", "refs/heads/main"), - ("manual", "refs/tags/castlabs-v0.37.13+signer.1.0.8"), + ("manual", "refs/tags/castlabs-v0.37.13+signer.1.0.9"), ("tag", "refs/tags/castlabs-v0.37.13.dev2"), + # The published 1.0.8 tag can never satisfy the 1.0.9 release lock. + ("tag", "refs/tags/castlabs-v0.37.13+signer.1.0.8"), ]) def test_aligned_release_does_not_relax_source_ref_policy(monkeypatch, tmp_path, event, ref): monkeypatch.setattr(release, "project_version", lambda root: release.RELEASE_VERSION) @@ -901,7 +908,7 @@ def test_wheel_local_version_filename_metadata_and_dist_info(tmp_path, target, p assert release.wheel_details(wheel, target, "3.10")["file"] == wheel.name with zipfile.ZipFile(wheel) as archive: assert f"c2pa_python-{release.RELEASE_VERSION}.dist-info/METADATA" in archive.namelist() - for version in ("0.37.13", "0.37.13.dev2", "0.37.13_signer.1.0.8"): + for version in ("0.37.13", "0.37.13.dev2", "0.37.13+signer.1.0.8", "0.37.13_signer.1.0.9"): _write_wheel(wheel, target, b"native", dist_info=f"c2pa_python-{version}.dist-info") with pytest.raises(SystemExit, match="unexpected wheel dist-info directory"): release.wheel_details(wheel, target, "3.10") @@ -1380,7 +1387,7 @@ def development_release(monkeypatch): @pytest.mark.parametrize("version", [ - "0.37.13+signer.1.0.8", "0.37.13.dev2+signer.1.0.8", + "0.37.13+signer.1.0.9", "0.37.13.dev2+signer.1.0.9", "0.37.13+signer.1.0.8", "0.37.13", "0.37.13rc1", "0.37.13.dev2junk", ]) def test_pypi_rejects_non_development_versions_before_reading_wheels( diff --git a/tests/test_trusted_vsi_build.py b/tests/test_trusted_vsi_build.py index 32c8e8c0..01af197c 100644 --- a/tests/test_trusted_vsi_build.py +++ b/tests/test_trusted_vsi_build.py @@ -16,7 +16,7 @@ @pytest.mark.parametrize("version", [ - "0.37.13+signer.1.0.8", "0.37.14.dev0", "0.37.14.dev1", "0.38.0.dev1", "1.0.dev0+local", + "0.37.13+signer.1.0.9", "0.37.14.dev0", "0.37.14.dev1", "0.38.0.dev1", "1.0.dev0+local", ]) def test_functional_source_or_future_development_version(version): assert functional_build.functional_version(version) == version @@ -29,15 +29,18 @@ def test_functional_source_or_future_development_version(version): "0.37.9.dev0", "0.37.12.dev0", "0.37.13.dev0", "0.37.13.dev1", # Conformance-line prereleases merged into this line are older too. "0.37.12.dev4", - "0.37.13.dev2", "0.37.13.dev3", "0.37.13", "0.37.13+signer.1.0.9", - "0.37.13+signer.1.0.8.1", "0.37.14+signer.1.0.8", + "0.37.13.dev2", "0.37.13.dev3", "0.37.13", + # The published 1.0.8 identity is historical; other final labels are not + # this checkout's signer-aligned source identity. + "0.37.13+signer.1.0.8", "0.37.13+signer.1.0.10", + "0.37.13+signer.1.0.9.1", "0.37.14+signer.1.0.9", ]) def test_functional_build_rejects_release_or_dev5_identity(version): with pytest.raises(ValueError): functional_build.functional_version(version) -@pytest.mark.parametrize("selected_version", ["0.37.13+signer.1.0.8", "0.37.14.dev0"]) +@pytest.mark.parametrize("selected_version", ["0.37.13+signer.1.0.9", "0.37.14.dev0"]) def test_staged_sdist_uses_new_version_without_touching_dev5_checkout(tmp_path, selected_version): import subprocess import sys @@ -71,13 +74,13 @@ def test_staged_sdist_uses_new_version_without_touching_dev5_checkout(tmp_path, after = {name: (ROOT / name).read_bytes() for name in before} assert after == before # The checkout keeps its own source identity; staging never rewrites it. - assert 'version = "0.37.13+signer.1.0.8"' in before["pyproject.toml"].decode() + assert 'version = "0.37.13+signer.1.0.9"' in before["pyproject.toml"].decode() def test_default_build_version_is_the_source_identity(monkeypatch): monkeypatch.delenv("FUNCTIONAL_BUILD_VERSION", raising=False) - assert functional_build.source_version() == "0.37.13+signer.1.0.8" - assert functional_build.functional_version(functional_build.source_version()) == "0.37.13+signer.1.0.8" + assert functional_build.source_version() == "0.37.13+signer.1.0.9" + assert functional_build.functional_version(functional_build.source_version()) == "0.37.13+signer.1.0.9" @pytest.mark.parametrize("version", ["0.37.13", "0.37.13+vendor.1"])