Development¶
Python 3.12+, uv for environments and poe for tasks. Django and django-ninja serve the API, c2pa-python (c2pa-rs) reads Manifest Stores, iscc-core and iscc-crypto build and sign declarations.
Setup¶
uv sync # dev and docs dependencies
uv run prek install # git hooks: ruff, mdformat, lockfile, workflow, OpenAPI, migrations and type checks
cp .env.example .env # local settings for the dev server
uv run poe migrate
uv run poe serve # http://127.0.0.1:45470
Without ISCC_C2PA_STORE_SECKEY and ISCC_C2PA_STORE_HUB_URL in .env the dev server stores and finds manifests
but does not declare them. To declare against the testnet hub, create a key with uvx iscc-crypto keygen and set
ISCC_C2PA_STORE_HUB_URL=https://staging.iscc.id; the hub then sees the controller did:web:127.0.0.1%3A45470.
Tasks¶
| Command | What it does |
|---|---|
uv run poe all |
Format, type check and test (the local loop) |
uv run poe ci |
Lint, type check, test and audit dependencies, changing nothing |
uv run poe format |
ruff format and fix, mdformat for README.md, CLAUDE.md, SECURITY.md, docs/ |
uv run poe lint |
Check formatting, lint rules and missing migrations |
uv run poe typecheck |
pyright |
uv run poe test |
Tests with the 100% branch coverage gate |
uv run poe audit |
Check locked dependencies for known vulnerabilities |
uv run poe migrate |
Apply migrations to the local dev database (settings from .env) |
uv run poe serve |
Dev server on http://127.0.0.1:45470 (settings from .env) |
uv run poe conformance |
C2PA conformance harness against the dev server (needs Node) |
uv run poe codegen |
Generate openapi.json and the pydantic models from openapi.yaml |
uv run poe sync-c2pa <dir> |
Copy the implemented subset of the C2PA OpenAPI from a specs-core checkout |
uv run poe vendor-c2pa |
Refresh the vendored C2PA Soft Binding Algorithm List and trust list |
uv run poe docs-serve |
Documentation preview on http://127.0.0.1:45471 |
uv run poe docs-build |
Build the documentation site into site/ |
uv run poe docs-check |
Check the built site against the ISCC theme rules |
API contract¶
The API is spec-first. iscc_c2pa_store/openapi/openapi.yaml is written by hand; its c2pa.* schemas are
references into c2pa-sbr.json, a verbatim subset of the C2PA Soft Binding Resolution API definition.
- Change
openapi.yaml. - Run
uv run poe codegen: it writesopenapi.jsonand regenerates the pydantic models iniscc_c2pa_store/schema/. - Change the code.
api.pymaps routes to HTTP only; the logic lives instore.pyand in the pure modules, which never import Django.
Never edit schema/, c2pa-sbr.json or vendor/ by hand. After uv run poe sync-c2pa, the git diff of
c2pa-sbr.json shows what changed upstream. Invalid parameters and bodies answer 400, never 422, as the C2PA
OpenAPI documents.
Test data¶
The tests use real data, no mocks of the store's own code:
- Manifest Stores are signed by c2pa-python during each test session with a throwaway certificate chain (a CA and an ES256 signer). Tests that add the CA to the trust anchors cover trusted uploads, the others untrusted ones. Soft bindings carry the IEP-0020 test vectors or fresh ISCC-UNITs.
- Manifest Stores written by c2pa-rs in
tests/data/: one without soft bindings, and one with a CAWG identity assertion, extracted from the c2pa-rs test fixtureC_with_CAWG_data.jpg(MIT or Apache-2.0). Its signing credential has expired, so it is refused, but without anycawg.*validation code. - A fake ISCC hub replaces the network: it checks the signature of every IsccNote and IsccNoteDelete, requires
X-Force-Declaration, issues ISCC-IDs, and can refuse, fail or go down to exercise every declaration status.
Quality gates¶
Every pull request and every push to main runs:
- the prek hooks: ruff (format and lint, including security and complexity rules), mdformat, the lockfile check,
zizmor for GitHub Actions security, openapi-spec-validator, the check that
openapi.jsonand the models matchopenapi.yaml, the migrations check and pyright; uv auditagainst known vulnerabilities in locked dependencies;- pytest with 100% branch coverage on Python 3.12, 3.13 and 3.14;
- after all of the above, a container build that must become healthy, run as uid 10001, serve its pages and pass
the C2PA Soft Binding API conformance harness (Conformance). On
main, this job pushes the image it tested asmainandsha-<commit>; nothing is rebuilt for publishing.
Changes to docs/ and zensical.toml also build and check this site; pushes to main publish it.
Release¶
- Bump
versioninpyproject.toml(andinfo.versioninopenapi.yaml) onmainand wait for CI to publish thesha-<sha>image. - Create a GitHub Release
vX.Y.Zon that commit. The release workflow checks that the tag matches the version and retags the tested image asX.Y.Z, and aslatestunless it is a pre-release. Nothing is rebuilt, so mainnet runs exactly the bytes that testnet ran.