From 53ed86269d44763956bcc3e245befb442b844004 Mon Sep 17 00:00:00 2001 From: Michael Stattmann Date: Thu, 24 Sep 2026 07:15:37 +0200 Subject: [PATCH 1/7] test: native validation time, checked crJSON, merged fixture fixes --- src/c2pa/c2pa.py | 5 +++-- tests/test_unit_tests.py | 27 +++++++++++++++++++-------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/c2pa/c2pa.py b/src/c2pa/c2pa.py index 4801b8cf..150a1f89 100644 --- a/src/c2pa/c2pa.py +++ b/src/c2pa/c2pa.py @@ -3776,8 +3776,9 @@ def detailed_json(self) -> str: def crjson(self) -> str: """Get the manifest store as a crJSON string. - crJSON is a standardized JSON format for C2PA manifest data. This - call yields empty JSON ("{}") when there are no Content Credentials. + crJSON is a standardized JSON format for C2PA manifest data. The + native export is checked: exporter failures raise instead of + returning an empty ``{}`` document. Returns: The manifest store as a crJSON string. diff --git a/tests/test_unit_tests.py b/tests/test_unit_tests.py index 962d12cd..b4595993 100644 --- a/tests/test_unit_tests.py +++ b/tests/test_unit_tests.py @@ -5622,10 +5622,6 @@ def test_link_archive_label_on_signing_builder_placed(self): "data": { "actions": [ _empty_created_action(), - { - "action": "c2pa.created", - "digitalSourceType": "http://c2pa.org/digitalsourcetype/empty", - }, { "action": "c2pa.placed", "parameters": { @@ -5862,10 +5858,6 @@ def test_link_archive_multiple_ingredients_in_one_placed_action(self): "data": { "actions": [ _empty_created_action(), - { - "action": "c2pa.created", - "digitalSourceType": "http://c2pa.org/digitalsourcetype/empty", - }, { "action": "c2pa.placed", "parameters": { @@ -10895,3 +10887,22 @@ def test_ed25519_sign_with_empty_data_raises(self): if __name__ == '__main__': unittest.main(warnings='ignore') + + +class TestConformanceValidationSettings(unittest.TestCase): + """Native caller-controlled validation time and checked crJSON.""" + + def test_invalid_validation_time_is_rejected(self): + with self.assertRaises(Exception): + Context.from_dict({"verify": {"validation_time": "2027-01-15 08:00:00"}}) + + def test_validation_time_is_reported_in_crjson(self): + context = Context.from_dict({ + "verify": {"validation_time": "2027-01-15T10:00:00+02:00"}, + }) + fixture = os.path.join(FIXTURES_DIR, "C.jpg") + with open(fixture, "rb") as stream: + reader = Reader("image/jpeg", stream, context=context) + document = json.loads(reader.crjson()) + results = document["manifests"][0]["validationResults"] + self.assertEqual(results["validationTime"], "2027-01-15T08:00:00Z") From 58b2ce667bc453c3bf0cbeb9d324abb3bbd98660 Mon Sep 17 00:00:00 2001 From: Michael Stattmann Date: Thu, 24 Sep 2026 07:40:47 +0200 Subject: [PATCH 2/7] chore(release): prepare Castlabs conformance candidate 0.37.12.dev1 Pin native castlabs/c2pa-rs a2739fd6 (VSI + contentauth main 6c92bc32 + fMP4/TFRA fixes + validation time/trust purposes), Rust 1.96.0, and release from feat/conformance-validation-v02. --- .github/workflows/castlabs-vsi-pypi.yml | 38 +++++++------- .github/workflows/castlabs-vsi-release.yml | 52 +++++++++---------- docs/release-notes.md | 15 ++++++ release/castlabs-release-evidence.schema.json | 12 ++--- release/castlabs-vsi-inputs.lock.json | 14 ++--- release/castlabs-vsi-inputs.schema.json | 16 +++--- scripts/castlabs_release.py | 25 ++++----- tests/test_castlabs_release_smoke.py | 2 +- tests/test_castlabs_release_tooling.py | 26 +++++----- 9 files changed, 108 insertions(+), 92 deletions(-) diff --git a/.github/workflows/castlabs-vsi-pypi.yml b/.github/workflows/castlabs-vsi-pypi.yml index 99814e93..959cdcb9 100644 --- a/.github/workflows/castlabs-vsi-pypi.yml +++ b/.github/workflows/castlabs-vsi-pypi.yml @@ -6,11 +6,11 @@ on: release_tag: description: Existing immutable Castlabs prerelease tag required: true - default: castlabs-v0.37.8.dev5 + default: castlabs-v0.37.12.dev1 type: string concurrency: - group: castlabs-vsi-pypi-0.37.8.dev5 + group: castlabs-vsi-pypi-0.37.12.dev1 cancel-in-progress: false permissions: @@ -29,7 +29,7 @@ jobs: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: repository: castlabs/c2pa-python - ref: castlabs-v0.37.8.dev5 + ref: castlabs-v0.37.12.dev1 fetch-depth: 0 path: c2pa-python @@ -45,13 +45,13 @@ jobs: GH_TOKEN: ${{ github.token }} INPUT_RELEASE_TAG: ${{ inputs.release_tag }} run: | - test "${INPUT_RELEASE_TAG}" = castlabs-v0.37.8.dev5 - test "$(python3 c2pa-python/scripts/castlabs_release.py lock-value package.version)" = 0.37.8.dev5 - test "$(gh api repos/castlabs/c2pa-python/releases/tags/castlabs-v0.37.8.dev5 --jq '(.draft == false and .prerelease == true)')" = true + test "${INPUT_RELEASE_TAG}" = castlabs-v0.37.12.dev1 + test "$(python3 c2pa-python/scripts/castlabs_release.py lock-value package.version)" = 0.37.12.dev1 + test "$(gh api repos/castlabs/c2pa-python/releases/tags/castlabs-v0.37.12.dev1 --jq '(.draft == false and .prerelease == true)')" = true SOURCE_SHA=$(git -C c2pa-python rev-parse HEAD) - test "$(git -C c2pa-python rev-list -n 1 castlabs-v0.37.8.dev5)" = "$SOURCE_SHA" + test "$(git -C c2pa-python rev-list -n 1 castlabs-v0.37.12.dev1)" = "$SOURCE_SHA" mkdir -p download dist - gh release download castlabs-v0.37.8.dev5 \ + gh release download castlabs-v0.37.12.dev1 \ --repo castlabs/c2pa-python --dir download python3 c2pa-python/scripts/castlabs_release.py validate-release-assets \ --assets-dir download @@ -63,29 +63,29 @@ jobs: --repo castlabs/c2pa-python \ --signer-workflow castlabs/c2pa-python/.github/workflows/castlabs-vsi-release.yml \ --signer-digest "$SOURCE_SHA" \ - --source-ref refs/tags/castlabs-v0.37.8.dev5 \ + --source-ref refs/tags/castlabs-v0.37.12.dev1 \ --source-digest "$SOURCE_SHA" \ --deny-self-hosted-runners done python3 c2pa-python/scripts/castlabs_release.py validate-evidence \ - --evidence download/c2pa-python-0.37.8.dev5-native-linux-x86_64.tar.gz.evidence.json \ + --evidence download/c2pa-python-0.37.12.dev1-native-linux-x86_64.tar.gz.evidence.json \ --artifact-dir download --source-sha "$SOURCE_SHA" python3 c2pa-python/scripts/castlabs_release.py validate-evidence \ - --evidence download/c2pa-python-0.37.8.dev5-native-windows-x86_64.tar.gz.evidence.json \ + --evidence download/c2pa-python-0.37.12.dev1-native-windows-x86_64.tar.gz.evidence.json \ --artifact-dir download --source-sha "$SOURCE_SHA" python3 c2pa-python/scripts/castlabs_release.py validate-evidence \ - --evidence download/c2pa-python-0.37.8.dev5-py3-none-wheels.tar.gz.evidence.json \ + --evidence download/c2pa-python-0.37.12.dev1-py3-none-wheels.tar.gz.evidence.json \ --artifact-dir download --source-sha "$SOURCE_SHA" python3 c2pa-python/scripts/castlabs_release.py extract \ - --archive download/c2pa-python-0.37.8.dev5-py3-none-wheels.tar.gz \ + --archive download/c2pa-python-0.37.12.dev1-py3-none-wheels.tar.gz \ --destination dist test "$(find dist -maxdepth 1 -name '*.whl' | wc -l)" -eq 2 - cmp dist/c2pa_python-0.37.8.dev5-py3-none-manylinux_2_28_x86_64.whl \ - download/c2pa_python-0.37.8.dev5-py3-none-manylinux_2_28_x86_64.whl - cmp dist/c2pa_python-0.37.8.dev5-py3-none-win_amd64.whl \ - download/c2pa_python-0.37.8.dev5-py3-none-win_amd64.whl + cmp dist/c2pa_python-0.37.12.dev1-py3-none-manylinux_2_28_x86_64.whl \ + download/c2pa_python-0.37.12.dev1-py3-none-manylinux_2_28_x86_64.whl + cmp dist/c2pa_python-0.37.12.dev1-py3-none-win_amd64.whl \ + download/c2pa_python-0.37.12.dev1-py3-none-win_amd64.whl python3 -m pip install packaging==25.0 twine==6.2.0 - python3 -c 'from packaging.version import Version; v=Version("0.37.8.dev5"); assert v.is_prerelease and str(v)=="0.37.8.dev5"' + python3 -c 'from packaging.version import Version; v=Version("0.37.12.dev1"); assert v.is_prerelease and str(v)=="0.37.12.dev1"' python3 -m twine check dist/*.whl - name: Preflight retry-safe PyPI upload set @@ -94,7 +94,7 @@ jobs: run: | STATUS=$(curl --silent --show-error --location \ --output pypi-response.json --write-out '%{http_code}' \ - https://pypi.org/pypi/c2pa-python/0.37.8.dev5/json) + https://pypi.org/pypi/c2pa-python/0.37.12.dev1/json) case "$STATUS" in 200|404) ;; *) echo "unexpected PyPI JSON API status: $STATUS" >&2; exit 1 ;; diff --git a/.github/workflows/castlabs-vsi-release.yml b/.github/workflows/castlabs-vsi-release.yml index 5dd4c696..40d2e9c1 100644 --- a/.github/workflows/castlabs-vsi-release.yml +++ b/.github/workflows/castlabs-vsi-release.yml @@ -3,16 +3,16 @@ name: Castlabs VSI prerelease on: push: tags: - - castlabs-v0.37.8.dev5 + - castlabs-v0.37.12.dev1 workflow_dispatch: inputs: source_sha: - description: Full feat/live-video-vsi branch-tip SHA to release + description: Full feat/conformance-validation-v02 branch-tip SHA to release required: true type: string concurrency: - group: castlabs-vsi-release-0.37.8.dev5 + group: castlabs-vsi-release-0.37.12.dev1 cancel-in-progress: false permissions: @@ -60,7 +60,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: repository: castlabs/c2pa-rs - ref: 9b80687f386f2b373fd1ac9c705d8e61a07b608e + ref: a2739fd66092c410773bdc6881474ec34fb8ea16 fetch-depth: 1 path: c2pa-rs @@ -68,7 +68,7 @@ jobs: shell: bash run: | git -C c2pa-python fetch --no-tags origin \ - refs/heads/feat/live-video-vsi:refs/remotes/origin/feat/live-video-vsi + refs/heads/feat/conformance-validation-v02:refs/remotes/origin/feat/conformance-validation-v02 EVENT=manual if [[ "${GITHUB_REF}" == refs/tags/* ]]; then EVENT=tag; fi python3 c2pa-python/scripts/castlabs_release.py validate-sources \ @@ -111,7 +111,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: repository: castlabs/c2pa-rs - ref: 9b80687f386f2b373fd1ac9c705d8e61a07b608e + ref: a2739fd66092c410773bdc6881474ec34fb8ea16 fetch-depth: 1 path: c2pa-rs @@ -119,7 +119,7 @@ jobs: shell: bash run: | git -C c2pa-python fetch --no-tags origin \ - refs/heads/feat/live-video-vsi:refs/remotes/origin/feat/live-video-vsi + refs/heads/feat/conformance-validation-v02:refs/remotes/origin/feat/conformance-validation-v02 EVENT=manual if [[ "${GITHUB_REF}" == refs/tags/* ]]; then EVENT=tag; fi python3 c2pa-python/scripts/castlabs_release.py validate-sources \ @@ -183,7 +183,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.8.dev5-py3-none-manylinux_2_28_x86_64.whl) ;; + c2pa_python-0.37.12.dev1-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" @@ -203,7 +203,7 @@ jobs: SOURCE_DATE_EPOCH: ${{ needs.prepare.outputs.source_epoch }} run: | cd c2pa-python - BUNDLE=release-out/c2pa-python-0.37.8.dev5-native-linux-x86_64.tar.gz + BUNDLE=release-out/c2pa-python-0.37.12.dev1-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 @@ -266,7 +266,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: repository: castlabs/c2pa-rs - ref: 9b80687f386f2b373fd1ac9c705d8e61a07b608e + ref: a2739fd66092c410773bdc6881474ec34fb8ea16 fetch-depth: 1 path: c2pa-rs @@ -290,7 +290,7 @@ jobs: throw "checked-out Cargo.lock SHA-256 changed" } git -C c2pa-python fetch --no-tags origin ` - refs/heads/feat/live-video-vsi:refs/remotes/origin/feat/live-video-vsi + refs/heads/feat/conformance-validation-v02:refs/remotes/origin/feat/conformance-validation-v02 $event = if ($env:GITHUB_REF.StartsWith("refs/tags/")) { "tag" } else { "manual" } python c2pa-python/scripts/castlabs_release.py validate-sources ` --python-root c2pa-python --rust-root c2pa-rs ` @@ -339,7 +339,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.8.dev5-py3-none-win_amd64.whl") { + if ($wheels[0].Name -ne "c2pa_python-0.37.12.dev1-py3-none-win_amd64.whl") { throw "unexpected Windows wheel $($wheels[0].Name)" } python -m twine check $wheels[0].FullName @@ -359,7 +359,7 @@ jobs: $ErrorActionPreference = "Stop" $PSNativeCommandUseErrorActionPreference = $true Push-Location c2pa-python - $bundle = "release-out/c2pa-python-0.37.8.dev5-native-windows-x86_64.tar.gz" + $bundle = "release-out/c2pa-python-0.37.12.dev1-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 @@ -412,9 +412,9 @@ jobs: - name: Test installed wheel in a clean environment shell: bash env: - CASTLABS_RELEASE_EXPECTED_VERSION: 0.37.8.dev5 + CASTLABS_RELEASE_EXPECTED_VERSION: 0.37.12.dev1 CASTLABS_RELEASE_SMOKE_REQUIRED: "1" - C2PA_SOURCE_BUILD_VERSION: 0.91.0-dev + C2PA_SOURCE_BUILD_VERSION: 0.92.0-dev run: | test "$(find release-out -maxdepth 1 -name '*.whl' | wc -l)" -eq 1 python -m venv "${RUNNER_TEMP}/wheel-test" @@ -452,9 +452,9 @@ jobs: - name: Test installed wheel in a clean environment shell: pwsh env: - CASTLABS_RELEASE_EXPECTED_VERSION: 0.37.8.dev5 + CASTLABS_RELEASE_EXPECTED_VERSION: 0.37.12.dev1 CASTLABS_RELEASE_SMOKE_REQUIRED: "1" - C2PA_SOURCE_BUILD_VERSION: 0.91.0-dev + C2PA_SOURCE_BUILD_VERSION: 0.92.0-dev run: | $ErrorActionPreference = "Stop" $PSNativeCommandUseErrorActionPreference = $true @@ -475,7 +475,7 @@ jobs: release: needs: [prepare, linux, windows, test-linux-wheel, test-windows-wheel] - if: github.ref == 'refs/tags/castlabs-v0.37.8.dev5' + if: github.ref == 'refs/tags/castlabs-v0.37.12.dev1' runs-on: ubuntu-24.04 timeout-minutes: 30 environment: castlabs-vsi-release @@ -510,7 +510,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.8.dev5-py3-none-wheels.tar.gz + BUNDLE=assets/c2pa-python-0.37.12.dev1-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" \ @@ -527,13 +527,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.8.dev5-native-linux-x86_64.tar.gz* assets/ - cp collected/windows/c2pa-python-0.37.8.dev5-native-windows-x86_64.tar.gz* assets/ + cp collected/linux/c2pa-python-0.37.12.dev1-native-linux-x86_64.tar.gz* assets/ + cp collected/windows/c2pa-python-0.37.12.dev1-native-windows-x86_64.tar.gz* assets/ python3 c2pa-python/scripts/castlabs_release.py validate-evidence \ - --evidence assets/c2pa-python-0.37.8.dev5-native-linux-x86_64.tar.gz.evidence.json \ + --evidence assets/c2pa-python-0.37.12.dev1-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.8.dev5-native-windows-x86_64.tar.gz.evidence.json \ + --evidence assets/c2pa-python-0.37.12.dev1-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 \ @@ -547,7 +547,7 @@ jobs: - name: Require immutable release tag shell: bash run: | - TAG=castlabs-v0.37.8.dev5 + TAG=castlabs-v0.37.12.dev1 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 }}" @@ -563,7 +563,7 @@ jobs: GH_TOKEN: ${{ github.token }} run: | set -euo pipefail - TAG=castlabs-v0.37.8.dev5 + TAG=castlabs-v0.37.12.dev1 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) @@ -589,7 +589,7 @@ jobs: } discover_release() { gh api --paginate repos/castlabs/c2pa-python/releases \ - --jq '.[] | select(.tag_name == "castlabs-v0.37.8.dev5")' > "$1" + --jq '.[] | select(.tag_name == "castlabs-v0.37.12.dev1")' > "$1" return 0 } query_release_id() { diff --git a/docs/release-notes.md b/docs/release-notes.md index 06f799e7..bffacf54 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,5 +1,20 @@ # Release notes +## Version 0.37.12.dev1 + +Castlabs Conformance Program 0.2 / C2PA 2.4 candidate prerelease. + +- Native `castlabs/c2pa-rs` `a2739fd66092c410773bdc6881474ec34fb8ea16`: the + live-video VSI line merged with contentauth main `6c92bc32` (C2PA 2.4 + validation), plus the single-file fMP4 Merkle and TFRA fixes. +- `verify.validation_time` (RFC 3339) selects the evaluation instant for + certificate, chain and OCSP decisions and the reported `validationTime`. +- Purpose-scoped trust anchors (`trust.anchors` with `trust_kind` + `manifest`/`tsa`/`cawg`) and `verify.strict_trust_purposes`. +- `Reader.crjson()` is checked: exporter failures raise instead of `{}`. +- 2.4 `specVersion` in `claim_generator_info` serializes correctly, and the + default EKU allow-list survives context trust settings. + ## Version 0.37.8.dev5 ### Breaking changes diff --git a/release/castlabs-release-evidence.schema.json b/release/castlabs-release-evidence.schema.json index b9703c71..56a3df05 100644 --- a/release/castlabs-release-evidence.schema.json +++ b/release/castlabs-release-evidence.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/castlabs/c2pa-python/blob/feat/live-video-vsi/release/castlabs-release-evidence.schema.json", + "$id": "https://github.com/castlabs/c2pa-python/blob/feat/conformance-validation-v02/release/castlabs-release-evidence.schema.json", "title": "Castlabs c2pa-python release evidence", "type": "object", "additionalProperties": false, @@ -13,7 +13,7 @@ "properties": { "type": {"enum": ["native-bundle", "wheel-bundle"]}, "package": {"const": "c2pa-python"}, - "version": {"const": "0.37.8.dev5"}, + "version": {"const": "0.37.12.dev1"}, "file": {"type": "string", "minLength": 1}, "size": {"type": "integer", "minimum": 1}, "sha256": {"$ref": "#/$defs/sha256"}, @@ -49,9 +49,9 @@ "properties": { "repository": {"const": "castlabs/c2pa-rs"}, "url": {"const": "https://github.com/castlabs/c2pa-rs.git"}, - "commit": {"const": "9b80687f386f2b373fd1ac9c705d8e61a07b608e"}, - "version": {"const": "0.91.0-dev"}, - "cargoLockSha256": {"const": "c4554b8fd3a1d3a1dc00482546750a40b06d0e5a86c9e66f4830c63fbd0df70f"} + "commit": {"const": "a2739fd66092c410773bdc6881474ec34fb8ea16"}, + "version": {"const": "0.92.0-dev"}, + "cargoLockSha256": {"const": "e074f31ad8c7d4d4278967bb0aaf12bde0013a5f9cb7517cc7af462dbed962e2"} } } } @@ -90,7 +90,7 @@ "type": "object", "additionalProperties": false, "required": ["target", "rustToolchain", "cargoCommand", "cargoTreeCommand", "noDefaultFeatures", "features", "resolvedFeatureReportSha256", "sourceDateEpoch", "cargoBuildJobs", "cargoIncremental", "pythonHashSeed", "container"], "properties": { - "target": {"enum": ["x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc"]}, "rustToolchain": {"const": "1.88.0"}, + "target": {"enum": ["x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc"]}, "rustToolchain": {"const": "1.96.0"}, "cargoCommand": {"type": "array", "minItems": 12, "items": {"type": "string"}}, "noDefaultFeatures": {"const": true}, "cargoTreeCommand": {"type": "array", "minItems": 16, "items": {"type": "string"}}, "features": {"const": ["rust_native_crypto", "http", "add_thumbnails", "file_io", "unstable_live_video"]}, "resolvedFeatureReportSha256": {"$ref": "#/$defs/sha256"}, diff --git a/release/castlabs-vsi-inputs.lock.json b/release/castlabs-vsi-inputs.lock.json index c5e082f2..3f1d72c8 100644 --- a/release/castlabs-vsi-inputs.lock.json +++ b/release/castlabs-vsi-inputs.lock.json @@ -2,23 +2,23 @@ "schemaVersion": 1, "package": { "name": "c2pa-python", - "version": "0.37.8.dev5" + "version": "0.37.12.dev1" }, "pythonSource": { "repository": "castlabs/c2pa-python", "url": "https://github.com/castlabs/c2pa-python.git", - "releaseBranch": "feat/live-video-vsi", - "releaseTag": "castlabs-v0.37.8.dev5" + "releaseBranch": "feat/conformance-validation-v02", + "releaseTag": "castlabs-v0.37.12.dev1" }, "rustSource": { "repository": "castlabs/c2pa-rs", "url": "https://github.com/castlabs/c2pa-rs.git", - "commit": "9b80687f386f2b373fd1ac9c705d8e61a07b608e", - "version": "0.91.0-dev", - "cargoLockSha256": "c4554b8fd3a1d3a1dc00482546750a40b06d0e5a86c9e66f4830c63fbd0df70f" + "commit": "a2739fd66092c410773bdc6881474ec34fb8ea16", + "version": "0.92.0-dev", + "cargoLockSha256": "e074f31ad8c7d4d4278967bb0aaf12bde0013a5f9cb7517cc7af462dbed962e2" }, "rustToolchain": { - "channel": "1.88.0", + "channel": "1.96.0", "rustupVersion": "1.28.2", "installers": { "x86_64-unknown-linux-gnu": { diff --git a/release/castlabs-vsi-inputs.schema.json b/release/castlabs-vsi-inputs.schema.json index 7fdb3df7..77aeef19 100644 --- a/release/castlabs-vsi-inputs.schema.json +++ b/release/castlabs-vsi-inputs.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/castlabs/c2pa-python/blob/feat/live-video-vsi/release/castlabs-vsi-inputs.schema.json", + "$id": "https://github.com/castlabs/c2pa-python/blob/feat/conformance-validation-v02/release/castlabs-vsi-inputs.schema.json", "title": "Castlabs VSI c2pa-python release input lock", "type": "object", "additionalProperties": false, @@ -30,7 +30,7 @@ "package": { "type": "object", "additionalProperties": false, "required": ["name", "version"], - "properties": {"name": {"const": "c2pa-python"}, "version": {"const": "0.37.8.dev5"}} + "properties": {"name": {"const": "c2pa-python"}, "version": {"const": "0.37.12.dev1"}} }, "pythonSource": { "type": "object", "additionalProperties": false, @@ -38,8 +38,8 @@ "properties": { "repository": {"const": "castlabs/c2pa-python"}, "url": {"const": "https://github.com/castlabs/c2pa-python.git"}, - "releaseBranch": {"const": "feat/live-video-vsi"}, - "releaseTag": {"const": "castlabs-v0.37.8.dev5"} + "releaseBranch": {"const": "feat/conformance-validation-v02"}, + "releaseTag": {"const": "castlabs-v0.37.12.dev1"} } }, "rustSource": { @@ -48,9 +48,9 @@ "properties": { "repository": {"const": "castlabs/c2pa-rs"}, "url": {"const": "https://github.com/castlabs/c2pa-rs.git"}, - "commit": {"const": "9b80687f386f2b373fd1ac9c705d8e61a07b608e"}, - "version": {"const": "0.91.0-dev"}, - "cargoLockSha256": {"const": "c4554b8fd3a1d3a1dc00482546750a40b06d0e5a86c9e66f4830c63fbd0df70f"} + "commit": {"const": "a2739fd66092c410773bdc6881474ec34fb8ea16"}, + "version": {"const": "0.92.0-dev"}, + "cargoLockSha256": {"const": "e074f31ad8c7d4d4278967bb0aaf12bde0013a5f9cb7517cc7af462dbed962e2"} } }, "installer": { @@ -62,7 +62,7 @@ "type": "object", "additionalProperties": false, "required": ["channel", "rustupVersion", "installers"], "properties": { - "channel": {"const": "1.88.0"}, + "channel": {"const": "1.96.0"}, "rustupVersion": {"const": "1.28.2"}, "installers": { "type": "object", "additionalProperties": false, diff --git a/scripts/castlabs_release.py b/scripts/castlabs_release.py index a280922f..236c2872 100644 --- a/scripts/castlabs_release.py +++ b/scripts/castlabs_release.py @@ -26,23 +26,24 @@ 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.8.dev5" -RUST_COMMIT = "9b80687f386f2b373fd1ac9c705d8e61a07b608e" -CARGO_LOCK_SHA256 = "c4554b8fd3a1d3a1dc00482546750a40b06d0e5a86c9e66f4830c63fbd0df70f" -RUST_TOOLCHAIN = "1.88.0" +RELEASE_VERSION = "0.37.12.dev1" +RUST_COMMIT = "a2739fd66092c410773bdc6881474ec34fb8ea16" +CARGO_LOCK_SHA256 = "e074f31ad8c7d4d4278967bb0aaf12bde0013a5f9cb7517cc7af462dbed962e2" +RUST_TOOLCHAIN = "1.96.0" MANYLINUX_IMAGE = "quay.io/pypa/manylinux_2_28_x86_64" MANYLINUX_DIGEST = ( "sha256:0d9c2a66a745961947a8cecbe217ca0a7ee7a5849ba2517f20f9581d18444977" ) -RELEASE_TAG = "castlabs-v0.37.8.dev5" +RELEASE_TAG = "castlabs-v0.37.12.dev1" RELEASE_NAME = f"Castlabs c2pa-python {RELEASE_VERSION} (VSI)" RELEASE_BODY_MARKER = f"castlabs-vsi-release:{RELEASE_VERSION}" RELEASE_IDENTITY_TEXT = f"Castlabs c2pa-python {RELEASE_VERSION} immutable prerelease" RELEASE_BODY = ( f"{RELEASE_BODY_MARKER}\n\n" - f"{RELEASE_IDENTITY_TEXT}. Successor to immutable dev4; enforces exact " - "DynamicAssertion callback sizing for every signing path. See the attached " - "schema-2 evidence and SHA-256 sidecars." + f"{RELEASE_IDENTITY_TEXT}. Conformance Program 0.2 / C2PA 2.4 candidate: " + "live-video VSI merged with contentauth main 6c92bc32, single-file fMP4 " + "Merkle and TFRA fixes, caller-controlled validation time and purpose-scoped " + "trust. See the attached schema-2 evidence and SHA-256 sidecars." ) @@ -218,12 +219,12 @@ def validate_lock(lock: dict[str, Any]) -> None: if set(lock) != required or lock.get("schemaVersion") != 1: fail("release lock does not match schema version 1") if lock["package"] != {"name": "c2pa-python", "version": RELEASE_VERSION}: - fail("release lock package identity is not 0.37.8.dev5") + fail("release lock package identity is not 0.37.12.dev1") if lock["pythonSource"] != { "repository": "castlabs/c2pa-python", "url": "https://github.com/castlabs/c2pa-python.git", - "releaseBranch": "feat/live-video-vsi", - "releaseTag": "castlabs-v0.37.8.dev5", + "releaseBranch": "feat/conformance-validation-v02", + "releaseTag": "castlabs-v0.37.12.dev1", }: fail("unexpected c2pa-python release source policy") rust = lock["rustSource"] @@ -231,7 +232,7 @@ def validate_lock(lock: dict[str, Any]) -> None: "repository": "castlabs/c2pa-rs", "url": "https://github.com/castlabs/c2pa-rs.git", "commit": RUST_COMMIT, - "version": "0.91.0-dev", + "version": "0.92.0-dev", "cargoLockSha256": CARGO_LOCK_SHA256, }: fail("unexpected c2pa-rs release source policy") diff --git a/tests/test_castlabs_release_smoke.py b/tests/test_castlabs_release_smoke.py index 0058218b..9d9714c7 100644 --- a/tests/test_castlabs_release_smoke.py +++ b/tests/test_castlabs_release_smoke.py @@ -45,7 +45,7 @@ FIXTURES = Path(__file__).parent / "fixtures" -EXPECTED_VERSION = os.environ.get("CASTLABS_RELEASE_EXPECTED_VERSION", "0.37.8.dev5") +EXPECTED_VERSION = os.environ.get("CASTLABS_RELEASE_EXPECTED_VERSION", "0.37.12.dev1") REQUIRED_CAPABILITIES = { "dynamic assertions": has_dynamic_assertions, "fragmented files": has_fragmented_files, diff --git a/tests/test_castlabs_release_tooling.py b/tests/test_castlabs_release_tooling.py index 43f11fc6..0d0f965c 100644 --- a/tests/test_castlabs_release_tooling.py +++ b/tests/test_castlabs_release_tooling.py @@ -29,11 +29,11 @@ def test_prerelease_version_is_consistent(): - assert release.project_version(ROOT) == "0.37.8.dev5" + assert release.project_version(ROOT) == "0.37.12.dev1" first_line = ( (ROOT / "src" / "c2pa" / "c2pa.py").read_text(encoding="utf-8").splitlines()[13] ) - assert first_line == "# Version: 0.37.8.dev5" + assert first_line == "# Version: 0.37.12.dev1" def test_release_lock_and_schemas_are_valid_json(): @@ -41,7 +41,7 @@ def test_release_lock_and_schemas_are_valid_json(): release.validate_lock(lock) assert lock["rustSource"]["commit"] == release.RUST_COMMIT assert lock["rustSource"]["cargoLockSha256"] == release.CARGO_LOCK_SHA256 - assert lock["rustToolchain"]["channel"] == "1.88.0" + assert lock["rustToolchain"]["channel"] == "1.96.0" for name in ( "castlabs-vsi-inputs.schema.json", "castlabs-release-evidence.schema.json", @@ -113,7 +113,7 @@ def test_release_workflows_are_pinned_bounded_and_do_not_drift_from_helper( in release_workflow ) assert ( - "c2pa_python-0.37.8.dev5-py3-none-manylinux_2_28_x86_64.whl" in release_workflow + "c2pa_python-0.37.12.dev1-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") < ( @@ -164,7 +164,7 @@ def test_release_workflows_are_pinned_bounded_and_do_not_drift_from_helper( "python3 -m pytest -q c2pa-python/tests/test_castlabs_release_tooling.py" in (release_workflow) ) - assert "if: github.ref == 'refs/tags/castlabs-v0.37.8.dev5'" in release_workflow + assert "if: github.ref == 'refs/tags/castlabs-v0.37.12.dev1'" in release_workflow assert "-F draft=true -F prerelease=true" in release_workflow assert "--clobber" not in release_workflow assert "repos/castlabs/c2pa-python/releases" in release_workflow @@ -238,9 +238,9 @@ def test_release_workflows_are_pinned_bounded_and_do_not_drift_from_helper( in pypi_workflow ) assert '--signer-digest "$SOURCE_SHA"' in pypi_workflow - assert "--source-ref refs/tags/castlabs-v0.37.8.dev5" in pypi_workflow + assert "--source-ref refs/tags/castlabs-v0.37.12.dev1" in pypi_workflow assert '--source-digest "$SOURCE_SHA"' in pypi_workflow - assert "pypi.org/pypi/c2pa-python/0.37.8.dev5/json" in pypi_workflow + assert "pypi.org/pypi/c2pa-python/0.37.12.dev1/json" in pypi_workflow assert "pypi-plan" in pypi_workflow assert "packages-dir: publish-dist/" in pypi_workflow assert "if: steps.pypi.outputs.upload == 'true'" in pypi_workflow @@ -269,7 +269,7 @@ def test_release_workflows_are_pinned_bounded_and_do_not_drift_from_helper( assert "manual legacy publishing accepts final X.Y.Z versions only" in ( legacy_release ) - assert 'test "$VERSION" != 0.37.8.dev5' not in legacy_workflow + assert 'test "$VERSION" != 0.37.12.dev1' not in legacy_workflow assert legacy_workflow.count("final X.Y.Z versions only") == 2 assert "tests/test_castlabs_release_tooling.py" in legacy_workflow shell_helpers_start = release_workflow.index(" download_existing() {") @@ -299,7 +299,7 @@ def test_release_workflows_are_pinned_bounded_and_do_not_drift_from_helper( assert smoke_gate in smoke assert smoke.index(smoke_gate) < smoke.index("from c2pa import") assert release_workflow.count('CASTLABS_RELEASE_SMOKE_REQUIRED: "1"') == 2 - assert release_workflow.count("CASTLABS_RELEASE_EXPECTED_VERSION: 0.37.8.dev5") == 2 + assert release_workflow.count("CASTLABS_RELEASE_EXPECTED_VERSION: 0.37.12.dev1") == 2 assert "pytest.skip(" in smoke assert "unittest.skip" not in smoke dynamic_start = smoke.index( @@ -362,7 +362,7 @@ 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.8.dev5" in release_notes + assert "## Version 0.37.12.dev1" in release_notes assert "Dev4 and earlier accepted\n undersized callback outputs" in release_notes assert "breaking strictness\n change" in release_notes @@ -557,7 +557,7 @@ def test_safe_extract_round_trip_and_rejects_links(): def test_wheel_inspection_rejects_unsafe_and_multiple_native_members(): with tempfile.TemporaryDirectory() as temp: root = Path(temp) - wheel = root / "c2pa_python-0.37.8.dev5-py3-none-win_amd64.whl" + wheel = root / "c2pa_python-0.37.12.dev1-py3-none-win_amd64.whl" with zipfile.ZipFile(wheel, "w") as archive: archive.writestr("c2pa/libs/first.dll", b"one") archive.writestr("c2pa/libs/second.dll", b"two") @@ -568,7 +568,7 @@ def test_wheel_inspection_rejects_unsafe_and_multiple_native_members(): else: raise AssertionError("accepted wheel with multiple native libraries") - unsafe = root / "c2pa_python-0.37.8.dev5-py3-none-manylinux_2_28_x86_64.whl" + unsafe = root / "c2pa_python-0.37.12.dev1-py3-none-manylinux_2_28_x86_64.whl" with zipfile.ZipFile(unsafe, "w") as archive: archive.writestr("../libc2pa_c.so", b"unsafe") try: @@ -996,7 +996,7 @@ def test_release_response_validators_cover_owned_empty_and_complete_states(tmp_p ) == release_id ) - for allowed_target in ("main", "feat/live-video-vsi", TEST_SOURCE_SHA): + for allowed_target in ("main", "feat/conformance-validation-v02", TEST_SOURCE_SHA): target_response = copy.deepcopy(create_response) target_response["target_commitish"] = allowed_target assert ( From d813dc8646c4814c7c6338fb5562e3f9eb945f2b Mon Sep 17 00:00:00 2001 From: Michael Stattmann Date: Thu, 24 Sep 2026 14:07:02 +0200 Subject: [PATCH 3/7] chore(release): prepare 0.37.12.dev2 after failed dev1 installed-wheel tests Install pytest explicitly in the installed-wheel test environments and pin native castlabs/c2pa-rs 6ca7fb16. --- .github/workflows/castlabs-vsi-pypi.yml | 38 ++++++++-------- .github/workflows/castlabs-vsi-release.yml | 43 ++++++++++--------- docs/release-notes.md | 10 +++-- pyproject.toml | 2 +- release/castlabs-release-evidence.schema.json | 4 +- release/castlabs-vsi-inputs.lock.json | 6 +-- release/castlabs-vsi-inputs.schema.json | 6 +-- scripts/castlabs_release.py | 12 +++--- src/c2pa/c2pa.py | 2 +- tests/test_castlabs_release_smoke.py | 2 +- tests/test_castlabs_release_tooling.py | 22 +++++----- 11 files changed, 77 insertions(+), 70 deletions(-) diff --git a/.github/workflows/castlabs-vsi-pypi.yml b/.github/workflows/castlabs-vsi-pypi.yml index 959cdcb9..1d8b4c93 100644 --- a/.github/workflows/castlabs-vsi-pypi.yml +++ b/.github/workflows/castlabs-vsi-pypi.yml @@ -6,11 +6,11 @@ on: release_tag: description: Existing immutable Castlabs prerelease tag required: true - default: castlabs-v0.37.12.dev1 + default: castlabs-v0.37.12.dev2 type: string concurrency: - group: castlabs-vsi-pypi-0.37.12.dev1 + group: castlabs-vsi-pypi-0.37.12.dev2 cancel-in-progress: false permissions: @@ -29,7 +29,7 @@ jobs: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: repository: castlabs/c2pa-python - ref: castlabs-v0.37.12.dev1 + ref: castlabs-v0.37.12.dev2 fetch-depth: 0 path: c2pa-python @@ -45,13 +45,13 @@ jobs: GH_TOKEN: ${{ github.token }} INPUT_RELEASE_TAG: ${{ inputs.release_tag }} run: | - test "${INPUT_RELEASE_TAG}" = castlabs-v0.37.12.dev1 - test "$(python3 c2pa-python/scripts/castlabs_release.py lock-value package.version)" = 0.37.12.dev1 - test "$(gh api repos/castlabs/c2pa-python/releases/tags/castlabs-v0.37.12.dev1 --jq '(.draft == false and .prerelease == true)')" = true + test "${INPUT_RELEASE_TAG}" = castlabs-v0.37.12.dev2 + test "$(python3 c2pa-python/scripts/castlabs_release.py lock-value package.version)" = 0.37.12.dev2 + test "$(gh api repos/castlabs/c2pa-python/releases/tags/castlabs-v0.37.12.dev2 --jq '(.draft == false and .prerelease == true)')" = true SOURCE_SHA=$(git -C c2pa-python rev-parse HEAD) - test "$(git -C c2pa-python rev-list -n 1 castlabs-v0.37.12.dev1)" = "$SOURCE_SHA" + test "$(git -C c2pa-python rev-list -n 1 castlabs-v0.37.12.dev2)" = "$SOURCE_SHA" mkdir -p download dist - gh release download castlabs-v0.37.12.dev1 \ + gh release download castlabs-v0.37.12.dev2 \ --repo castlabs/c2pa-python --dir download python3 c2pa-python/scripts/castlabs_release.py validate-release-assets \ --assets-dir download @@ -63,29 +63,29 @@ jobs: --repo castlabs/c2pa-python \ --signer-workflow castlabs/c2pa-python/.github/workflows/castlabs-vsi-release.yml \ --signer-digest "$SOURCE_SHA" \ - --source-ref refs/tags/castlabs-v0.37.12.dev1 \ + --source-ref refs/tags/castlabs-v0.37.12.dev2 \ --source-digest "$SOURCE_SHA" \ --deny-self-hosted-runners done python3 c2pa-python/scripts/castlabs_release.py validate-evidence \ - --evidence download/c2pa-python-0.37.12.dev1-native-linux-x86_64.tar.gz.evidence.json \ + --evidence download/c2pa-python-0.37.12.dev2-native-linux-x86_64.tar.gz.evidence.json \ --artifact-dir download --source-sha "$SOURCE_SHA" python3 c2pa-python/scripts/castlabs_release.py validate-evidence \ - --evidence download/c2pa-python-0.37.12.dev1-native-windows-x86_64.tar.gz.evidence.json \ + --evidence download/c2pa-python-0.37.12.dev2-native-windows-x86_64.tar.gz.evidence.json \ --artifact-dir download --source-sha "$SOURCE_SHA" python3 c2pa-python/scripts/castlabs_release.py validate-evidence \ - --evidence download/c2pa-python-0.37.12.dev1-py3-none-wheels.tar.gz.evidence.json \ + --evidence download/c2pa-python-0.37.12.dev2-py3-none-wheels.tar.gz.evidence.json \ --artifact-dir download --source-sha "$SOURCE_SHA" python3 c2pa-python/scripts/castlabs_release.py extract \ - --archive download/c2pa-python-0.37.12.dev1-py3-none-wheels.tar.gz \ + --archive download/c2pa-python-0.37.12.dev2-py3-none-wheels.tar.gz \ --destination dist test "$(find dist -maxdepth 1 -name '*.whl' | wc -l)" -eq 2 - cmp dist/c2pa_python-0.37.12.dev1-py3-none-manylinux_2_28_x86_64.whl \ - download/c2pa_python-0.37.12.dev1-py3-none-manylinux_2_28_x86_64.whl - cmp dist/c2pa_python-0.37.12.dev1-py3-none-win_amd64.whl \ - download/c2pa_python-0.37.12.dev1-py3-none-win_amd64.whl + cmp dist/c2pa_python-0.37.12.dev2-py3-none-manylinux_2_28_x86_64.whl \ + download/c2pa_python-0.37.12.dev2-py3-none-manylinux_2_28_x86_64.whl + cmp dist/c2pa_python-0.37.12.dev2-py3-none-win_amd64.whl \ + download/c2pa_python-0.37.12.dev2-py3-none-win_amd64.whl python3 -m pip install packaging==25.0 twine==6.2.0 - python3 -c 'from packaging.version import Version; v=Version("0.37.12.dev1"); assert v.is_prerelease and str(v)=="0.37.12.dev1"' + python3 -c 'from packaging.version import Version; v=Version("0.37.12.dev2"); assert v.is_prerelease and str(v)=="0.37.12.dev2"' python3 -m twine check dist/*.whl - name: Preflight retry-safe PyPI upload set @@ -94,7 +94,7 @@ jobs: run: | STATUS=$(curl --silent --show-error --location \ --output pypi-response.json --write-out '%{http_code}' \ - https://pypi.org/pypi/c2pa-python/0.37.12.dev1/json) + https://pypi.org/pypi/c2pa-python/0.37.12.dev2/json) case "$STATUS" in 200|404) ;; *) echo "unexpected PyPI JSON API status: $STATUS" >&2; exit 1 ;; diff --git a/.github/workflows/castlabs-vsi-release.yml b/.github/workflows/castlabs-vsi-release.yml index 40d2e9c1..86c0d405 100644 --- a/.github/workflows/castlabs-vsi-release.yml +++ b/.github/workflows/castlabs-vsi-release.yml @@ -3,7 +3,7 @@ name: Castlabs VSI prerelease on: push: tags: - - castlabs-v0.37.12.dev1 + - castlabs-v0.37.12.dev2 workflow_dispatch: inputs: source_sha: @@ -12,7 +12,7 @@ on: type: string concurrency: - group: castlabs-vsi-release-0.37.12.dev1 + group: castlabs-vsi-release-0.37.12.dev2 cancel-in-progress: false permissions: @@ -60,7 +60,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: repository: castlabs/c2pa-rs - ref: a2739fd66092c410773bdc6881474ec34fb8ea16 + ref: 6ca7fb168b10bde7a1ef8bb8e577ffbae73233c7 fetch-depth: 1 path: c2pa-rs @@ -111,7 +111,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: repository: castlabs/c2pa-rs - ref: a2739fd66092c410773bdc6881474ec34fb8ea16 + ref: 6ca7fb168b10bde7a1ef8bb8e577ffbae73233c7 fetch-depth: 1 path: c2pa-rs @@ -183,7 +183,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.12.dev1-py3-none-manylinux_2_28_x86_64.whl) ;; + c2pa_python-0.37.12.dev2-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" @@ -203,7 +203,7 @@ jobs: SOURCE_DATE_EPOCH: ${{ needs.prepare.outputs.source_epoch }} run: | cd c2pa-python - BUNDLE=release-out/c2pa-python-0.37.12.dev1-native-linux-x86_64.tar.gz + BUNDLE=release-out/c2pa-python-0.37.12.dev2-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 @@ -266,7 +266,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: repository: castlabs/c2pa-rs - ref: a2739fd66092c410773bdc6881474ec34fb8ea16 + ref: 6ca7fb168b10bde7a1ef8bb8e577ffbae73233c7 fetch-depth: 1 path: c2pa-rs @@ -339,7 +339,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.12.dev1-py3-none-win_amd64.whl") { + if ($wheels[0].Name -ne "c2pa_python-0.37.12.dev2-py3-none-win_amd64.whl") { throw "unexpected Windows wheel $($wheels[0].Name)" } python -m twine check $wheels[0].FullName @@ -359,7 +359,7 @@ jobs: $ErrorActionPreference = "Stop" $PSNativeCommandUseErrorActionPreference = $true Push-Location c2pa-python - $bundle = "release-out/c2pa-python-0.37.12.dev1-native-windows-x86_64.tar.gz" + $bundle = "release-out/c2pa-python-0.37.12.dev2-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 @@ -412,7 +412,7 @@ jobs: - name: Test installed wheel in a clean environment shell: bash env: - CASTLABS_RELEASE_EXPECTED_VERSION: 0.37.12.dev1 + CASTLABS_RELEASE_EXPECTED_VERSION: 0.37.12.dev2 CASTLABS_RELEASE_SMOKE_REQUIRED: "1" C2PA_SOURCE_BUILD_VERSION: 0.92.0-dev run: | @@ -420,6 +420,8 @@ jobs: python -m venv "${RUNNER_TEMP}/wheel-test" "${RUNNER_TEMP}/wheel-test/bin/python" -m pip install --upgrade pip "${RUNNER_TEMP}/wheel-test/bin/python" -m pip install release-out/*.whl + # pytest is no longer a runtime dependency of the wheel (upstream 4d909cf). + "${RUNNER_TEMP}/wheel-test/bin/python" -m pip install pytest==8.4.1 "${RUNNER_TEMP}/wheel-test/bin/python" -m pytest -q \ c2pa-python/tests/test_castlabs_release_smoke.py if [[ "${{ matrix.python }}" == "3.10" ]]; then @@ -452,7 +454,7 @@ jobs: - name: Test installed wheel in a clean environment shell: pwsh env: - CASTLABS_RELEASE_EXPECTED_VERSION: 0.37.12.dev1 + CASTLABS_RELEASE_EXPECTED_VERSION: 0.37.12.dev2 CASTLABS_RELEASE_SMOKE_REQUIRED: "1" C2PA_SOURCE_BUILD_VERSION: 0.92.0-dev run: | @@ -464,6 +466,7 @@ jobs: & "$env:RUNNER_TEMP\wheel-test\Scripts\python.exe" -m pip install --upgrade pip $wheel = (Get-ChildItem release-out/*.whl).FullName & "$env:RUNNER_TEMP\wheel-test\Scripts\python.exe" -m pip install $wheel + & "$env:RUNNER_TEMP\wheel-test\Scripts\python.exe" -m pip install pytest==8.4.1 & "$env:RUNNER_TEMP\wheel-test\Scripts\python.exe" -m pytest -q ` c2pa-python/tests/test_castlabs_release_smoke.py if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } @@ -475,7 +478,7 @@ jobs: release: needs: [prepare, linux, windows, test-linux-wheel, test-windows-wheel] - if: github.ref == 'refs/tags/castlabs-v0.37.12.dev1' + if: github.ref == 'refs/tags/castlabs-v0.37.12.dev2' runs-on: ubuntu-24.04 timeout-minutes: 30 environment: castlabs-vsi-release @@ -510,7 +513,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.12.dev1-py3-none-wheels.tar.gz + BUNDLE=assets/c2pa-python-0.37.12.dev2-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" \ @@ -527,13 +530,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.12.dev1-native-linux-x86_64.tar.gz* assets/ - cp collected/windows/c2pa-python-0.37.12.dev1-native-windows-x86_64.tar.gz* assets/ + cp collected/linux/c2pa-python-0.37.12.dev2-native-linux-x86_64.tar.gz* assets/ + cp collected/windows/c2pa-python-0.37.12.dev2-native-windows-x86_64.tar.gz* assets/ python3 c2pa-python/scripts/castlabs_release.py validate-evidence \ - --evidence assets/c2pa-python-0.37.12.dev1-native-linux-x86_64.tar.gz.evidence.json \ + --evidence assets/c2pa-python-0.37.12.dev2-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.12.dev1-native-windows-x86_64.tar.gz.evidence.json \ + --evidence assets/c2pa-python-0.37.12.dev2-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 \ @@ -547,7 +550,7 @@ jobs: - name: Require immutable release tag shell: bash run: | - TAG=castlabs-v0.37.12.dev1 + TAG=castlabs-v0.37.12.dev2 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 }}" @@ -563,7 +566,7 @@ jobs: GH_TOKEN: ${{ github.token }} run: | set -euo pipefail - TAG=castlabs-v0.37.12.dev1 + TAG=castlabs-v0.37.12.dev2 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) @@ -589,7 +592,7 @@ jobs: } discover_release() { gh api --paginate repos/castlabs/c2pa-python/releases \ - --jq '.[] | select(.tag_name == "castlabs-v0.37.12.dev1")' > "$1" + --jq '.[] | select(.tag_name == "castlabs-v0.37.12.dev2")' > "$1" return 0 } query_release_id() { diff --git a/docs/release-notes.md b/docs/release-notes.md index bffacf54..886b17e9 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,10 +1,14 @@ # Release notes -## Version 0.37.12.dev1 +## Version 0.37.12.dev2 -Castlabs Conformance Program 0.2 / C2PA 2.4 candidate prerelease. +Castlabs Conformance Program 0.2 / C2PA 2.4 candidate prerelease. Successor to +0.37.12.dev1, whose tag-triggered run `35961050991` built both platforms but +failed installed-wheel tests (pytest no longer a runtime dependency) and whose +native source failed one upstream test under `http_reqwest_blocking`; no dev1 +draft or GitHub release was created. -- Native `castlabs/c2pa-rs` `a2739fd66092c410773bdc6881474ec34fb8ea16`: the +- Native `castlabs/c2pa-rs` `6ca7fb168b10bde7a1ef8bb8e577ffbae73233c7`: the live-video VSI line merged with contentauth main `6c92bc32` (C2PA 2.4 validation), plus the single-file fMP4 Merkle and TFRA fixes. - `verify.validation_time` (RFC 3339) selects the evaluation instant for diff --git a/pyproject.toml b/pyproject.toml index 8554b7b1..7800e4c7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "c2pa-python" -version = "0.37.12.dev1" +version = "0.37.12.dev2" 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 56a3df05..1dfcf36c 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.12.dev1"}, + "version": {"const": "0.37.12.dev2"}, "file": {"type": "string", "minLength": 1}, "size": {"type": "integer", "minimum": 1}, "sha256": {"$ref": "#/$defs/sha256"}, @@ -49,7 +49,7 @@ "properties": { "repository": {"const": "castlabs/c2pa-rs"}, "url": {"const": "https://github.com/castlabs/c2pa-rs.git"}, - "commit": {"const": "a2739fd66092c410773bdc6881474ec34fb8ea16"}, + "commit": {"const": "6ca7fb168b10bde7a1ef8bb8e577ffbae73233c7"}, "version": {"const": "0.92.0-dev"}, "cargoLockSha256": {"const": "e074f31ad8c7d4d4278967bb0aaf12bde0013a5f9cb7517cc7af462dbed962e2"} } diff --git a/release/castlabs-vsi-inputs.lock.json b/release/castlabs-vsi-inputs.lock.json index 3f1d72c8..d259a384 100644 --- a/release/castlabs-vsi-inputs.lock.json +++ b/release/castlabs-vsi-inputs.lock.json @@ -2,18 +2,18 @@ "schemaVersion": 1, "package": { "name": "c2pa-python", - "version": "0.37.12.dev1" + "version": "0.37.12.dev2" }, "pythonSource": { "repository": "castlabs/c2pa-python", "url": "https://github.com/castlabs/c2pa-python.git", "releaseBranch": "feat/conformance-validation-v02", - "releaseTag": "castlabs-v0.37.12.dev1" + "releaseTag": "castlabs-v0.37.12.dev2" }, "rustSource": { "repository": "castlabs/c2pa-rs", "url": "https://github.com/castlabs/c2pa-rs.git", - "commit": "a2739fd66092c410773bdc6881474ec34fb8ea16", + "commit": "6ca7fb168b10bde7a1ef8bb8e577ffbae73233c7", "version": "0.92.0-dev", "cargoLockSha256": "e074f31ad8c7d4d4278967bb0aaf12bde0013a5f9cb7517cc7af462dbed962e2" }, diff --git a/release/castlabs-vsi-inputs.schema.json b/release/castlabs-vsi-inputs.schema.json index 77aeef19..380d086b 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.12.dev1"}} + "properties": {"name": {"const": "c2pa-python"}, "version": {"const": "0.37.12.dev2"}} }, "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/conformance-validation-v02"}, - "releaseTag": {"const": "castlabs-v0.37.12.dev1"} + "releaseTag": {"const": "castlabs-v0.37.12.dev2"} } }, "rustSource": { @@ -48,7 +48,7 @@ "properties": { "repository": {"const": "castlabs/c2pa-rs"}, "url": {"const": "https://github.com/castlabs/c2pa-rs.git"}, - "commit": {"const": "a2739fd66092c410773bdc6881474ec34fb8ea16"}, + "commit": {"const": "6ca7fb168b10bde7a1ef8bb8e577ffbae73233c7"}, "version": {"const": "0.92.0-dev"}, "cargoLockSha256": {"const": "e074f31ad8c7d4d4278967bb0aaf12bde0013a5f9cb7517cc7af462dbed962e2"} } diff --git a/scripts/castlabs_release.py b/scripts/castlabs_release.py index 236c2872..26971218 100644 --- a/scripts/castlabs_release.py +++ b/scripts/castlabs_release.py @@ -26,21 +26,21 @@ 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.12.dev1" -RUST_COMMIT = "a2739fd66092c410773bdc6881474ec34fb8ea16" +RELEASE_VERSION = "0.37.12.dev2" +RUST_COMMIT = "6ca7fb168b10bde7a1ef8bb8e577ffbae73233c7" CARGO_LOCK_SHA256 = "e074f31ad8c7d4d4278967bb0aaf12bde0013a5f9cb7517cc7af462dbed962e2" RUST_TOOLCHAIN = "1.96.0" MANYLINUX_IMAGE = "quay.io/pypa/manylinux_2_28_x86_64" MANYLINUX_DIGEST = ( "sha256:0d9c2a66a745961947a8cecbe217ca0a7ee7a5849ba2517f20f9581d18444977" ) -RELEASE_TAG = "castlabs-v0.37.12.dev1" +RELEASE_TAG = "castlabs-v0.37.12.dev2" RELEASE_NAME = f"Castlabs c2pa-python {RELEASE_VERSION} (VSI)" RELEASE_BODY_MARKER = f"castlabs-vsi-release:{RELEASE_VERSION}" RELEASE_IDENTITY_TEXT = f"Castlabs c2pa-python {RELEASE_VERSION} immutable prerelease" RELEASE_BODY = ( f"{RELEASE_BODY_MARKER}\n\n" - f"{RELEASE_IDENTITY_TEXT}. Conformance Program 0.2 / C2PA 2.4 candidate: " + f"{RELEASE_IDENTITY_TEXT}. Conformance Program 0.2 / C2PA 2.4 candidate (successor to dev1 run 35961050991; no dev1 release): " "live-video VSI merged with contentauth main 6c92bc32, single-file fMP4 " "Merkle and TFRA fixes, caller-controlled validation time and purpose-scoped " "trust. See the attached schema-2 evidence and SHA-256 sidecars." @@ -219,12 +219,12 @@ def validate_lock(lock: dict[str, Any]) -> None: if set(lock) != required or lock.get("schemaVersion") != 1: fail("release lock does not match schema version 1") if lock["package"] != {"name": "c2pa-python", "version": RELEASE_VERSION}: - fail("release lock package identity is not 0.37.12.dev1") + fail("release lock package identity is not 0.37.12.dev2") if lock["pythonSource"] != { "repository": "castlabs/c2pa-python", "url": "https://github.com/castlabs/c2pa-python.git", "releaseBranch": "feat/conformance-validation-v02", - "releaseTag": "castlabs-v0.37.12.dev1", + "releaseTag": "castlabs-v0.37.12.dev2", }: fail("unexpected c2pa-python release source policy") rust = lock["rustSource"] diff --git a/src/c2pa/c2pa.py b/src/c2pa/c2pa.py index 150a1f89..b4df734a 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.12.dev1 +# Version: 0.37.12.dev2 import ctypes import enum diff --git a/tests/test_castlabs_release_smoke.py b/tests/test_castlabs_release_smoke.py index 9d9714c7..db3df384 100644 --- a/tests/test_castlabs_release_smoke.py +++ b/tests/test_castlabs_release_smoke.py @@ -45,7 +45,7 @@ FIXTURES = Path(__file__).parent / "fixtures" -EXPECTED_VERSION = os.environ.get("CASTLABS_RELEASE_EXPECTED_VERSION", "0.37.12.dev1") +EXPECTED_VERSION = os.environ.get("CASTLABS_RELEASE_EXPECTED_VERSION", "0.37.12.dev2") REQUIRED_CAPABILITIES = { "dynamic assertions": has_dynamic_assertions, "fragmented files": has_fragmented_files, diff --git a/tests/test_castlabs_release_tooling.py b/tests/test_castlabs_release_tooling.py index 0d0f965c..fc0d9d3b 100644 --- a/tests/test_castlabs_release_tooling.py +++ b/tests/test_castlabs_release_tooling.py @@ -29,11 +29,11 @@ def test_prerelease_version_is_consistent(): - assert release.project_version(ROOT) == "0.37.12.dev1" + assert release.project_version(ROOT) == "0.37.12.dev2" first_line = ( (ROOT / "src" / "c2pa" / "c2pa.py").read_text(encoding="utf-8").splitlines()[13] ) - assert first_line == "# Version: 0.37.12.dev1" + assert first_line == "# Version: 0.37.12.dev2" def test_release_lock_and_schemas_are_valid_json(): @@ -113,7 +113,7 @@ def test_release_workflows_are_pinned_bounded_and_do_not_drift_from_helper( in release_workflow ) assert ( - "c2pa_python-0.37.12.dev1-py3-none-manylinux_2_28_x86_64.whl" in release_workflow + "c2pa_python-0.37.12.dev2-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") < ( @@ -164,7 +164,7 @@ def test_release_workflows_are_pinned_bounded_and_do_not_drift_from_helper( "python3 -m pytest -q c2pa-python/tests/test_castlabs_release_tooling.py" in (release_workflow) ) - assert "if: github.ref == 'refs/tags/castlabs-v0.37.12.dev1'" in release_workflow + assert "if: github.ref == 'refs/tags/castlabs-v0.37.12.dev2'" in release_workflow assert "-F draft=true -F prerelease=true" in release_workflow assert "--clobber" not in release_workflow assert "repos/castlabs/c2pa-python/releases" in release_workflow @@ -238,9 +238,9 @@ def test_release_workflows_are_pinned_bounded_and_do_not_drift_from_helper( in pypi_workflow ) assert '--signer-digest "$SOURCE_SHA"' in pypi_workflow - assert "--source-ref refs/tags/castlabs-v0.37.12.dev1" in pypi_workflow + assert "--source-ref refs/tags/castlabs-v0.37.12.dev2" in pypi_workflow assert '--source-digest "$SOURCE_SHA"' in pypi_workflow - assert "pypi.org/pypi/c2pa-python/0.37.12.dev1/json" in pypi_workflow + assert "pypi.org/pypi/c2pa-python/0.37.12.dev2/json" in pypi_workflow assert "pypi-plan" in pypi_workflow assert "packages-dir: publish-dist/" in pypi_workflow assert "if: steps.pypi.outputs.upload == 'true'" in pypi_workflow @@ -269,7 +269,7 @@ def test_release_workflows_are_pinned_bounded_and_do_not_drift_from_helper( assert "manual legacy publishing accepts final X.Y.Z versions only" in ( legacy_release ) - assert 'test "$VERSION" != 0.37.12.dev1' not in legacy_workflow + assert 'test "$VERSION" != 0.37.12.dev2' not in legacy_workflow assert legacy_workflow.count("final X.Y.Z versions only") == 2 assert "tests/test_castlabs_release_tooling.py" in legacy_workflow shell_helpers_start = release_workflow.index(" download_existing() {") @@ -299,7 +299,7 @@ def test_release_workflows_are_pinned_bounded_and_do_not_drift_from_helper( assert smoke_gate in smoke assert smoke.index(smoke_gate) < smoke.index("from c2pa import") assert release_workflow.count('CASTLABS_RELEASE_SMOKE_REQUIRED: "1"') == 2 - assert release_workflow.count("CASTLABS_RELEASE_EXPECTED_VERSION: 0.37.12.dev1") == 2 + assert release_workflow.count("CASTLABS_RELEASE_EXPECTED_VERSION: 0.37.12.dev2") == 2 assert "pytest.skip(" in smoke assert "unittest.skip" not in smoke dynamic_start = smoke.index( @@ -362,7 +362,7 @@ 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.12.dev1" in release_notes + assert "## Version 0.37.12.dev2" in release_notes assert "Dev4 and earlier accepted\n undersized callback outputs" in release_notes assert "breaking strictness\n change" in release_notes @@ -557,7 +557,7 @@ def test_safe_extract_round_trip_and_rejects_links(): def test_wheel_inspection_rejects_unsafe_and_multiple_native_members(): with tempfile.TemporaryDirectory() as temp: root = Path(temp) - wheel = root / "c2pa_python-0.37.12.dev1-py3-none-win_amd64.whl" + wheel = root / "c2pa_python-0.37.12.dev2-py3-none-win_amd64.whl" with zipfile.ZipFile(wheel, "w") as archive: archive.writestr("c2pa/libs/first.dll", b"one") archive.writestr("c2pa/libs/second.dll", b"two") @@ -568,7 +568,7 @@ def test_wheel_inspection_rejects_unsafe_and_multiple_native_members(): else: raise AssertionError("accepted wheel with multiple native libraries") - unsafe = root / "c2pa_python-0.37.12.dev1-py3-none-manylinux_2_28_x86_64.whl" + unsafe = root / "c2pa_python-0.37.12.dev2-py3-none-manylinux_2_28_x86_64.whl" with zipfile.ZipFile(unsafe, "w") as archive: archive.writestr("../libc2pa_c.so", b"unsafe") try: From 83cdc4758ed3cd1c375b189980508e2a38a37c84 Mon Sep 17 00:00:00 2001 From: Michael Stattmann Date: Thu, 24 Sep 2026 14:42:22 +0200 Subject: [PATCH 4/7] chore(release): prepare 0.37.12.dev3 on qualified native 6fa3e010 --- .github/workflows/castlabs-vsi-pypi.yml | 38 +++++++++--------- .github/workflows/castlabs-vsi-release.yml | 40 +++++++++---------- docs/release-notes.md | 11 ++++- pyproject.toml | 2 +- release/castlabs-release-evidence.schema.json | 4 +- release/castlabs-vsi-inputs.lock.json | 6 +-- release/castlabs-vsi-inputs.schema.json | 6 +-- scripts/castlabs_release.py | 12 +++--- src/c2pa/c2pa.py | 2 +- tests/test_castlabs_release_smoke.py | 2 +- tests/test_castlabs_release_tooling.py | 22 +++++----- 11 files changed, 76 insertions(+), 69 deletions(-) diff --git a/.github/workflows/castlabs-vsi-pypi.yml b/.github/workflows/castlabs-vsi-pypi.yml index 1d8b4c93..dcfc4d60 100644 --- a/.github/workflows/castlabs-vsi-pypi.yml +++ b/.github/workflows/castlabs-vsi-pypi.yml @@ -6,11 +6,11 @@ on: release_tag: description: Existing immutable Castlabs prerelease tag required: true - default: castlabs-v0.37.12.dev2 + default: castlabs-v0.37.12.dev3 type: string concurrency: - group: castlabs-vsi-pypi-0.37.12.dev2 + group: castlabs-vsi-pypi-0.37.12.dev3 cancel-in-progress: false permissions: @@ -29,7 +29,7 @@ jobs: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: repository: castlabs/c2pa-python - ref: castlabs-v0.37.12.dev2 + ref: castlabs-v0.37.12.dev3 fetch-depth: 0 path: c2pa-python @@ -45,13 +45,13 @@ jobs: GH_TOKEN: ${{ github.token }} INPUT_RELEASE_TAG: ${{ inputs.release_tag }} run: | - test "${INPUT_RELEASE_TAG}" = castlabs-v0.37.12.dev2 - test "$(python3 c2pa-python/scripts/castlabs_release.py lock-value package.version)" = 0.37.12.dev2 - test "$(gh api repos/castlabs/c2pa-python/releases/tags/castlabs-v0.37.12.dev2 --jq '(.draft == false and .prerelease == true)')" = true + test "${INPUT_RELEASE_TAG}" = castlabs-v0.37.12.dev3 + test "$(python3 c2pa-python/scripts/castlabs_release.py lock-value package.version)" = 0.37.12.dev3 + test "$(gh api repos/castlabs/c2pa-python/releases/tags/castlabs-v0.37.12.dev3 --jq '(.draft == false and .prerelease == true)')" = true SOURCE_SHA=$(git -C c2pa-python rev-parse HEAD) - test "$(git -C c2pa-python rev-list -n 1 castlabs-v0.37.12.dev2)" = "$SOURCE_SHA" + test "$(git -C c2pa-python rev-list -n 1 castlabs-v0.37.12.dev3)" = "$SOURCE_SHA" mkdir -p download dist - gh release download castlabs-v0.37.12.dev2 \ + gh release download castlabs-v0.37.12.dev3 \ --repo castlabs/c2pa-python --dir download python3 c2pa-python/scripts/castlabs_release.py validate-release-assets \ --assets-dir download @@ -63,29 +63,29 @@ jobs: --repo castlabs/c2pa-python \ --signer-workflow castlabs/c2pa-python/.github/workflows/castlabs-vsi-release.yml \ --signer-digest "$SOURCE_SHA" \ - --source-ref refs/tags/castlabs-v0.37.12.dev2 \ + --source-ref refs/tags/castlabs-v0.37.12.dev3 \ --source-digest "$SOURCE_SHA" \ --deny-self-hosted-runners done python3 c2pa-python/scripts/castlabs_release.py validate-evidence \ - --evidence download/c2pa-python-0.37.12.dev2-native-linux-x86_64.tar.gz.evidence.json \ + --evidence download/c2pa-python-0.37.12.dev3-native-linux-x86_64.tar.gz.evidence.json \ --artifact-dir download --source-sha "$SOURCE_SHA" python3 c2pa-python/scripts/castlabs_release.py validate-evidence \ - --evidence download/c2pa-python-0.37.12.dev2-native-windows-x86_64.tar.gz.evidence.json \ + --evidence download/c2pa-python-0.37.12.dev3-native-windows-x86_64.tar.gz.evidence.json \ --artifact-dir download --source-sha "$SOURCE_SHA" python3 c2pa-python/scripts/castlabs_release.py validate-evidence \ - --evidence download/c2pa-python-0.37.12.dev2-py3-none-wheels.tar.gz.evidence.json \ + --evidence download/c2pa-python-0.37.12.dev3-py3-none-wheels.tar.gz.evidence.json \ --artifact-dir download --source-sha "$SOURCE_SHA" python3 c2pa-python/scripts/castlabs_release.py extract \ - --archive download/c2pa-python-0.37.12.dev2-py3-none-wheels.tar.gz \ + --archive download/c2pa-python-0.37.12.dev3-py3-none-wheels.tar.gz \ --destination dist test "$(find dist -maxdepth 1 -name '*.whl' | wc -l)" -eq 2 - cmp dist/c2pa_python-0.37.12.dev2-py3-none-manylinux_2_28_x86_64.whl \ - download/c2pa_python-0.37.12.dev2-py3-none-manylinux_2_28_x86_64.whl - cmp dist/c2pa_python-0.37.12.dev2-py3-none-win_amd64.whl \ - download/c2pa_python-0.37.12.dev2-py3-none-win_amd64.whl + cmp dist/c2pa_python-0.37.12.dev3-py3-none-manylinux_2_28_x86_64.whl \ + download/c2pa_python-0.37.12.dev3-py3-none-manylinux_2_28_x86_64.whl + cmp dist/c2pa_python-0.37.12.dev3-py3-none-win_amd64.whl \ + download/c2pa_python-0.37.12.dev3-py3-none-win_amd64.whl python3 -m pip install packaging==25.0 twine==6.2.0 - python3 -c 'from packaging.version import Version; v=Version("0.37.12.dev2"); assert v.is_prerelease and str(v)=="0.37.12.dev2"' + python3 -c 'from packaging.version import Version; v=Version("0.37.12.dev3"); assert v.is_prerelease and str(v)=="0.37.12.dev3"' python3 -m twine check dist/*.whl - name: Preflight retry-safe PyPI upload set @@ -94,7 +94,7 @@ jobs: run: | STATUS=$(curl --silent --show-error --location \ --output pypi-response.json --write-out '%{http_code}' \ - https://pypi.org/pypi/c2pa-python/0.37.12.dev2/json) + https://pypi.org/pypi/c2pa-python/0.37.12.dev3/json) case "$STATUS" in 200|404) ;; *) echo "unexpected PyPI JSON API status: $STATUS" >&2; exit 1 ;; diff --git a/.github/workflows/castlabs-vsi-release.yml b/.github/workflows/castlabs-vsi-release.yml index 86c0d405..d9c1f141 100644 --- a/.github/workflows/castlabs-vsi-release.yml +++ b/.github/workflows/castlabs-vsi-release.yml @@ -3,7 +3,7 @@ name: Castlabs VSI prerelease on: push: tags: - - castlabs-v0.37.12.dev2 + - castlabs-v0.37.12.dev3 workflow_dispatch: inputs: source_sha: @@ -12,7 +12,7 @@ on: type: string concurrency: - group: castlabs-vsi-release-0.37.12.dev2 + group: castlabs-vsi-release-0.37.12.dev3 cancel-in-progress: false permissions: @@ -60,7 +60,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: repository: castlabs/c2pa-rs - ref: 6ca7fb168b10bde7a1ef8bb8e577ffbae73233c7 + ref: 6fa3e01097a87ee9265c97e00d9ec12be3052288 fetch-depth: 1 path: c2pa-rs @@ -111,7 +111,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: repository: castlabs/c2pa-rs - ref: 6ca7fb168b10bde7a1ef8bb8e577ffbae73233c7 + ref: 6fa3e01097a87ee9265c97e00d9ec12be3052288 fetch-depth: 1 path: c2pa-rs @@ -183,7 +183,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.12.dev2-py3-none-manylinux_2_28_x86_64.whl) ;; + c2pa_python-0.37.12.dev3-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" @@ -203,7 +203,7 @@ jobs: SOURCE_DATE_EPOCH: ${{ needs.prepare.outputs.source_epoch }} run: | cd c2pa-python - BUNDLE=release-out/c2pa-python-0.37.12.dev2-native-linux-x86_64.tar.gz + BUNDLE=release-out/c2pa-python-0.37.12.dev3-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 @@ -266,7 +266,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: repository: castlabs/c2pa-rs - ref: 6ca7fb168b10bde7a1ef8bb8e577ffbae73233c7 + ref: 6fa3e01097a87ee9265c97e00d9ec12be3052288 fetch-depth: 1 path: c2pa-rs @@ -339,7 +339,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.12.dev2-py3-none-win_amd64.whl") { + if ($wheels[0].Name -ne "c2pa_python-0.37.12.dev3-py3-none-win_amd64.whl") { throw "unexpected Windows wheel $($wheels[0].Name)" } python -m twine check $wheels[0].FullName @@ -359,7 +359,7 @@ jobs: $ErrorActionPreference = "Stop" $PSNativeCommandUseErrorActionPreference = $true Push-Location c2pa-python - $bundle = "release-out/c2pa-python-0.37.12.dev2-native-windows-x86_64.tar.gz" + $bundle = "release-out/c2pa-python-0.37.12.dev3-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 @@ -412,7 +412,7 @@ jobs: - name: Test installed wheel in a clean environment shell: bash env: - CASTLABS_RELEASE_EXPECTED_VERSION: 0.37.12.dev2 + CASTLABS_RELEASE_EXPECTED_VERSION: 0.37.12.dev3 CASTLABS_RELEASE_SMOKE_REQUIRED: "1" C2PA_SOURCE_BUILD_VERSION: 0.92.0-dev run: | @@ -454,7 +454,7 @@ jobs: - name: Test installed wheel in a clean environment shell: pwsh env: - CASTLABS_RELEASE_EXPECTED_VERSION: 0.37.12.dev2 + CASTLABS_RELEASE_EXPECTED_VERSION: 0.37.12.dev3 CASTLABS_RELEASE_SMOKE_REQUIRED: "1" C2PA_SOURCE_BUILD_VERSION: 0.92.0-dev run: | @@ -478,7 +478,7 @@ jobs: release: needs: [prepare, linux, windows, test-linux-wheel, test-windows-wheel] - if: github.ref == 'refs/tags/castlabs-v0.37.12.dev2' + if: github.ref == 'refs/tags/castlabs-v0.37.12.dev3' runs-on: ubuntu-24.04 timeout-minutes: 30 environment: castlabs-vsi-release @@ -513,7 +513,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.12.dev2-py3-none-wheels.tar.gz + BUNDLE=assets/c2pa-python-0.37.12.dev3-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" \ @@ -530,13 +530,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.12.dev2-native-linux-x86_64.tar.gz* assets/ - cp collected/windows/c2pa-python-0.37.12.dev2-native-windows-x86_64.tar.gz* assets/ + cp collected/linux/c2pa-python-0.37.12.dev3-native-linux-x86_64.tar.gz* assets/ + cp collected/windows/c2pa-python-0.37.12.dev3-native-windows-x86_64.tar.gz* assets/ python3 c2pa-python/scripts/castlabs_release.py validate-evidence \ - --evidence assets/c2pa-python-0.37.12.dev2-native-linux-x86_64.tar.gz.evidence.json \ + --evidence assets/c2pa-python-0.37.12.dev3-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.12.dev2-native-windows-x86_64.tar.gz.evidence.json \ + --evidence assets/c2pa-python-0.37.12.dev3-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 \ @@ -550,7 +550,7 @@ jobs: - name: Require immutable release tag shell: bash run: | - TAG=castlabs-v0.37.12.dev2 + TAG=castlabs-v0.37.12.dev3 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 }}" @@ -566,7 +566,7 @@ jobs: GH_TOKEN: ${{ github.token }} run: | set -euo pipefail - TAG=castlabs-v0.37.12.dev2 + TAG=castlabs-v0.37.12.dev3 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) @@ -592,7 +592,7 @@ jobs: } discover_release() { gh api --paginate repos/castlabs/c2pa-python/releases \ - --jq '.[] | select(.tag_name == "castlabs-v0.37.12.dev2")' > "$1" + --jq '.[] | select(.tag_name == "castlabs-v0.37.12.dev3")' > "$1" return 0 } query_release_id() { diff --git a/docs/release-notes.md b/docs/release-notes.md index 886b17e9..89e96094 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,6 +1,13 @@ # Release notes -## Version 0.37.12.dev2 +## Version 0.37.12.dev3 + +Supersedes 0.37.12.dev2 (run `35997070721`, cancelled before publication): the +native source is re-pinned to `6fa3e01097a87ee9265c97e00d9ec12be3052288`, whose +only change over dev2's `6ca7fb16` aligns c2patool CLI tests with contentauth +main, so native qualification passes on both platforms. + +### 0.37.12.dev2 (not released) Castlabs Conformance Program 0.2 / C2PA 2.4 candidate prerelease. Successor to 0.37.12.dev1, whose tag-triggered run `35961050991` built both platforms but @@ -8,7 +15,7 @@ failed installed-wheel tests (pytest no longer a runtime dependency) and whose native source failed one upstream test under `http_reqwest_blocking`; no dev1 draft or GitHub release was created. -- Native `castlabs/c2pa-rs` `6ca7fb168b10bde7a1ef8bb8e577ffbae73233c7`: the +- Native `castlabs/c2pa-rs` `6ca7fb168b10bde7a1ef8bb8e577ffbae73233c7` (dev3: `6fa3e010`): the live-video VSI line merged with contentauth main `6c92bc32` (C2PA 2.4 validation), plus the single-file fMP4 Merkle and TFRA fixes. - `verify.validation_time` (RFC 3339) selects the evaluation instant for diff --git a/pyproject.toml b/pyproject.toml index 7800e4c7..2dacec09 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "c2pa-python" -version = "0.37.12.dev2" +version = "0.37.12.dev3" 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 1dfcf36c..33ec7349 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.12.dev2"}, + "version": {"const": "0.37.12.dev3"}, "file": {"type": "string", "minLength": 1}, "size": {"type": "integer", "minimum": 1}, "sha256": {"$ref": "#/$defs/sha256"}, @@ -49,7 +49,7 @@ "properties": { "repository": {"const": "castlabs/c2pa-rs"}, "url": {"const": "https://github.com/castlabs/c2pa-rs.git"}, - "commit": {"const": "6ca7fb168b10bde7a1ef8bb8e577ffbae73233c7"}, + "commit": {"const": "6fa3e01097a87ee9265c97e00d9ec12be3052288"}, "version": {"const": "0.92.0-dev"}, "cargoLockSha256": {"const": "e074f31ad8c7d4d4278967bb0aaf12bde0013a5f9cb7517cc7af462dbed962e2"} } diff --git a/release/castlabs-vsi-inputs.lock.json b/release/castlabs-vsi-inputs.lock.json index d259a384..1822c6c8 100644 --- a/release/castlabs-vsi-inputs.lock.json +++ b/release/castlabs-vsi-inputs.lock.json @@ -2,18 +2,18 @@ "schemaVersion": 1, "package": { "name": "c2pa-python", - "version": "0.37.12.dev2" + "version": "0.37.12.dev3" }, "pythonSource": { "repository": "castlabs/c2pa-python", "url": "https://github.com/castlabs/c2pa-python.git", "releaseBranch": "feat/conformance-validation-v02", - "releaseTag": "castlabs-v0.37.12.dev2" + "releaseTag": "castlabs-v0.37.12.dev3" }, "rustSource": { "repository": "castlabs/c2pa-rs", "url": "https://github.com/castlabs/c2pa-rs.git", - "commit": "6ca7fb168b10bde7a1ef8bb8e577ffbae73233c7", + "commit": "6fa3e01097a87ee9265c97e00d9ec12be3052288", "version": "0.92.0-dev", "cargoLockSha256": "e074f31ad8c7d4d4278967bb0aaf12bde0013a5f9cb7517cc7af462dbed962e2" }, diff --git a/release/castlabs-vsi-inputs.schema.json b/release/castlabs-vsi-inputs.schema.json index 380d086b..e48c9d19 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.12.dev2"}} + "properties": {"name": {"const": "c2pa-python"}, "version": {"const": "0.37.12.dev3"}} }, "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/conformance-validation-v02"}, - "releaseTag": {"const": "castlabs-v0.37.12.dev2"} + "releaseTag": {"const": "castlabs-v0.37.12.dev3"} } }, "rustSource": { @@ -48,7 +48,7 @@ "properties": { "repository": {"const": "castlabs/c2pa-rs"}, "url": {"const": "https://github.com/castlabs/c2pa-rs.git"}, - "commit": {"const": "6ca7fb168b10bde7a1ef8bb8e577ffbae73233c7"}, + "commit": {"const": "6fa3e01097a87ee9265c97e00d9ec12be3052288"}, "version": {"const": "0.92.0-dev"}, "cargoLockSha256": {"const": "e074f31ad8c7d4d4278967bb0aaf12bde0013a5f9cb7517cc7af462dbed962e2"} } diff --git a/scripts/castlabs_release.py b/scripts/castlabs_release.py index 26971218..2a79c423 100644 --- a/scripts/castlabs_release.py +++ b/scripts/castlabs_release.py @@ -26,21 +26,21 @@ 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.12.dev2" -RUST_COMMIT = "6ca7fb168b10bde7a1ef8bb8e577ffbae73233c7" +RELEASE_VERSION = "0.37.12.dev3" +RUST_COMMIT = "6fa3e01097a87ee9265c97e00d9ec12be3052288" CARGO_LOCK_SHA256 = "e074f31ad8c7d4d4278967bb0aaf12bde0013a5f9cb7517cc7af462dbed962e2" RUST_TOOLCHAIN = "1.96.0" MANYLINUX_IMAGE = "quay.io/pypa/manylinux_2_28_x86_64" MANYLINUX_DIGEST = ( "sha256:0d9c2a66a745961947a8cecbe217ca0a7ee7a5849ba2517f20f9581d18444977" ) -RELEASE_TAG = "castlabs-v0.37.12.dev2" +RELEASE_TAG = "castlabs-v0.37.12.dev3" RELEASE_NAME = f"Castlabs c2pa-python {RELEASE_VERSION} (VSI)" RELEASE_BODY_MARKER = f"castlabs-vsi-release:{RELEASE_VERSION}" RELEASE_IDENTITY_TEXT = f"Castlabs c2pa-python {RELEASE_VERSION} immutable prerelease" RELEASE_BODY = ( f"{RELEASE_BODY_MARKER}\n\n" - f"{RELEASE_IDENTITY_TEXT}. Conformance Program 0.2 / C2PA 2.4 candidate (successor to dev1 run 35961050991; no dev1 release): " + f"{RELEASE_IDENTITY_TEXT}. Conformance Program 0.2 / C2PA 2.4 candidate (successor to dev1 run 35961050991 and dev2 run 35997070721; neither was released): " "live-video VSI merged with contentauth main 6c92bc32, single-file fMP4 " "Merkle and TFRA fixes, caller-controlled validation time and purpose-scoped " "trust. See the attached schema-2 evidence and SHA-256 sidecars." @@ -219,12 +219,12 @@ def validate_lock(lock: dict[str, Any]) -> None: if set(lock) != required or lock.get("schemaVersion") != 1: fail("release lock does not match schema version 1") if lock["package"] != {"name": "c2pa-python", "version": RELEASE_VERSION}: - fail("release lock package identity is not 0.37.12.dev2") + fail("release lock package identity is not 0.37.12.dev3") if lock["pythonSource"] != { "repository": "castlabs/c2pa-python", "url": "https://github.com/castlabs/c2pa-python.git", "releaseBranch": "feat/conformance-validation-v02", - "releaseTag": "castlabs-v0.37.12.dev2", + "releaseTag": "castlabs-v0.37.12.dev3", }: fail("unexpected c2pa-python release source policy") rust = lock["rustSource"] diff --git a/src/c2pa/c2pa.py b/src/c2pa/c2pa.py index b4df734a..6fc29e8a 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.12.dev2 +# Version: 0.37.12.dev3 import ctypes import enum diff --git a/tests/test_castlabs_release_smoke.py b/tests/test_castlabs_release_smoke.py index db3df384..749795e6 100644 --- a/tests/test_castlabs_release_smoke.py +++ b/tests/test_castlabs_release_smoke.py @@ -45,7 +45,7 @@ FIXTURES = Path(__file__).parent / "fixtures" -EXPECTED_VERSION = os.environ.get("CASTLABS_RELEASE_EXPECTED_VERSION", "0.37.12.dev2") +EXPECTED_VERSION = os.environ.get("CASTLABS_RELEASE_EXPECTED_VERSION", "0.37.12.dev3") REQUIRED_CAPABILITIES = { "dynamic assertions": has_dynamic_assertions, "fragmented files": has_fragmented_files, diff --git a/tests/test_castlabs_release_tooling.py b/tests/test_castlabs_release_tooling.py index fc0d9d3b..9e6df1a1 100644 --- a/tests/test_castlabs_release_tooling.py +++ b/tests/test_castlabs_release_tooling.py @@ -29,11 +29,11 @@ def test_prerelease_version_is_consistent(): - assert release.project_version(ROOT) == "0.37.12.dev2" + assert release.project_version(ROOT) == "0.37.12.dev3" first_line = ( (ROOT / "src" / "c2pa" / "c2pa.py").read_text(encoding="utf-8").splitlines()[13] ) - assert first_line == "# Version: 0.37.12.dev2" + assert first_line == "# Version: 0.37.12.dev3" def test_release_lock_and_schemas_are_valid_json(): @@ -113,7 +113,7 @@ def test_release_workflows_are_pinned_bounded_and_do_not_drift_from_helper( in release_workflow ) assert ( - "c2pa_python-0.37.12.dev2-py3-none-manylinux_2_28_x86_64.whl" in release_workflow + "c2pa_python-0.37.12.dev3-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") < ( @@ -164,7 +164,7 @@ def test_release_workflows_are_pinned_bounded_and_do_not_drift_from_helper( "python3 -m pytest -q c2pa-python/tests/test_castlabs_release_tooling.py" in (release_workflow) ) - assert "if: github.ref == 'refs/tags/castlabs-v0.37.12.dev2'" in release_workflow + assert "if: github.ref == 'refs/tags/castlabs-v0.37.12.dev3'" in release_workflow assert "-F draft=true -F prerelease=true" in release_workflow assert "--clobber" not in release_workflow assert "repos/castlabs/c2pa-python/releases" in release_workflow @@ -238,9 +238,9 @@ def test_release_workflows_are_pinned_bounded_and_do_not_drift_from_helper( in pypi_workflow ) assert '--signer-digest "$SOURCE_SHA"' in pypi_workflow - assert "--source-ref refs/tags/castlabs-v0.37.12.dev2" in pypi_workflow + assert "--source-ref refs/tags/castlabs-v0.37.12.dev3" in pypi_workflow assert '--source-digest "$SOURCE_SHA"' in pypi_workflow - assert "pypi.org/pypi/c2pa-python/0.37.12.dev2/json" in pypi_workflow + assert "pypi.org/pypi/c2pa-python/0.37.12.dev3/json" in pypi_workflow assert "pypi-plan" in pypi_workflow assert "packages-dir: publish-dist/" in pypi_workflow assert "if: steps.pypi.outputs.upload == 'true'" in pypi_workflow @@ -269,7 +269,7 @@ def test_release_workflows_are_pinned_bounded_and_do_not_drift_from_helper( assert "manual legacy publishing accepts final X.Y.Z versions only" in ( legacy_release ) - assert 'test "$VERSION" != 0.37.12.dev2' not in legacy_workflow + assert 'test "$VERSION" != 0.37.12.dev3' not in legacy_workflow assert legacy_workflow.count("final X.Y.Z versions only") == 2 assert "tests/test_castlabs_release_tooling.py" in legacy_workflow shell_helpers_start = release_workflow.index(" download_existing() {") @@ -299,7 +299,7 @@ def test_release_workflows_are_pinned_bounded_and_do_not_drift_from_helper( assert smoke_gate in smoke assert smoke.index(smoke_gate) < smoke.index("from c2pa import") assert release_workflow.count('CASTLABS_RELEASE_SMOKE_REQUIRED: "1"') == 2 - assert release_workflow.count("CASTLABS_RELEASE_EXPECTED_VERSION: 0.37.12.dev2") == 2 + assert release_workflow.count("CASTLABS_RELEASE_EXPECTED_VERSION: 0.37.12.dev3") == 2 assert "pytest.skip(" in smoke assert "unittest.skip" not in smoke dynamic_start = smoke.index( @@ -362,7 +362,7 @@ 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.12.dev2" in release_notes + assert "## Version 0.37.12.dev3" in release_notes assert "Dev4 and earlier accepted\n undersized callback outputs" in release_notes assert "breaking strictness\n change" in release_notes @@ -557,7 +557,7 @@ def test_safe_extract_round_trip_and_rejects_links(): def test_wheel_inspection_rejects_unsafe_and_multiple_native_members(): with tempfile.TemporaryDirectory() as temp: root = Path(temp) - wheel = root / "c2pa_python-0.37.12.dev2-py3-none-win_amd64.whl" + wheel = root / "c2pa_python-0.37.12.dev3-py3-none-win_amd64.whl" with zipfile.ZipFile(wheel, "w") as archive: archive.writestr("c2pa/libs/first.dll", b"one") archive.writestr("c2pa/libs/second.dll", b"two") @@ -568,7 +568,7 @@ def test_wheel_inspection_rejects_unsafe_and_multiple_native_members(): else: raise AssertionError("accepted wheel with multiple native libraries") - unsafe = root / "c2pa_python-0.37.12.dev2-py3-none-manylinux_2_28_x86_64.whl" + unsafe = root / "c2pa_python-0.37.12.dev3-py3-none-manylinux_2_28_x86_64.whl" with zipfile.ZipFile(unsafe, "w") as archive: archive.writestr("../libc2pa_c.so", b"unsafe") try: From f55a1fb7f05de005eeca580b023d97b104e411ef Mon Sep 17 00:00:00 2001 From: Michael Stattmann Date: Thu, 24 Sep 2026 16:54:45 +0200 Subject: [PATCH 5/7] chore(release): prepare 0.37.12.dev4 with crJSON 2.4 conformance (native e1cc897d) --- .github/workflows/castlabs-vsi-pypi.yml | 38 +++++++++--------- .github/workflows/castlabs-vsi-release.yml | 40 +++++++++---------- docs/release-notes.md | 9 +++++ pyproject.toml | 2 +- release/castlabs-release-evidence.schema.json | 4 +- release/castlabs-vsi-inputs.lock.json | 6 +-- release/castlabs-vsi-inputs.schema.json | 6 +-- scripts/castlabs_release.py | 12 +++--- src/c2pa/c2pa.py | 2 +- tests/test_castlabs_release_smoke.py | 2 +- tests/test_castlabs_release_tooling.py | 22 +++++----- tests/test_unit_tests.py | 5 +++ 12 files changed, 81 insertions(+), 67 deletions(-) diff --git a/.github/workflows/castlabs-vsi-pypi.yml b/.github/workflows/castlabs-vsi-pypi.yml index dcfc4d60..f0681254 100644 --- a/.github/workflows/castlabs-vsi-pypi.yml +++ b/.github/workflows/castlabs-vsi-pypi.yml @@ -6,11 +6,11 @@ on: release_tag: description: Existing immutable Castlabs prerelease tag required: true - default: castlabs-v0.37.12.dev3 + default: castlabs-v0.37.12.dev4 type: string concurrency: - group: castlabs-vsi-pypi-0.37.12.dev3 + group: castlabs-vsi-pypi-0.37.12.dev4 cancel-in-progress: false permissions: @@ -29,7 +29,7 @@ jobs: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: repository: castlabs/c2pa-python - ref: castlabs-v0.37.12.dev3 + ref: castlabs-v0.37.12.dev4 fetch-depth: 0 path: c2pa-python @@ -45,13 +45,13 @@ jobs: GH_TOKEN: ${{ github.token }} INPUT_RELEASE_TAG: ${{ inputs.release_tag }} run: | - test "${INPUT_RELEASE_TAG}" = castlabs-v0.37.12.dev3 - test "$(python3 c2pa-python/scripts/castlabs_release.py lock-value package.version)" = 0.37.12.dev3 - test "$(gh api repos/castlabs/c2pa-python/releases/tags/castlabs-v0.37.12.dev3 --jq '(.draft == false and .prerelease == true)')" = true + test "${INPUT_RELEASE_TAG}" = castlabs-v0.37.12.dev4 + test "$(python3 c2pa-python/scripts/castlabs_release.py lock-value package.version)" = 0.37.12.dev4 + test "$(gh api repos/castlabs/c2pa-python/releases/tags/castlabs-v0.37.12.dev4 --jq '(.draft == false and .prerelease == true)')" = true SOURCE_SHA=$(git -C c2pa-python rev-parse HEAD) - test "$(git -C c2pa-python rev-list -n 1 castlabs-v0.37.12.dev3)" = "$SOURCE_SHA" + test "$(git -C c2pa-python rev-list -n 1 castlabs-v0.37.12.dev4)" = "$SOURCE_SHA" mkdir -p download dist - gh release download castlabs-v0.37.12.dev3 \ + gh release download castlabs-v0.37.12.dev4 \ --repo castlabs/c2pa-python --dir download python3 c2pa-python/scripts/castlabs_release.py validate-release-assets \ --assets-dir download @@ -63,29 +63,29 @@ jobs: --repo castlabs/c2pa-python \ --signer-workflow castlabs/c2pa-python/.github/workflows/castlabs-vsi-release.yml \ --signer-digest "$SOURCE_SHA" \ - --source-ref refs/tags/castlabs-v0.37.12.dev3 \ + --source-ref refs/tags/castlabs-v0.37.12.dev4 \ --source-digest "$SOURCE_SHA" \ --deny-self-hosted-runners done python3 c2pa-python/scripts/castlabs_release.py validate-evidence \ - --evidence download/c2pa-python-0.37.12.dev3-native-linux-x86_64.tar.gz.evidence.json \ + --evidence download/c2pa-python-0.37.12.dev4-native-linux-x86_64.tar.gz.evidence.json \ --artifact-dir download --source-sha "$SOURCE_SHA" python3 c2pa-python/scripts/castlabs_release.py validate-evidence \ - --evidence download/c2pa-python-0.37.12.dev3-native-windows-x86_64.tar.gz.evidence.json \ + --evidence download/c2pa-python-0.37.12.dev4-native-windows-x86_64.tar.gz.evidence.json \ --artifact-dir download --source-sha "$SOURCE_SHA" python3 c2pa-python/scripts/castlabs_release.py validate-evidence \ - --evidence download/c2pa-python-0.37.12.dev3-py3-none-wheels.tar.gz.evidence.json \ + --evidence download/c2pa-python-0.37.12.dev4-py3-none-wheels.tar.gz.evidence.json \ --artifact-dir download --source-sha "$SOURCE_SHA" python3 c2pa-python/scripts/castlabs_release.py extract \ - --archive download/c2pa-python-0.37.12.dev3-py3-none-wheels.tar.gz \ + --archive download/c2pa-python-0.37.12.dev4-py3-none-wheels.tar.gz \ --destination dist test "$(find dist -maxdepth 1 -name '*.whl' | wc -l)" -eq 2 - cmp dist/c2pa_python-0.37.12.dev3-py3-none-manylinux_2_28_x86_64.whl \ - download/c2pa_python-0.37.12.dev3-py3-none-manylinux_2_28_x86_64.whl - cmp dist/c2pa_python-0.37.12.dev3-py3-none-win_amd64.whl \ - download/c2pa_python-0.37.12.dev3-py3-none-win_amd64.whl + cmp dist/c2pa_python-0.37.12.dev4-py3-none-manylinux_2_28_x86_64.whl \ + download/c2pa_python-0.37.12.dev4-py3-none-manylinux_2_28_x86_64.whl + cmp dist/c2pa_python-0.37.12.dev4-py3-none-win_amd64.whl \ + download/c2pa_python-0.37.12.dev4-py3-none-win_amd64.whl python3 -m pip install packaging==25.0 twine==6.2.0 - python3 -c 'from packaging.version import Version; v=Version("0.37.12.dev3"); assert v.is_prerelease and str(v)=="0.37.12.dev3"' + python3 -c 'from packaging.version import Version; v=Version("0.37.12.dev4"); assert v.is_prerelease and str(v)=="0.37.12.dev4"' python3 -m twine check dist/*.whl - name: Preflight retry-safe PyPI upload set @@ -94,7 +94,7 @@ jobs: run: | STATUS=$(curl --silent --show-error --location \ --output pypi-response.json --write-out '%{http_code}' \ - https://pypi.org/pypi/c2pa-python/0.37.12.dev3/json) + https://pypi.org/pypi/c2pa-python/0.37.12.dev4/json) case "$STATUS" in 200|404) ;; *) echo "unexpected PyPI JSON API status: $STATUS" >&2; exit 1 ;; diff --git a/.github/workflows/castlabs-vsi-release.yml b/.github/workflows/castlabs-vsi-release.yml index d9c1f141..36d1b493 100644 --- a/.github/workflows/castlabs-vsi-release.yml +++ b/.github/workflows/castlabs-vsi-release.yml @@ -3,7 +3,7 @@ name: Castlabs VSI prerelease on: push: tags: - - castlabs-v0.37.12.dev3 + - castlabs-v0.37.12.dev4 workflow_dispatch: inputs: source_sha: @@ -12,7 +12,7 @@ on: type: string concurrency: - group: castlabs-vsi-release-0.37.12.dev3 + group: castlabs-vsi-release-0.37.12.dev4 cancel-in-progress: false permissions: @@ -60,7 +60,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: repository: castlabs/c2pa-rs - ref: 6fa3e01097a87ee9265c97e00d9ec12be3052288 + ref: e1cc897d5c04d11e7773f54a659f98ec89ae224f fetch-depth: 1 path: c2pa-rs @@ -111,7 +111,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: repository: castlabs/c2pa-rs - ref: 6fa3e01097a87ee9265c97e00d9ec12be3052288 + ref: e1cc897d5c04d11e7773f54a659f98ec89ae224f fetch-depth: 1 path: c2pa-rs @@ -183,7 +183,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.12.dev3-py3-none-manylinux_2_28_x86_64.whl) ;; + c2pa_python-0.37.12.dev4-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" @@ -203,7 +203,7 @@ jobs: SOURCE_DATE_EPOCH: ${{ needs.prepare.outputs.source_epoch }} run: | cd c2pa-python - BUNDLE=release-out/c2pa-python-0.37.12.dev3-native-linux-x86_64.tar.gz + BUNDLE=release-out/c2pa-python-0.37.12.dev4-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 @@ -266,7 +266,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: repository: castlabs/c2pa-rs - ref: 6fa3e01097a87ee9265c97e00d9ec12be3052288 + ref: e1cc897d5c04d11e7773f54a659f98ec89ae224f fetch-depth: 1 path: c2pa-rs @@ -339,7 +339,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.12.dev3-py3-none-win_amd64.whl") { + if ($wheels[0].Name -ne "c2pa_python-0.37.12.dev4-py3-none-win_amd64.whl") { throw "unexpected Windows wheel $($wheels[0].Name)" } python -m twine check $wheels[0].FullName @@ -359,7 +359,7 @@ jobs: $ErrorActionPreference = "Stop" $PSNativeCommandUseErrorActionPreference = $true Push-Location c2pa-python - $bundle = "release-out/c2pa-python-0.37.12.dev3-native-windows-x86_64.tar.gz" + $bundle = "release-out/c2pa-python-0.37.12.dev4-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 @@ -412,7 +412,7 @@ jobs: - name: Test installed wheel in a clean environment shell: bash env: - CASTLABS_RELEASE_EXPECTED_VERSION: 0.37.12.dev3 + CASTLABS_RELEASE_EXPECTED_VERSION: 0.37.12.dev4 CASTLABS_RELEASE_SMOKE_REQUIRED: "1" C2PA_SOURCE_BUILD_VERSION: 0.92.0-dev run: | @@ -454,7 +454,7 @@ jobs: - name: Test installed wheel in a clean environment shell: pwsh env: - CASTLABS_RELEASE_EXPECTED_VERSION: 0.37.12.dev3 + CASTLABS_RELEASE_EXPECTED_VERSION: 0.37.12.dev4 CASTLABS_RELEASE_SMOKE_REQUIRED: "1" C2PA_SOURCE_BUILD_VERSION: 0.92.0-dev run: | @@ -478,7 +478,7 @@ jobs: release: needs: [prepare, linux, windows, test-linux-wheel, test-windows-wheel] - if: github.ref == 'refs/tags/castlabs-v0.37.12.dev3' + if: github.ref == 'refs/tags/castlabs-v0.37.12.dev4' runs-on: ubuntu-24.04 timeout-minutes: 30 environment: castlabs-vsi-release @@ -513,7 +513,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.12.dev3-py3-none-wheels.tar.gz + BUNDLE=assets/c2pa-python-0.37.12.dev4-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" \ @@ -530,13 +530,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.12.dev3-native-linux-x86_64.tar.gz* assets/ - cp collected/windows/c2pa-python-0.37.12.dev3-native-windows-x86_64.tar.gz* assets/ + cp collected/linux/c2pa-python-0.37.12.dev4-native-linux-x86_64.tar.gz* assets/ + cp collected/windows/c2pa-python-0.37.12.dev4-native-windows-x86_64.tar.gz* assets/ python3 c2pa-python/scripts/castlabs_release.py validate-evidence \ - --evidence assets/c2pa-python-0.37.12.dev3-native-linux-x86_64.tar.gz.evidence.json \ + --evidence assets/c2pa-python-0.37.12.dev4-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.12.dev3-native-windows-x86_64.tar.gz.evidence.json \ + --evidence assets/c2pa-python-0.37.12.dev4-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 \ @@ -550,7 +550,7 @@ jobs: - name: Require immutable release tag shell: bash run: | - TAG=castlabs-v0.37.12.dev3 + TAG=castlabs-v0.37.12.dev4 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 }}" @@ -566,7 +566,7 @@ jobs: GH_TOKEN: ${{ github.token }} run: | set -euo pipefail - TAG=castlabs-v0.37.12.dev3 + TAG=castlabs-v0.37.12.dev4 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) @@ -592,7 +592,7 @@ jobs: } discover_release() { gh api --paginate repos/castlabs/c2pa-python/releases \ - --jq '.[] | select(.tag_name == "castlabs-v0.37.12.dev3")' > "$1" + --jq '.[] | select(.tag_name == "castlabs-v0.37.12.dev4")' > "$1" return 0 } query_release_id() { diff --git a/docs/release-notes.md b/docs/release-notes.md index 89e96094..21abf2c4 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,5 +1,14 @@ # Release notes +## Version 0.37.12.dev4 + +Supersedes the published 0.37.12.dev3 (which remains unchanged). Native +`castlabs/c2pa-rs` `e1cc897d5c04d11e7773f54a659f98ec89ae224f` makes `Reader.crjson()` +conform to the published crJSON 2.4 schema: `validationResults.specVersion` +reports `2.4.0` (the validator's C2PA version) instead of `2.3.0`, and the +post-2.4 `isUpdateManifest` / `isCompressedManifest` manifest fields are no +longer emitted. + ## Version 0.37.12.dev3 Supersedes 0.37.12.dev2 (run `35997070721`, cancelled before publication): the diff --git a/pyproject.toml b/pyproject.toml index 2dacec09..f57adc9a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "c2pa-python" -version = "0.37.12.dev3" +version = "0.37.12.dev4" 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 33ec7349..fa1c5cee 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.12.dev3"}, + "version": {"const": "0.37.12.dev4"}, "file": {"type": "string", "minLength": 1}, "size": {"type": "integer", "minimum": 1}, "sha256": {"$ref": "#/$defs/sha256"}, @@ -49,7 +49,7 @@ "properties": { "repository": {"const": "castlabs/c2pa-rs"}, "url": {"const": "https://github.com/castlabs/c2pa-rs.git"}, - "commit": {"const": "6fa3e01097a87ee9265c97e00d9ec12be3052288"}, + "commit": {"const": "e1cc897d5c04d11e7773f54a659f98ec89ae224f"}, "version": {"const": "0.92.0-dev"}, "cargoLockSha256": {"const": "e074f31ad8c7d4d4278967bb0aaf12bde0013a5f9cb7517cc7af462dbed962e2"} } diff --git a/release/castlabs-vsi-inputs.lock.json b/release/castlabs-vsi-inputs.lock.json index 1822c6c8..1f9f269c 100644 --- a/release/castlabs-vsi-inputs.lock.json +++ b/release/castlabs-vsi-inputs.lock.json @@ -2,18 +2,18 @@ "schemaVersion": 1, "package": { "name": "c2pa-python", - "version": "0.37.12.dev3" + "version": "0.37.12.dev4" }, "pythonSource": { "repository": "castlabs/c2pa-python", "url": "https://github.com/castlabs/c2pa-python.git", "releaseBranch": "feat/conformance-validation-v02", - "releaseTag": "castlabs-v0.37.12.dev3" + "releaseTag": "castlabs-v0.37.12.dev4" }, "rustSource": { "repository": "castlabs/c2pa-rs", "url": "https://github.com/castlabs/c2pa-rs.git", - "commit": "6fa3e01097a87ee9265c97e00d9ec12be3052288", + "commit": "e1cc897d5c04d11e7773f54a659f98ec89ae224f", "version": "0.92.0-dev", "cargoLockSha256": "e074f31ad8c7d4d4278967bb0aaf12bde0013a5f9cb7517cc7af462dbed962e2" }, diff --git a/release/castlabs-vsi-inputs.schema.json b/release/castlabs-vsi-inputs.schema.json index e48c9d19..8a5ccb0a 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.12.dev3"}} + "properties": {"name": {"const": "c2pa-python"}, "version": {"const": "0.37.12.dev4"}} }, "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/conformance-validation-v02"}, - "releaseTag": {"const": "castlabs-v0.37.12.dev3"} + "releaseTag": {"const": "castlabs-v0.37.12.dev4"} } }, "rustSource": { @@ -48,7 +48,7 @@ "properties": { "repository": {"const": "castlabs/c2pa-rs"}, "url": {"const": "https://github.com/castlabs/c2pa-rs.git"}, - "commit": {"const": "6fa3e01097a87ee9265c97e00d9ec12be3052288"}, + "commit": {"const": "e1cc897d5c04d11e7773f54a659f98ec89ae224f"}, "version": {"const": "0.92.0-dev"}, "cargoLockSha256": {"const": "e074f31ad8c7d4d4278967bb0aaf12bde0013a5f9cb7517cc7af462dbed962e2"} } diff --git a/scripts/castlabs_release.py b/scripts/castlabs_release.py index 2a79c423..544207ed 100644 --- a/scripts/castlabs_release.py +++ b/scripts/castlabs_release.py @@ -26,21 +26,21 @@ 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.12.dev3" -RUST_COMMIT = "6fa3e01097a87ee9265c97e00d9ec12be3052288" +RELEASE_VERSION = "0.37.12.dev4" +RUST_COMMIT = "e1cc897d5c04d11e7773f54a659f98ec89ae224f" CARGO_LOCK_SHA256 = "e074f31ad8c7d4d4278967bb0aaf12bde0013a5f9cb7517cc7af462dbed962e2" RUST_TOOLCHAIN = "1.96.0" MANYLINUX_IMAGE = "quay.io/pypa/manylinux_2_28_x86_64" MANYLINUX_DIGEST = ( "sha256:0d9c2a66a745961947a8cecbe217ca0a7ee7a5849ba2517f20f9581d18444977" ) -RELEASE_TAG = "castlabs-v0.37.12.dev3" +RELEASE_TAG = "castlabs-v0.37.12.dev4" RELEASE_NAME = f"Castlabs c2pa-python {RELEASE_VERSION} (VSI)" RELEASE_BODY_MARKER = f"castlabs-vsi-release:{RELEASE_VERSION}" RELEASE_IDENTITY_TEXT = f"Castlabs c2pa-python {RELEASE_VERSION} immutable prerelease" RELEASE_BODY = ( f"{RELEASE_BODY_MARKER}\n\n" - f"{RELEASE_IDENTITY_TEXT}. Conformance Program 0.2 / C2PA 2.4 candidate (successor to dev1 run 35961050991 and dev2 run 35997070721; neither was released): " + f"{RELEASE_IDENTITY_TEXT}. Conformance Program 0.2 / C2PA 2.4 candidate (successor to the published dev3; crJSON 2.4 conformance): " "live-video VSI merged with contentauth main 6c92bc32, single-file fMP4 " "Merkle and TFRA fixes, caller-controlled validation time and purpose-scoped " "trust. See the attached schema-2 evidence and SHA-256 sidecars." @@ -219,12 +219,12 @@ def validate_lock(lock: dict[str, Any]) -> None: if set(lock) != required or lock.get("schemaVersion") != 1: fail("release lock does not match schema version 1") if lock["package"] != {"name": "c2pa-python", "version": RELEASE_VERSION}: - fail("release lock package identity is not 0.37.12.dev3") + fail("release lock package identity is not 0.37.12.dev4") if lock["pythonSource"] != { "repository": "castlabs/c2pa-python", "url": "https://github.com/castlabs/c2pa-python.git", "releaseBranch": "feat/conformance-validation-v02", - "releaseTag": "castlabs-v0.37.12.dev3", + "releaseTag": "castlabs-v0.37.12.dev4", }: fail("unexpected c2pa-python release source policy") rust = lock["rustSource"] diff --git a/src/c2pa/c2pa.py b/src/c2pa/c2pa.py index 6fc29e8a..fb73c633 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.12.dev3 +# Version: 0.37.12.dev4 import ctypes import enum diff --git a/tests/test_castlabs_release_smoke.py b/tests/test_castlabs_release_smoke.py index 749795e6..a4c3469b 100644 --- a/tests/test_castlabs_release_smoke.py +++ b/tests/test_castlabs_release_smoke.py @@ -45,7 +45,7 @@ FIXTURES = Path(__file__).parent / "fixtures" -EXPECTED_VERSION = os.environ.get("CASTLABS_RELEASE_EXPECTED_VERSION", "0.37.12.dev3") +EXPECTED_VERSION = os.environ.get("CASTLABS_RELEASE_EXPECTED_VERSION", "0.37.12.dev4") REQUIRED_CAPABILITIES = { "dynamic assertions": has_dynamic_assertions, "fragmented files": has_fragmented_files, diff --git a/tests/test_castlabs_release_tooling.py b/tests/test_castlabs_release_tooling.py index 9e6df1a1..0aca17b8 100644 --- a/tests/test_castlabs_release_tooling.py +++ b/tests/test_castlabs_release_tooling.py @@ -29,11 +29,11 @@ def test_prerelease_version_is_consistent(): - assert release.project_version(ROOT) == "0.37.12.dev3" + assert release.project_version(ROOT) == "0.37.12.dev4" first_line = ( (ROOT / "src" / "c2pa" / "c2pa.py").read_text(encoding="utf-8").splitlines()[13] ) - assert first_line == "# Version: 0.37.12.dev3" + assert first_line == "# Version: 0.37.12.dev4" def test_release_lock_and_schemas_are_valid_json(): @@ -113,7 +113,7 @@ def test_release_workflows_are_pinned_bounded_and_do_not_drift_from_helper( in release_workflow ) assert ( - "c2pa_python-0.37.12.dev3-py3-none-manylinux_2_28_x86_64.whl" in release_workflow + "c2pa_python-0.37.12.dev4-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") < ( @@ -164,7 +164,7 @@ def test_release_workflows_are_pinned_bounded_and_do_not_drift_from_helper( "python3 -m pytest -q c2pa-python/tests/test_castlabs_release_tooling.py" in (release_workflow) ) - assert "if: github.ref == 'refs/tags/castlabs-v0.37.12.dev3'" in release_workflow + assert "if: github.ref == 'refs/tags/castlabs-v0.37.12.dev4'" in release_workflow assert "-F draft=true -F prerelease=true" in release_workflow assert "--clobber" not in release_workflow assert "repos/castlabs/c2pa-python/releases" in release_workflow @@ -238,9 +238,9 @@ def test_release_workflows_are_pinned_bounded_and_do_not_drift_from_helper( in pypi_workflow ) assert '--signer-digest "$SOURCE_SHA"' in pypi_workflow - assert "--source-ref refs/tags/castlabs-v0.37.12.dev3" in pypi_workflow + assert "--source-ref refs/tags/castlabs-v0.37.12.dev4" in pypi_workflow assert '--source-digest "$SOURCE_SHA"' in pypi_workflow - assert "pypi.org/pypi/c2pa-python/0.37.12.dev3/json" in pypi_workflow + assert "pypi.org/pypi/c2pa-python/0.37.12.dev4/json" in pypi_workflow assert "pypi-plan" in pypi_workflow assert "packages-dir: publish-dist/" in pypi_workflow assert "if: steps.pypi.outputs.upload == 'true'" in pypi_workflow @@ -269,7 +269,7 @@ def test_release_workflows_are_pinned_bounded_and_do_not_drift_from_helper( assert "manual legacy publishing accepts final X.Y.Z versions only" in ( legacy_release ) - assert 'test "$VERSION" != 0.37.12.dev3' not in legacy_workflow + assert 'test "$VERSION" != 0.37.12.dev4' not in legacy_workflow assert legacy_workflow.count("final X.Y.Z versions only") == 2 assert "tests/test_castlabs_release_tooling.py" in legacy_workflow shell_helpers_start = release_workflow.index(" download_existing() {") @@ -299,7 +299,7 @@ def test_release_workflows_are_pinned_bounded_and_do_not_drift_from_helper( assert smoke_gate in smoke assert smoke.index(smoke_gate) < smoke.index("from c2pa import") assert release_workflow.count('CASTLABS_RELEASE_SMOKE_REQUIRED: "1"') == 2 - assert release_workflow.count("CASTLABS_RELEASE_EXPECTED_VERSION: 0.37.12.dev3") == 2 + assert release_workflow.count("CASTLABS_RELEASE_EXPECTED_VERSION: 0.37.12.dev4") == 2 assert "pytest.skip(" in smoke assert "unittest.skip" not in smoke dynamic_start = smoke.index( @@ -362,7 +362,7 @@ 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.12.dev3" in release_notes + assert "## Version 0.37.12.dev4" in release_notes assert "Dev4 and earlier accepted\n undersized callback outputs" in release_notes assert "breaking strictness\n change" in release_notes @@ -557,7 +557,7 @@ def test_safe_extract_round_trip_and_rejects_links(): def test_wheel_inspection_rejects_unsafe_and_multiple_native_members(): with tempfile.TemporaryDirectory() as temp: root = Path(temp) - wheel = root / "c2pa_python-0.37.12.dev3-py3-none-win_amd64.whl" + wheel = root / "c2pa_python-0.37.12.dev4-py3-none-win_amd64.whl" with zipfile.ZipFile(wheel, "w") as archive: archive.writestr("c2pa/libs/first.dll", b"one") archive.writestr("c2pa/libs/second.dll", b"two") @@ -568,7 +568,7 @@ def test_wheel_inspection_rejects_unsafe_and_multiple_native_members(): else: raise AssertionError("accepted wheel with multiple native libraries") - unsafe = root / "c2pa_python-0.37.12.dev3-py3-none-manylinux_2_28_x86_64.whl" + unsafe = root / "c2pa_python-0.37.12.dev4-py3-none-manylinux_2_28_x86_64.whl" with zipfile.ZipFile(unsafe, "w") as archive: archive.writestr("../libc2pa_c.so", b"unsafe") try: diff --git a/tests/test_unit_tests.py b/tests/test_unit_tests.py index b4595993..7cba06f1 100644 --- a/tests/test_unit_tests.py +++ b/tests/test_unit_tests.py @@ -10906,3 +10906,8 @@ def test_validation_time_is_reported_in_crjson(self): document = json.loads(reader.crjson()) results = document["manifests"][0]["validationResults"] self.assertEqual(results["validationTime"], "2027-01-15T08:00:00Z") + # crJSON 2.4: the validator version, and no post-2.4 manifest fields. + self.assertEqual(results["specVersion"], "2.4.0") + for manifest in document["manifests"]: + self.assertNotIn("isUpdateManifest", manifest) + self.assertNotIn("isCompressedManifest", manifest) From 563e37b94580dd8da8db08a43b9d411734ff9755 Mon Sep 17 00:00:00 2001 From: Michael Stattmann Date: Thu, 15 Oct 2026 23:13:10 +0200 Subject: [PATCH 6/7] chore(release): prepare 0.37.13.dev2 on qualified native afd6fffe Pin native castlabs/c2pa-rs afd6fffe5a066e0b5b1a14f3f8c7ac189e7f0c95 (the 1.0.8 trusted VSI native line merged with the conformance line; workspace 0.92.0-dev, Cargo.lock SHA-256 f7022b3e..., Rust 1.96.0) and release 0.37.13.dev2 from feat/trusted-vsi-functional as castlabs-v0.37.13.dev2. castlabs-v0.37.13.dev1 (run 38786913569) was not published: its installed-wheel test jobs lacked pytest. The paired qualification uses the same native commit; functional build identities dev1 and older are rejected. --- .github/workflows/castlabs-vsi-pypi.yml | 38 ++++++++-------- .github/workflows/castlabs-vsi-release.yml | 42 +++++++++--------- .github/workflows/trusted-vsi-paired.yml | 8 ++-- README.md | 16 ++++--- docs/release-notes.md | 44 ++++++++++++++++--- docs/trusted-vsi-python-contract.md | 19 ++++---- pyproject.toml | 2 +- release/castlabs-release-evidence.schema.json | 4 +- release/castlabs-vsi-inputs.lock.json | 6 +-- release/castlabs-vsi-inputs.schema.json | 6 +-- scripts/castlabs_release.py | 18 ++++---- src/c2pa/c2pa.py | 2 +- tests/test_castlabs_release_smoke.py | 2 +- tests/test_castlabs_release_tooling.py | 32 ++++++++------ tests/test_trusted_vsi_build.py | 22 +++++----- 15 files changed, 153 insertions(+), 108 deletions(-) diff --git a/.github/workflows/castlabs-vsi-pypi.yml b/.github/workflows/castlabs-vsi-pypi.yml index 45c81f59..724c73df 100644 --- a/.github/workflows/castlabs-vsi-pypi.yml +++ b/.github/workflows/castlabs-vsi-pypi.yml @@ -6,11 +6,11 @@ on: release_tag: description: Existing immutable Castlabs prerelease tag required: true - default: castlabs-v0.37.13.dev1 + default: castlabs-v0.37.13.dev2 type: string concurrency: - group: castlabs-vsi-pypi-0.37.13.dev1 + group: castlabs-vsi-pypi-0.37.13.dev2 cancel-in-progress: false permissions: @@ -29,7 +29,7 @@ jobs: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: repository: castlabs/c2pa-python - ref: castlabs-v0.37.13.dev1 + ref: castlabs-v0.37.13.dev2 fetch-depth: 0 path: c2pa-python @@ -45,13 +45,13 @@ jobs: GH_TOKEN: ${{ github.token }} INPUT_RELEASE_TAG: ${{ inputs.release_tag }} run: | - test "${INPUT_RELEASE_TAG}" = castlabs-v0.37.13.dev1 - test "$(python3 c2pa-python/scripts/castlabs_release.py lock-value package.version)" = 0.37.13.dev1 - test "$(gh api repos/castlabs/c2pa-python/releases/tags/castlabs-v0.37.13.dev1 --jq '(.draft == false and .prerelease == true)')" = true + test "${INPUT_RELEASE_TAG}" = castlabs-v0.37.13.dev2 + test "$(python3 c2pa-python/scripts/castlabs_release.py lock-value package.version)" = 0.37.13.dev2 + test "$(gh api repos/castlabs/c2pa-python/releases/tags/castlabs-v0.37.13.dev2 --jq '(.draft == false and .prerelease == true)')" = true SOURCE_SHA=$(git -C c2pa-python rev-parse HEAD) - test "$(git -C c2pa-python rev-list -n 1 castlabs-v0.37.13.dev1)" = "$SOURCE_SHA" + test "$(git -C c2pa-python rev-list -n 1 castlabs-v0.37.13.dev2)" = "$SOURCE_SHA" mkdir -p download dist - gh release download castlabs-v0.37.13.dev1 \ + gh release download castlabs-v0.37.13.dev2 \ --repo castlabs/c2pa-python --dir download python3 c2pa-python/scripts/castlabs_release.py validate-release-assets \ --assets-dir download @@ -63,29 +63,29 @@ jobs: --repo castlabs/c2pa-python \ --signer-workflow castlabs/c2pa-python/.github/workflows/castlabs-vsi-release.yml \ --signer-digest "$SOURCE_SHA" \ - --source-ref refs/tags/castlabs-v0.37.13.dev1 \ + --source-ref refs/tags/castlabs-v0.37.13.dev2 \ --source-digest "$SOURCE_SHA" \ --deny-self-hosted-runners done python3 c2pa-python/scripts/castlabs_release.py validate-evidence \ - --evidence download/c2pa-python-0.37.13.dev1-native-linux-x86_64.tar.gz.evidence.json \ + --evidence download/c2pa-python-0.37.13.dev2-native-linux-x86_64.tar.gz.evidence.json \ --artifact-dir download --source-sha "$SOURCE_SHA" python3 c2pa-python/scripts/castlabs_release.py validate-evidence \ - --evidence download/c2pa-python-0.37.13.dev1-native-windows-x86_64.tar.gz.evidence.json \ + --evidence download/c2pa-python-0.37.13.dev2-native-windows-x86_64.tar.gz.evidence.json \ --artifact-dir download --source-sha "$SOURCE_SHA" python3 c2pa-python/scripts/castlabs_release.py validate-evidence \ - --evidence download/c2pa-python-0.37.13.dev1-py3-none-wheels.tar.gz.evidence.json \ + --evidence download/c2pa-python-0.37.13.dev2-py3-none-wheels.tar.gz.evidence.json \ --artifact-dir download --source-sha "$SOURCE_SHA" python3 c2pa-python/scripts/castlabs_release.py extract \ - --archive download/c2pa-python-0.37.13.dev1-py3-none-wheels.tar.gz \ + --archive download/c2pa-python-0.37.13.dev2-py3-none-wheels.tar.gz \ --destination dist test "$(find dist -maxdepth 1 -name '*.whl' | wc -l)" -eq 2 - cmp dist/c2pa_python-0.37.13.dev1-py3-none-manylinux_2_28_x86_64.whl \ - download/c2pa_python-0.37.13.dev1-py3-none-manylinux_2_28_x86_64.whl - cmp dist/c2pa_python-0.37.13.dev1-py3-none-win_amd64.whl \ - download/c2pa_python-0.37.13.dev1-py3-none-win_amd64.whl + cmp dist/c2pa_python-0.37.13.dev2-py3-none-manylinux_2_28_x86_64.whl \ + download/c2pa_python-0.37.13.dev2-py3-none-manylinux_2_28_x86_64.whl + cmp dist/c2pa_python-0.37.13.dev2-py3-none-win_amd64.whl \ + download/c2pa_python-0.37.13.dev2-py3-none-win_amd64.whl python3 -m pip install packaging==25.0 twine==6.2.0 - python3 -c 'from packaging.version import Version; v=Version("0.37.13.dev1"); assert v.is_prerelease and str(v)=="0.37.13.dev1"' + python3 -c 'from packaging.version import Version; v=Version("0.37.13.dev2"); assert v.is_prerelease and str(v)=="0.37.13.dev2"' python3 -m twine check dist/*.whl - name: Preflight retry-safe PyPI upload set @@ -94,7 +94,7 @@ jobs: run: | STATUS=$(curl --silent --show-error --location \ --output pypi-response.json --write-out '%{http_code}' \ - https://pypi.org/pypi/c2pa-python/0.37.13.dev1/json) + https://pypi.org/pypi/c2pa-python/0.37.13.dev2/json) case "$STATUS" in 200|404) ;; *) echo "unexpected PyPI JSON API status: $STATUS" >&2; exit 1 ;; diff --git a/.github/workflows/castlabs-vsi-release.yml b/.github/workflows/castlabs-vsi-release.yml index 7e30dbca..772a7e50 100644 --- a/.github/workflows/castlabs-vsi-release.yml +++ b/.github/workflows/castlabs-vsi-release.yml @@ -3,11 +3,11 @@ name: Castlabs VSI prerelease on: push: tags: - - castlabs-v0.37.13.dev1 + - castlabs-v0.37.13.dev2 workflow_dispatch: inputs: source_sha: - description: Full Python SHA (feat/trusted-vsi-functional tip for 0.37.13.dev1; paired source for trusted_vsi_only) + description: Full Python SHA (feat/trusted-vsi-functional tip for 0.37.13.dev2; 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.dev1 + group: castlabs-vsi-release-0.37.13.dev2 cancel-in-progress: false permissions: @@ -72,7 +72,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: repository: castlabs/c2pa-rs - ref: 3328b2b440fd07d9f857cbefa3c6d565093425de + ref: afd6fffe5a066e0b5b1a14f3f8c7ac189e7f0c95 fetch-depth: 1 path: c2pa-rs @@ -123,7 +123,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: repository: castlabs/c2pa-rs - ref: 3328b2b440fd07d9f857cbefa3c6d565093425de + ref: afd6fffe5a066e0b5b1a14f3f8c7ac189e7f0c95 fetch-depth: 1 path: c2pa-rs @@ -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.dev1-py3-none-manylinux_2_28_x86_64.whl) ;; + c2pa_python-0.37.13.dev2-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.dev1-native-linux-x86_64.tar.gz + BUNDLE=release-out/c2pa-python-0.37.13.dev2-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 @@ -278,7 +278,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: repository: castlabs/c2pa-rs - ref: 3328b2b440fd07d9f857cbefa3c6d565093425de + ref: afd6fffe5a066e0b5b1a14f3f8c7ac189e7f0c95 fetch-depth: 1 path: c2pa-rs @@ -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.dev1-py3-none-win_amd64.whl") { + if ($wheels[0].Name -ne "c2pa_python-0.37.13.dev2-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.dev1-native-windows-x86_64.tar.gz" + $bundle = "release-out/c2pa-python-0.37.13.dev2-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.dev1 + CASTLABS_RELEASE_EXPECTED_VERSION: 0.37.13.dev2 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.dev1 + CASTLABS_RELEASE_EXPECTED_VERSION: 0.37.13.dev2 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.ref == 'refs/tags/castlabs-v0.37.13.dev1' + if: github.ref == 'refs/tags/castlabs-v0.37.13.dev2' 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.dev1-py3-none-wheels.tar.gz + BUNDLE=assets/c2pa-python-0.37.13.dev2-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.dev1-native-linux-x86_64.tar.gz* assets/ - cp collected/windows/c2pa-python-0.37.13.dev1-native-windows-x86_64.tar.gz* assets/ + cp collected/linux/c2pa-python-0.37.13.dev2-native-linux-x86_64.tar.gz* assets/ + cp collected/windows/c2pa-python-0.37.13.dev2-native-windows-x86_64.tar.gz* assets/ python3 c2pa-python/scripts/castlabs_release.py validate-evidence \ - --evidence assets/c2pa-python-0.37.13.dev1-native-linux-x86_64.tar.gz.evidence.json \ + --evidence assets/c2pa-python-0.37.13.dev2-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.dev1-native-windows-x86_64.tar.gz.evidence.json \ + --evidence assets/c2pa-python-0.37.13.dev2-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.dev1 + TAG=castlabs-v0.37.13.dev2 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.dev1 + TAG=castlabs-v0.37.13.dev2 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.dev1")' > "$1" + --jq '.[] | select(.tag_name == "castlabs-v0.37.13.dev2")' > "$1" return 0 } query_release_id() { diff --git a/.github/workflows/trusted-vsi-paired.yml b/.github/workflows/trusted-vsi-paired.yml index fad74d99..7983e116 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.dev1 + FUNCTIONAL_BUILD_VERSION: 0.37.13.dev2 # The release smoke asserts the installed distribution version. - CASTLABS_RELEASE_EXPECTED_VERSION: 0.37.13.dev1 + CASTLABS_RELEASE_EXPECTED_VERSION: 0.37.13.dev2 steps: - name: Require a full Python source SHA shell: bash @@ -52,9 +52,9 @@ jobs: with: repository: castlabs/c2pa-rs # Revision-3 trusted VSI plus offset-aware fragment verification: - # the same native commit the 0.37.13.dev1 release lock pins. This job + # the same native commit the 0.37.13.dev2 release lock pins. This job # is qualification-only and never produces release artifacts. - ref: 3328b2b440fd07d9f857cbefa3c6d565093425de + ref: afd6fffe5a066e0b5b1a14f3f8c7ac189e7f0c95 path: paired-rust - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: diff --git a/README.md b/README.md index 537eaab6..cfbda439 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ make build-from-source C2PA_RS_PATH=$C2PA_RS_PATH The [trusted-processor API](docs/usage.md#trusted-processor-vsi-unreleased-functional-api) is separate from complete-buffer VSI and immutable dev5. It is first released in -`0.37.13.dev1`. Its probes are true only on a native library reporting trusted +`0.37.13.dev2`. Its probes are true only on a native library reporting trusted contract revision 3 and capability mask 63 (the pinned release native does); otherwise every probe is false and session construction/operations fail before side effects. Expert signing accepts only exact `Sig_structure` bytes and returns @@ -118,8 +118,8 @@ library (`-k "not paired"`). Separately, both it and the dedicated workflow use Linux/Windows source builds and the full focused `tests/test_trusted_vsi_api.py` with `C2PA_TRUSTED_VSI_ABI_REQUIRED=1`. Missing reduced ABI symbols fail rather than skip. The paired Rust source is pinned to -`castlabs/c2pa-rs@3328b2b440fd07d9f857cbefa3c6d565093425de`, the same commit as -the 0.37.13.dev1 release lock; the job logs resolved Python/Rust SHAs. The +`castlabs/c2pa-rs@afd6fffe5a066e0b5b1a14f3f8c7ac189e7f0c95`, the same commit as +the 0.37.13.dev2 release lock; the job logs resolved Python/Rust SHAs. The paired job is qualification only and is not release-artifact evidence. For the dedicated workflow, manually select the trusted-API workflow revision, @@ -147,7 +147,7 @@ imports (`python setup.py egg_info`, without a native download or wheel build). ### Immutable Castlabs VSI prerelease process -The dedicated [`Castlabs VSI prerelease`](.github/workflows/castlabs-vsi-release.yml) workflow builds version `0.37.13.dev1` 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 prerelease`](.github/workflows/castlabs-vsi-release.yml) workflow builds version `0.37.13.dev2` 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. @@ -157,9 +157,11 @@ The immutable `castlabs-v0.37.8.dev3` tag records failed prerelease workflow run The published `castlabs-v0.37.8.dev4` prerelease and its tag are immutable historical release artifacts and remain unchanged. Dev4 used the earlier maximum-size DynamicAssertion callback contract, which accepted outputs shorter than `reserve_size`. Dev5 intentionally makes that contract stricter: exact callback sizing is enforced for every signing path, and both undersized and oversized outputs are rejected. +The immutable `castlabs-v0.37.13.dev1` tag (source `78771fa`, native `3328b2b4`) records failed prerelease workflow run `38786913569`. The Linux build succeeded, but every Linux installed-wheel test job (Python 3.10 through 3.13) failed with `No module named pytest`: pytest is no longer a runtime dependency of the wheel (upstream `4d909cf`) and the clean test venv did not install it. The Windows jobs and the release job were cancelled. No dev1 draft or GitHub release was created, and the tag remains unchanged. Dev2 installs `pytest==8.4.1` after the wheel in both the Linux and Windows installed-wheel steps, and pairs with native `afd6fffe`, which merges the Conformance Program 0.2 / C2PA 2.4 line into the 1.0.8 trusted VSI native line. + Release controls: -- Configure `castlabs-v0.37.13.dev1` as a protected tag and require approval for the `castlabs-vsi-release` environment. +- Configure `castlabs-v0.37.13.dev2` as a protected tag and require approval for the `castlabs-vsi-release` environment. - 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. @@ -167,9 +169,9 @@ Release controls: - [`Promote Castlabs VSI prerelease to PyPI`](.github/workflows/castlabs-vsi-pypi.yml) is a separate manual workflow protected by the `pypipublish` environment. It requires the finalized GitHub prerelease and verifies every wheel, bundle, evidence document, feature report, and checksum against provenance from the exact dedicated signer workflow, tag, and source SHA. Checksums and strict evidence then validate the final-wheel metadata and embedded native digests before promotion. - PyPI does not provide a multi-file transaction. Promotion therefore queries the version JSON API first. A fresh release uploads both platform wheels together; a retry accepts an existing wheel only when PyPI's SHA-256 exactly matches the local attested wheel, then uploads all missing wheels together. A mismatched or unexpected PyPI file fails. This policy intentionally publishes no source distribution for the two-platform prerelease and safely recovers from an unavoidable prior partial wheel upload. -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, then remove only that draft with `gh release delete castlabs-v0.37.13.dev1 --repo castlabs/c2pa-python --yes` before rerunning the complete protected-tag workflow. Never use clobber to repair a mismatch. +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, then remove only that draft with `gh release delete castlabs-v0.37.13.dev2 --repo castlabs/c2pa-python --yes` before rerunning the complete protected-tag workflow. Never 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.dev1`. +`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.dev2`. 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 345e3a94..a5da5177 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,11 +1,45 @@ # Release notes -## Version 0.37.13.dev1 +## Version 0.37.13.dev2 -Castlabs VSI prerelease (tag `castlabs-v0.37.13.dev1`, released from -`feat/trusted-vsi-functional`). It is the first release of the trusted-processor -VSI line. The immutable `0.37.8.dev5` release inputs and artifacts -(`feat/live-video-vsi`) stay unchanged. +Castlabs VSI prerelease (tag `castlabs-v0.37.13.dev2`, released from +`feat/trusted-vsi-functional`). It is the first published release of the +trusted-processor VSI line and true-merges the Conformance Program 0.2 / +C2PA 2.4 line (`feat/conformance-validation-v02`, published as +`0.37.12.dev3`/`0.37.12.dev4`) into it. The immutable `0.37.8.dev5` and +`0.37.12.dev*` release inputs and artifacts stay unchanged. + +- Native `castlabs/c2pa-rs` `afd6fffe5a066e0b5b1a14f3f8c7ac189e7f0c95` + (workspace `0.92.0-dev`, `Cargo.lock` SHA-256 + `f7022b3e05c2dc2080946064b386b293b29c3a60ca9daf344e1b4e3fc398c370`, + unchanged from dev1), built with Rust `1.96.0`. It is the 1.0.8 trusted VSI + native line (`3328b2b4`) merged with the native conformance line. +- `Reader.crjson()` emits crJSON 2.4: `validationResults.specVersion` reports + `2.4.0`, the post-2.4 `isUpdateManifest` / `isCompressedManifest` manifest + fields are not emitted, and `specVersion` is placed per 2.4 in + `claim_generator_info`. +- `Reader.crjson()` is checked: the native export returns NULL with an error + instead of `"{}"`, so exporter failures raise `C2paError`. +- `verify.validation_time` (RFC 3339) selects the evaluation instant for + certificate, chain and OCSP decisions and the reported `validationTime`. + Malformed values are rejected when the context is built. +- Purpose-scoped trust anchors (`trust.anchors` with `trust_kind` + `manifest`/`tsa`/`cawg`) are always strict on this line: an anchor + authorizes only its declared purpose. The conformance line's + `verify.strict_trust_purposes` setting (and its non-strict fallback) does + not exist here. +- The installed-wheel release tests install `pytest==8.4.1` after the wheel + on Linux and Windows; pytest is no longer a runtime dependency of the wheel + (upstream `4d909cf`). + +### 0.37.13.dev1 (not released) + +Tag `castlabs-v0.37.13.dev1` (source `78771fa`, native `3328b2b4`) ran +workflow `38786913569`: the Linux build succeeded but every Linux +installed-wheel test job failed with `No module named pytest` because the +clean test venv did not install pytest; the Windows and release jobs were +cancelled. No dev1 draft or GitHub release was created and the tag remains +unchanged. Everything below ships first in dev2. - Native `castlabs/c2pa-rs` `3328b2b440fd07d9f857cbefa3c6d565093425de` (workspace `0.92.0-dev`, `Cargo.lock` SHA-256 diff --git a/docs/trusted-vsi-python-contract.md b/docs/trusted-vsi-python-contract.md index cd3c4f51..600df64f 100644 --- a/docs/trusted-vsi-python-contract.md +++ b/docs/trusted-vsi-python-contract.md @@ -3,11 +3,12 @@ Status: integrated, unreleased PR4 review changes 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 Python identity is `0.37.13.dev1`, +`docs/trusted-vsi-native-contract.md`. The Python identity is `0.37.13.dev2`, prepared as the Castlabs VSI prerelease on native -`3328b2b440fd07d9f857cbefa3c6d565093425de`; immutable dev5 release inputs and +`afd6fffe5a066e0b5b1a14f3f8c7ac189e7f0c95`; immutable dev5 release inputs and artifacts are unchanged. The paired source workflow pins the same native commit -(previously `a6d4cdcc05638ee8dd0afce7fa5c850d7031a80c`) for revision-3 +(previously `3328b2b440fd07d9f857cbefa3c6d565093425de` for the unpublished +0.37.13.dev1 and `a6d4cdcc05638ee8dd0afce7fa5c850d7031a80c` before it) for revision-3 qualification. Select a reviewed local library with `C2PA_LIBRARY_NAME` for integration tests. Version `0.92.0-dev` and mask 63 do not establish contract identity; Python now requires the explicit native contract-revision probe. @@ -243,14 +244,14 @@ CBOR, BMFF or validation results. ` first requires all probes true against that library, then builds a NON-PUBLISHING wheel and sdist in a temporary staging copy with `FUNCTIONAL_BUILD_VERSION` (default: the checkout's source identity -`0.37.13.dev1`; rejects `0.37.8.*`, dev5, release versions and anything older -than the source identity, including `0.37.13.dev0` and the historical +`0.37.13.dev2`; rejects `0.37.8.*`, dev5, release versions and anything older +than the source identity, including `0.37.13.dev1`, `0.37.13.dev0` and the historical `0.37.9.dev0`). `C2PA_SOURCE_BUILD_VERSION=0.92.0-dev` identifies the native library only. The release lock and tooling now match this source -(`castlabs-v0.37.13.dev1`); the immutable dev5 release remains historical. +(`castlabs-v0.37.13.dev2`); the immutable dev5 release remains historical. `scripts/qualify_trusted_vsi_functional.py --wheel --venv ---version 0.37.13.dev1` installs the wheel into an isolated venv, strips +--version 0.37.13.dev2` 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`, @@ -263,8 +264,8 @@ Paired tests require the full native library and FAIL under `C2PA_TRUSTED_VSI_ABI_REQUIRED=1` (all Linux/Windows qualification jobs); they 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 -`3328b2b440fd07d9f857cbefa3c6d565093425de`, the same commit as the -`castlabs-v0.37.13.dev1` release lock (trusted contract revision 3, mask 63, +`afd6fffe5a066e0b5b1a14f3f8c7ac189e7f0c95`, the same commit as the +`castlabs-v0.37.13.dev2` release 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 diff --git a/pyproject.toml b/pyproject.toml index 3c49910a..827f4385 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "c2pa-python" -version = "0.37.13.dev1" +version = "0.37.13.dev2" 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 6a3d73b8..18d07134 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.dev1"}, + "version": {"const": "0.37.13.dev2"}, "file": {"type": "string", "minLength": 1}, "size": {"type": "integer", "minimum": 1}, "sha256": {"$ref": "#/$defs/sha256"}, @@ -49,7 +49,7 @@ "properties": { "repository": {"const": "castlabs/c2pa-rs"}, "url": {"const": "https://github.com/castlabs/c2pa-rs.git"}, - "commit": {"const": "3328b2b440fd07d9f857cbefa3c6d565093425de"}, + "commit": {"const": "afd6fffe5a066e0b5b1a14f3f8c7ac189e7f0c95"}, "version": {"const": "0.92.0-dev"}, "cargoLockSha256": {"const": "f7022b3e05c2dc2080946064b386b293b29c3a60ca9daf344e1b4e3fc398c370"} } diff --git a/release/castlabs-vsi-inputs.lock.json b/release/castlabs-vsi-inputs.lock.json index cfa8d565..5e8a982c 100644 --- a/release/castlabs-vsi-inputs.lock.json +++ b/release/castlabs-vsi-inputs.lock.json @@ -2,18 +2,18 @@ "schemaVersion": 1, "package": { "name": "c2pa-python", - "version": "0.37.13.dev1" + "version": "0.37.13.dev2" }, "pythonSource": { "repository": "castlabs/c2pa-python", "url": "https://github.com/castlabs/c2pa-python.git", "releaseBranch": "feat/trusted-vsi-functional", - "releaseTag": "castlabs-v0.37.13.dev1" + "releaseTag": "castlabs-v0.37.13.dev2" }, "rustSource": { "repository": "castlabs/c2pa-rs", "url": "https://github.com/castlabs/c2pa-rs.git", - "commit": "3328b2b440fd07d9f857cbefa3c6d565093425de", + "commit": "afd6fffe5a066e0b5b1a14f3f8c7ac189e7f0c95", "version": "0.92.0-dev", "cargoLockSha256": "f7022b3e05c2dc2080946064b386b293b29c3a60ca9daf344e1b4e3fc398c370" }, diff --git a/release/castlabs-vsi-inputs.schema.json b/release/castlabs-vsi-inputs.schema.json index e0460536..6e0a84bd 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.dev1"}} + "properties": {"name": {"const": "c2pa-python"}, "version": {"const": "0.37.13.dev2"}} }, "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.dev1"} + "releaseTag": {"const": "castlabs-v0.37.13.dev2"} } }, "rustSource": { @@ -48,7 +48,7 @@ "properties": { "repository": {"const": "castlabs/c2pa-rs"}, "url": {"const": "https://github.com/castlabs/c2pa-rs.git"}, - "commit": {"const": "3328b2b440fd07d9f857cbefa3c6d565093425de"}, + "commit": {"const": "afd6fffe5a066e0b5b1a14f3f8c7ac189e7f0c95"}, "version": {"const": "0.92.0-dev"}, "cargoLockSha256": {"const": "f7022b3e05c2dc2080946064b386b293b29c3a60ca9daf344e1b4e3fc398c370"} } diff --git a/scripts/castlabs_release.py b/scripts/castlabs_release.py index a093f741..35995525 100644 --- a/scripts/castlabs_release.py +++ b/scripts/castlabs_release.py @@ -26,24 +26,26 @@ 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.dev1" -RUST_COMMIT = "3328b2b440fd07d9f857cbefa3c6d565093425de" +RELEASE_VERSION = "0.37.13.dev2" +RUST_COMMIT = "afd6fffe5a066e0b5b1a14f3f8c7ac189e7f0c95" CARGO_LOCK_SHA256 = "f7022b3e05c2dc2080946064b386b293b29c3a60ca9daf344e1b4e3fc398c370" RUST_TOOLCHAIN = "1.96.0" MANYLINUX_IMAGE = "quay.io/pypa/manylinux_2_28_x86_64" MANYLINUX_DIGEST = ( "sha256:0d9c2a66a745961947a8cecbe217ca0a7ee7a5849ba2517f20f9581d18444977" ) -RELEASE_TAG = "castlabs-v0.37.13.dev1" +RELEASE_TAG = "castlabs-v0.37.13.dev2" RELEASE_NAME = f"Castlabs c2pa-python {RELEASE_VERSION} (VSI)" RELEASE_BODY_MARKER = f"castlabs-vsi-release:{RELEASE_VERSION}" RELEASE_IDENTITY_TEXT = f"Castlabs c2pa-python {RELEASE_VERSION} immutable prerelease" RELEASE_BODY = ( f"{RELEASE_BODY_MARKER}\n\n" f"{RELEASE_IDENTITY_TEXT}. Trusted-processor VSI line (native contract " - "revision 3, capability mask 63) on castlabs/c2pa-rs 3328b2b4 (0.92.0-dev, " - "Rust 1.96.0): offset-aware single-file fMP4 fragment verification, default " - "EKU trust-policy fix and patched event-listener/rustls/quinn-proto. See " + "revision 3, capability mask 63) on castlabs/c2pa-rs afd6fffe (0.92.0-dev, " + "Rust 1.96.0), merged with the Conformance Program 0.2 / C2PA 2.4 line: " + "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. Successor to dev1 run 38786913569; no dev1 release. See " "the attached schema-2 evidence and SHA-256 sidecars." ) @@ -220,12 +222,12 @@ def validate_lock(lock: dict[str, Any]) -> None: if set(lock) != required or lock.get("schemaVersion") != 1: fail("release lock does not match schema version 1") if lock["package"] != {"name": "c2pa-python", "version": RELEASE_VERSION}: - fail("release lock package identity is not 0.37.13.dev1") + fail("release lock package identity is not 0.37.13.dev2") if lock["pythonSource"] != { "repository": "castlabs/c2pa-python", "url": "https://github.com/castlabs/c2pa-python.git", "releaseBranch": "feat/trusted-vsi-functional", - "releaseTag": "castlabs-v0.37.13.dev1", + "releaseTag": "castlabs-v0.37.13.dev2", }: fail("unexpected c2pa-python release source policy") rust = lock["rustSource"] diff --git a/src/c2pa/c2pa.py b/src/c2pa/c2pa.py index 60d5e05a..654330ab 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.dev1 +# Version: 0.37.13.dev2 import ctypes import enum diff --git a/tests/test_castlabs_release_smoke.py b/tests/test_castlabs_release_smoke.py index 95c1fd33..8dd940a6 100644 --- a/tests/test_castlabs_release_smoke.py +++ b/tests/test_castlabs_release_smoke.py @@ -46,7 +46,7 @@ FIXTURES = Path(__file__).parent / "fixtures" -EXPECTED_VERSION = os.environ.get("CASTLABS_RELEASE_EXPECTED_VERSION", "0.37.13.dev1") +EXPECTED_VERSION = os.environ.get("CASTLABS_RELEASE_EXPECTED_VERSION", "0.37.13.dev2") REQUIRED_CAPABILITIES = { "dynamic assertions": has_dynamic_assertions, "fragmented files": has_fragmented_files, diff --git a/tests/test_castlabs_release_tooling.py b/tests/test_castlabs_release_tooling.py index a2915b6f..09f7cadf 100644 --- a/tests/test_castlabs_release_tooling.py +++ b/tests/test_castlabs_release_tooling.py @@ -50,10 +50,10 @@ def _posix_bash() -> str: def test_prerelease_version_is_consistent(): # The release identity is consistent across the tooling, lock and source, - # so validate-sources accepts this checkout under the 0.37.13.dev1 lock. - assert release.RELEASE_VERSION == "0.37.13.dev1" - assert release.RELEASE_TAG == "castlabs-v0.37.13.dev1" - assert release.load_lock()["package"]["version"] == "0.37.13.dev1" + # so validate-sources accepts this checkout under the 0.37.13.dev2 lock. + assert release.RELEASE_VERSION == "0.37.13.dev2" + assert release.RELEASE_TAG == "castlabs-v0.37.13.dev2" + assert release.load_lock()["package"]["version"] == "0.37.13.dev2" source = release.project_version(ROOT) assert source == release.RELEASE_VERSION first_line = ( @@ -68,7 +68,7 @@ def test_release_lock_and_schemas_are_valid_json(): assert lock["rustSource"]["commit"] == release.RUST_COMMIT assert lock["rustSource"]["cargoLockSha256"] == release.CARGO_LOCK_SHA256 assert lock["rustToolchain"]["channel"] == "1.96.0" - assert release.RUST_COMMIT == "3328b2b440fd07d9f857cbefa3c6d565093425de" + assert release.RUST_COMMIT == "afd6fffe5a066e0b5b1a14f3f8c7ac189e7f0c95" assert release.CARGO_LOCK_SHA256 == ( "f7022b3e05c2dc2080946064b386b293b29c3a60ca9daf344e1b4e3fc398c370" ) @@ -195,7 +195,7 @@ def test_release_workflows_are_pinned_bounded_and_do_not_drift_from_helper( in release_workflow ) assert ( - "c2pa_python-0.37.13.dev1-py3-none-manylinux_2_28_x86_64.whl" in release_workflow + "c2pa_python-0.37.13.dev2-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") < ( @@ -246,7 +246,7 @@ def test_release_workflows_are_pinned_bounded_and_do_not_drift_from_helper( "python3 -m pytest -q c2pa-python/tests/test_castlabs_release_tooling.py" in (release_workflow) ) - assert "if: github.ref == 'refs/tags/castlabs-v0.37.13.dev1'" in release_workflow + assert "if: github.ref == 'refs/tags/castlabs-v0.37.13.dev2'" in release_workflow assert "-F draft=true -F prerelease=true" in release_workflow assert "--clobber" not in release_workflow assert "repos/castlabs/c2pa-python/releases" in release_workflow @@ -320,9 +320,9 @@ def test_release_workflows_are_pinned_bounded_and_do_not_drift_from_helper( in pypi_workflow ) assert '--signer-digest "$SOURCE_SHA"' in pypi_workflow - assert "--source-ref refs/tags/castlabs-v0.37.13.dev1" in pypi_workflow + assert "--source-ref refs/tags/castlabs-v0.37.13.dev2" in pypi_workflow assert '--source-digest "$SOURCE_SHA"' in pypi_workflow - assert "pypi.org/pypi/c2pa-python/0.37.13.dev1/json" in pypi_workflow + assert "pypi.org/pypi/c2pa-python/0.37.13.dev2/json" in pypi_workflow assert "pypi-plan" in pypi_workflow assert "packages-dir: publish-dist/" in pypi_workflow assert "if: steps.pypi.outputs.upload == 'true'" in pypi_workflow @@ -351,7 +351,7 @@ def test_release_workflows_are_pinned_bounded_and_do_not_drift_from_helper( assert "manual legacy publishing accepts final X.Y.Z versions only" in ( legacy_release ) - assert 'test "$VERSION" != 0.37.13.dev1' not in legacy_workflow + assert 'test "$VERSION" != 0.37.13.dev2' not in legacy_workflow assert legacy_workflow.count("final X.Y.Z versions only") == 2 assert "tests/test_castlabs_release_tooling.py" in legacy_workflow shell_helpers_start = release_workflow.index(" download_existing() {") @@ -381,7 +381,7 @@ def test_release_workflows_are_pinned_bounded_and_do_not_drift_from_helper( assert smoke_gate in smoke assert smoke.index(smoke_gate) < smoke.index("from c2pa import") assert release_workflow.count('CASTLABS_RELEASE_SMOKE_REQUIRED: "1"') == 2 - assert release_workflow.count("CASTLABS_RELEASE_EXPECTED_VERSION: 0.37.13.dev1") == 2 + assert release_workflow.count("CASTLABS_RELEASE_EXPECTED_VERSION: 0.37.13.dev2") == 2 assert release_workflow.count("C2PA_SOURCE_BUILD_VERSION: 0.92.0-dev") == 2 # pytest is not a wheel runtime dependency: both clean installed-wheel # environments install it after the wheel, before running pytest. @@ -445,6 +445,9 @@ def test_release_workflows_are_pinned_bounded_and_do_not_drift_from_helper( assert "castlabs-v0.37.8.dev3" not in pypi_workflow assert "castlabs-v0.37.8.dev4" not in release_workflow assert "castlabs-v0.37.8.dev4" not in pypi_workflow + # 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 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 @@ -461,7 +464,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.dev1" in release_notes + assert "## Version 0.37.13.dev2" 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 @@ -656,7 +660,7 @@ def test_safe_extract_round_trip_and_rejects_links(): def test_wheel_inspection_rejects_unsafe_and_multiple_native_members(): with tempfile.TemporaryDirectory() as temp: root = Path(temp) - wheel = root / "c2pa_python-0.37.13.dev1-py3-none-win_amd64.whl" + wheel = root / "c2pa_python-0.37.13.dev2-py3-none-win_amd64.whl" with zipfile.ZipFile(wheel, "w") as archive: archive.writestr("c2pa/libs/first.dll", b"one") archive.writestr("c2pa/libs/second.dll", b"two") @@ -667,7 +671,7 @@ def test_wheel_inspection_rejects_unsafe_and_multiple_native_members(): else: raise AssertionError("accepted wheel with multiple native libraries") - unsafe = root / "c2pa_python-0.37.13.dev1-py3-none-manylinux_2_28_x86_64.whl" + unsafe = root / "c2pa_python-0.37.13.dev2-py3-none-manylinux_2_28_x86_64.whl" with zipfile.ZipFile(unsafe, "w") as archive: archive.writestr("../libc2pa_c.so", b"unsafe") try: diff --git a/tests/test_trusted_vsi_build.py b/tests/test_trusted_vsi_build.py index 704a43a0..587a4161 100644 --- a/tests/test_trusted_vsi_build.py +++ b/tests/test_trusted_vsi_build.py @@ -15,7 +15,7 @@ SPEC.loader.exec_module(functional_build) -@pytest.mark.parametrize("version", ["0.37.13.dev1", "0.37.13.dev2", "0.38.0.dev1", "1.0.dev0+local"]) +@pytest.mark.parametrize("version", ["0.37.13.dev2", "0.37.13.dev3", "0.38.0.dev1", "1.0.dev0+local"]) def test_new_functional_development_version(version): assert functional_build.functional_version(version) == version @@ -24,7 +24,9 @@ def test_new_functional_development_version(version): "0.37.8.dev5", "0.37.8.dev6", "0.37.8.dev5+functional", "0.37.7.dev1", "0.37.9", "0.38.0rc1", "invalid", # Historical functional qualification identities are older than the source. - "0.37.9.dev0", "0.37.12.dev0", "0.37.13.dev0", + "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", ]) def test_functional_build_rejects_release_or_dev5_identity(version): with pytest.raises(ValueError): @@ -39,9 +41,9 @@ def test_staged_sdist_uses_new_version_without_touching_dev5_checkout(tmp_path): before = {name: (ROOT / name).read_bytes() for name in ("pyproject.toml", "src/c2pa/c2pa.py")} stage = tmp_path / "stage" stage.mkdir() - functional_build.stage_source(stage, "0.37.13.dev2") - assert 'version = "0.37.13.dev2"' in (stage / "pyproject.toml").read_text() - assert "# Version: 0.37.13.dev2" in (stage / "src/c2pa/c2pa.py").read_text() + functional_build.stage_source(stage, "0.37.13.dev3") + assert 'version = "0.37.13.dev3"' in (stage / "pyproject.toml").read_text() + assert "# Version: 0.37.13.dev3" in (stage / "src/c2pa/c2pa.py").read_text() assert not (stage / "src/c2pa/libs").exists() subprocess.run([sys.executable, "setup.py", "-q", "sdist", "--dist-dir", str(tmp_path / "dist")], cwd=stage, check=True, capture_output=True) @@ -50,12 +52,12 @@ def test_staged_sdist_uses_new_version_without_touching_dev5_checkout(tmp_path): (sdist,) = (tmp_path / "dist").iterdir() name, version = parse_sdist_filename(sdist.name) assert canonicalize_name(name) == "c2pa-python" - assert version == Version("0.37.13.dev2") + assert version == Version("0.37.13.dev3") root = sdist.name[:-len(".tar.gz")] with tarfile.open(sdist) as archive: names = archive.getnames() info = archive.extractfile(f"{root}/PKG-INFO").read().decode() - assert "Version: 0.37.13.dev2" in info.splitlines() + assert "Version: 0.37.13.dev3" in info.splitlines() for member in ("scripts/build_trusted_vsi_functional.py", "scripts/qualify_trusted_vsi_functional.py", "docs/trusted-vsi-python-contract.md", "src/c2pa/c2pa.py"): @@ -64,13 +66,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.dev1"' in before["pyproject.toml"].decode() + assert 'version = "0.37.13.dev2"' 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.dev1" - assert functional_build.functional_version(functional_build.source_version()) == "0.37.13.dev1" + assert functional_build.source_version() == "0.37.13.dev2" + assert functional_build.functional_version(functional_build.source_version()) == "0.37.13.dev2" def test_probe_script_requires_every_functional_capability(): From e87d3e335c6dab7395509793c176d7f57c1956a2 Mon Sep 17 00:00:00 2001 From: Michael Stattmann Date: Thu, 15 Oct 2026 23:32:05 +0200 Subject: [PATCH 7/7] test: run native validation-time tests only on the Castlabs source build The upstream prebuilt library used by build.yml has no verify.validation_time or crJSON 2.4. Document that strict_trust_purposes is accepted and ignored. --- docs/release-notes.md | 3 ++- tests/test_unit_tests.py | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index a5da5177..71162078 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -27,7 +27,8 @@ C2PA 2.4 line (`feat/conformance-validation-v02`, published as `manifest`/`tsa`/`cawg`) are always strict on this line: an anchor authorizes only its declared purpose. The conformance line's `verify.strict_trust_purposes` setting (and its non-strict fallback) does - not exist here. + not exist here: the key is accepted and silently ignored, so anchors must + carry the correct `trust_kind`. - The installed-wheel release tests install `pytest==8.4.1` after the wheel on Linux and Windows; pytest is no longer a runtime dependency of the wheel (upstream `4d909cf`). diff --git a/tests/test_unit_tests.py b/tests/test_unit_tests.py index 77a899f7..210fa2fe 100644 --- a/tests/test_unit_tests.py +++ b/tests/test_unit_tests.py @@ -11181,13 +11181,21 @@ def test_ed25519_sign_with_empty_data_raises(self): c2pa_module.ed25519_sign(b"", "not a key") +# Validation time and crJSON 2.4 exist only in the Castlabs source-built native +# (C2PA_SOURCE_BUILD_VERSION set by the paired/release jobs), not in the upstream +# prebuilt library that build.yml downloads. +_CASTLABS_SOURCE_NATIVE = bool(os.environ.get("C2PA_SOURCE_BUILD_VERSION")) + + class TestConformanceValidationSettings(unittest.TestCase): """Native caller-controlled validation time and checked crJSON.""" + @unittest.skipUnless(_CASTLABS_SOURCE_NATIVE, "requires the Castlabs source-built native") def test_invalid_validation_time_is_rejected(self): with self.assertRaises(Exception): Context.from_dict({"verify": {"validation_time": "2027-01-15 08:00:00"}}) + @unittest.skipUnless(_CASTLABS_SOURCE_NATIVE, "requires the Castlabs source-built native") def test_validation_time_is_reported_in_crjson(self): context = Context.from_dict({ "verify": {"validation_time": "2027-01-15T10:00:00+02:00"},