Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 18 additions & 18 deletions .github/workflows/castlabs-vsi-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -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"
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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: |
Expand Down Expand Up @@ -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: |
Expand All @@ -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
Expand Down Expand Up @@ -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" \
Expand All @@ -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 \
Expand All @@ -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 }}"
Expand All @@ -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)
Expand All @@ -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() {
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/trusted-vsi-paired.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
32 changes: 18 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.

Expand All @@ -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.
Expand All @@ -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.

Expand Down
Loading
Loading