Skip to content

Publish, discover and fetch delivery proofs

0.4 development — not released

These commands require a wheel built from the development source, not the published 0.3.0 package. Follow the development installation and record its source commit.

After signing a delivery proof, the optional apizr-attest plugin can store its existing six public files alongside the image. It does not rebuild, retag, re-sign or timestamp anything. The image and proof have different manifest digests. No private key or TSA is needed.

Explicit tools and credentials

Prepare ORAS 1.3.4 independently from its official release. Review the archive hash, extract it, then supply the absolute executable path and executable SHA-256. Invocation snapshots and checks that executable and its version; it never installs tools. Continuum Attest 0.1.0 and independent trust use the same pins and setup as the signing page. Discovery needs only ORAS.

Platform ORAS archive SHA-256 ORAS executable SHA-256
Linux amd64 f27adb935022d94df8dc77719c322dda592c78a0d57a6f7dcdd8d900b248c454 246c47e91bf2749a555ffe00a9824844c6df3a26d61974e3ce08f2077d79c556
macOS arm64 217761a9500242ff473de8656b5aca21136ff39e17e9e61fd8936bbfd902704c 50ae33461ea6e2746b774587d2cb060850961ce01bfd6e70b48d4267919b491c

Supply a private registry configuration with exactly one auths entry, matching the registry (including its port). Credential helpers, implicit Docker configuration and inherited authentication variables are not used. ca_file is optional for publicly trusted TLS. There is no insecure transport option. A hash identifies reviewed tool bytes; it does not establish publisher trust.

The following complete Linux examples use illustrative image/artifact digests and a signer. Replace those identities independently, paths and credentials with your actual values. Install and enable the plugin as on the signing page. The consumer only needs registry pull permissions, no Docker daemon or image. If following the signing page's custom installation directory, append --plugins-dir "$work/plugins" to each command below; otherwise they use the default plugin store. Run the Python example in the separate environment where apizr-attest is installed, not in the minimal core environment.

Publish an existing proof

publish.json:

{
  "schema": "apizr.publish-proof/v1",
  "proof_dir": "/work/proofs/delivery",
  "expected_reference": "registry.example:5443/services/rest@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  "expected_signer": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
  "trust_store": "/private/independent-trust",
  "tool": {
    "executable": "/opt/attest/attest",
    "version": "0.1.0",
    "sha256": "c73ecb92a2ebbf324cb0bdcf631fc3505f3395aa1acad30501284156cef9ce35"
  },
  "transport": {
    "tool": {
      "executable": "/opt/oras/oras",
      "version": "1.3.4",
      "sha256": "246c47e91bf2749a555ffe00a9824844c6df3a26d61974e3ce08f2077d79c556"
    },
    "authentication": {
      "config_file": "/private/registry-auth.json",
      "ca_file": "/private/registry-ca.pem"
    }
  },
  "timeout_ms": 300000
}
apizr plugins run apizr-attest publish --arguments publish.json --timeout-ms 360000

The plugin snapshots the allowlisted files, runs all five native verification checks without warnings or skips, reads the image manifest by digest, and calls ORAS attach on those same private bytes. It rereads the artifact manifest by digest, validates its subject and file descriptors, and requires discovery via referrers before returning receipt_published:true.

The result schema is apizr.published-proof/v1. Keep artifact_reference for fetch. image_digest identifies the image manifest; artifact_manifest_digest identifies the proof manifest; receipt_sha256 identifies the unchanged native receipt bytes. The nested verification retains the existing offline verification contract, whose receipt_published:false means that verification itself performs no publication.

Publishing identical bytes is idempotent. Different signed receipts remain separate artifacts. No proof is deleted. Failure after an attempted attach is a result with state:"remote_state_unconfirmed", receipt_published:false and null artifact identities: the registry may retain the artifact even though confirmation failed. This structured state reaches both Python and CLI callers; a returned result alone is not a publication confirmation. A successful confirmation has state:"verified". A forced interruption cannot return a result and must never be interpreted as rollback. Discover and fetch an explicit digest to establish what is present.

Discover candidates

discover.json:

{
  "schema": "apizr.discover-proofs/v1",
  "expected_reference": "registry.example:5443/services/rest@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  "transport": {
    "tool": {
      "executable": "/opt/oras/oras",
      "version": "1.3.4",
      "sha256": "246c47e91bf2749a555ffe00a9824844c6df3a26d61974e3ce08f2077d79c556"
    },
    "authentication": {
      "config_file": "/private/registry-read-auth.json",
      "ca_file": "/private/registry-ca.pem"
    },
    "max_candidates": 128,
    "max_discovery_bytes": 1048576
  },
  "timeout_ms": 120000
}
apizr plugins run apizr-attest discover --arguments discover.json --timeout-ms 180000

apizr.discovered-proofs/v1 contains digest-sorted candidates, each with a reference, digest, advertised media type/size, artifact type and verified:false. It never selects a candidate. Registry annotations do not establish trust. complete:true means ORAS completed its direct referrer enumeration, not that all proofs are valid or that the registry could not change afterwards. An empty successful list differs from an authentication, network or API failure.

Fetch the explicitly chosen digest

fetch.json:

{
  "schema": "apizr.fetch-proof/v1",
  "artifact_reference": "registry.example:5443/services/rest@sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
  "expected_reference": "registry.example:5443/services/rest@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  "expected_signer": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
  "trust_store": "/private/independent-trust",
  "output_dir": "/received/delivery",
  "tool": {
    "executable": "/opt/attest/attest",
    "version": "0.1.0",
    "sha256": "c73ecb92a2ebbf324cb0bdcf631fc3505f3395aa1acad30501284156cef9ce35"
  },
  "transport": {
    "tool": {
      "executable": "/opt/oras/oras",
      "version": "1.3.4",
      "sha256": "246c47e91bf2749a555ffe00a9824844c6df3a26d61974e3ce08f2077d79c556"
    },
    "authentication": {
      "config_file": "/private/registry-read-auth.json",
      "ca_file": "/private/registry-ca.pem"
    }
  },
  "timeout_ms": 300000
}
apizr plugins run apizr-attest fetch --arguments fetch.json --timeout-ms 360000

Fetch validates the raw manifest hash and exact profile before receiving blobs. Each individual blob is limited while reading, then checked against its advertised size and hash. Local paths come from the code allowlist, never untrusted paths or archive extraction. The existing native verifier requires the independently chosen image, signer and trust. Only a fully verified proof becomes visible via the existing locked atomic export. The destination must not exist; prepare its parent as a user-owned directory without group/world write permission.

The result is apizr.fetched-proof/v1, with the same three identities and native verification details. Afterwards use the signing page's verify.json, pointing proof_dir at /received/delivery. Offline verify needs no ORAS, registry credentials, private key, network, Docker daemon or local image.

Python callers use the same supervised operations:

import json
from pathlib import Path
from apizr_attest import FetchRequest, fetch

request = FetchRequest.model_validate(json.loads(Path("fetch.json").read_text()))
result = fetch(request)  # optional cancel=threading.Event()
print(result.artifact_reference)

publish(PublishRequest(...)) and discover(DiscoverRequest(...)) follow the same pattern. The plugin remains optional and separately installed.

OCI profile and operational limits

The application type application/vnd.apizr.attest.delivery.v1 is an Apizr profile, not a new OCI standard. It uses an OCI 1.1 image manifest with the exact image descriptor as subject, empty OCI config, and six individual layers of application/vnd.apizr.attest.delivery.file.v1, ordered by filename: attest.yaml, delivery/build.json, delivery/manifest.json, delivery/oci-manifest.json, delivery/push.json, receipt.yaml. Each layer has only its allowlisted org.opencontainers.image.title annotation. The fixed manifest creation annotation 1970-01-01T00:00:00Z is a deterministic normalization marker, not the signing time. The native receipt keeps its original signature and timestamp.

Only SHA-256 digest references in the same repository, single image manifests, and this exact profile are supported. Tags, indexes, alternate config, external blob URLs, embedded file data, extra files and hostile paths are refused. ORAS attach/discover explicitly require v1.1-referrers-api; there is no tag fallback. These operations do not change image tags or manufacture executable images. Registries must retain both subject and artifact data.

Proof files are individually limited to 1 MiB, manifests to 1 MiB, ORAS to 128 MiB, and discovery output to 1 MiB by default (maximum 4 MiB). At most 128 candidates are returned. Limits cause errors, never a truncated successful list. ORAS 1.3.4 follows referrer pagination through oras-go 2.6.2; Apizr bounds its captured output and total execution time. The native client's in-memory page collection is not an Apizr per-page memory quota. Depth is one; no recursive referrer traversal. ORAS's experimental JSON contract is pinned and tested, including reference, descriptors and referrers; no nonexistent JSON schema version is assumed.

Each operation has one total deadline (maximum 540 seconds); the outer CLI runtime timeout must allow that operation to finish. Native output is bounded while read. A fresh process group and private snapshots use the existing runtime cleanup, including cancellation. Trusted native tools are not sandboxed; detached descendants and uninterruptible kernel tasks retain the runtime's limits. Local cleanup cannot undo a remote transfer. Native diagnostics and credentials are never copied into results or artifacts.

Normative references and the pinned transport implementation:

Reproduce the installed-package proof

uv run --locked pytest tests/attest_plugin --cov=apizr_attest
uv run --locked pytest tests/oci_plugin --cov=apizr_oci
python3 scripts/smoke_oci_registry.py --artifacts --output /private/test-results/artifacts

The fixture extends the existing build/push/sign tests with a real authenticated HTTPS Zot 2.1.21 registry, local TSA and pinned ORAS. It installs wheels outside the checkout, publishes two proofs, checks idempotence and direct ORAS interoperability, then deletes original proofs and private signing keys. A separate consumer has no Docker socket and a registry identity restricted to read access by Zot policies, also enforced by its HTTPS test gateway. It fetches the selected digest, compares all six files, and verifies in a final networkless container with ORAS and registry credentials removed. The core's files/distributions are compared before and after. Public results and refusals are retained by CI. This fixture does not qualify Docker Hub or Trunx. Apizr release attestation, its secrets and the continuum-attest repository are unchanged.