Certify a generated application¶
Use the certification scaffold to add the declaration, reusable-workflow caller, fail-closed readiness probe, and certification Dockerfile together. The workflow builds and measures a candidate but does not push or deploy it.
Generate the assets¶
Run the pinned Zeroth checkout's scaffold command from the generated app root:
PYTHONPATH=/path/to/zeroth python -m release.app_certification scaffold \
--root . \
--app-name my-app \
--module my_app \
--zeroth-version 0.23.11 \
--zeroth-ref <FULL_ZEROTH_COMMIT_SHA>
The command refuses to overwrite existing files and emits:
.github/workflows/app-certification.yml;certification.json;certification.semantic.json;Dockerfile.certification;<module>/certification_entrypoint.py;<module>/certification_healthcheck.py;<module>/migrations.py.
The generated module must already expose the graph, contract, authentication, and policy factories named by the standard targets. The scaffold executes those generator-owned inputs once and emits their complete deterministic semantic manifest, including target-source hashes. Refresh it after changing a target:
PYTHONPATH=/path/to/zeroth python -m release.app_certification generate-semantic \
--root . --declaration certification.json \
--output certification.semantic.json --database-backend sqlite
The generation command normalizes volatile graph timestamps and writes the
canonical JSON atomically, so identical inputs produce identical bytes. Adjust
only the structured targets references if the app uses different names.
Neither declaration can provide commands or shell text.
The reusable workflow binds the validated semantic backend to the certification process and both runtime containers. It provisions isolated SQLite storage; a manifest declaring PostgreSQL fails closed because that workflow does not own a fresh PostgreSQL database or DSN. Direct certification of PostgreSQL requires an explicit matching backend and fresh DSN.
What is checked¶
Certifier-owned implementations validate the source-bound static semantic document without importing candidate Python:
- register every Pydantic contract in a migrated temporary database;
- validate every graph with the complete runtime validator and resolve every referenced contract;
- resolve policy and capability bindings and require allowed decisions;
- validate service authentication, the frozen lock and declared Zeroth version, the declared app migration against a fresh database, container state, readiness JSON, and frontend/API drift;
- import the exact installed runtime distribution and exercise authenticated Regulus capability, budget, and instrumentation operations in both modes;
- send the same deterministic smoke request to packaged and tmpfs-backed candidate containers.
Arbitrary Python cannot prove that a returned object represents its real behavior, so dynamic graph, contract, service, policy, and optional-import results are outside this certification contract. App-local callable reducers are likewise unsupported; use statically valid graph behavior or a separately reviewed integration. The only candidate Python executed for a verdict is the declared migration, and its output is ignored: the trusted supervisor accepts only the independently inspected fresh-database effect.
Each host-side check runs in a bounded subprocess without an ambient shell.
The migration runs as a fresh locked low-privilege user with a private primary
group. On Linux the supervisor becomes a child subreaper and terminates adopted
run descendants through stable pidfds. The final UID inventory is only a
fail-closed leak signal and never authorizes an account-wide kill. Docker state
and the locked frontend tool tree are checked on the trusted path. Container
readiness requires a parsed JSON body with status: ok; HTTP 200 by itself is
not sufficient. Smoke requests refuse redirects, and frontend targets must
remain below the app checkout after symlink resolution.
The public run command requires Linux pidfd containment and
--untrusted-user to name an existing, non-root account distinct from the
certifier. It must have a locked password, a nologin or false login shell,
a same-name private primary group, no supplementary groups, no sudo rules, and
no pre-existing processes. Candidate execution also sets no_new_privs and
drops inherited, ambient, and bounding capabilities. The declaration,
candidate root, report directory, and
--evidence-root, including both resolved and lexical ancestor chains, must
not be writable or replaceable by that account under the kernel's effective
access checks, including named POSIX ACLs. Omitted, shared, active, privileged,
or writable-result configurations fail before candidate execution.
Identity, evidence, and privileges¶
The certify job has only contents: read. The candidate Dockerfile runs in a
named disposable BuildKit scope with bounded CPU, memory, processes, output,
and fixed-size state storage. App dependency hooks run inside a digest-pinned,
read-only container with the same resource classes and a fixed-size
virtual-environment filesystem. Docker daemon logs use a rotating size cap,
and the complete named build and dependency scopes are removed and inventoried
after every outcome. Every Docker name and image tag includes the immutable
workflow run/attempt identity; collisions fail before creation, and image cleanup
requires the exact recorded image IDs. The pinned certifier and handoff remain runner-owned. The job builds
from an exact Git archive and measures its SHA-256
alongside the app commit and local image descriptor, generates an SPDX SBOM, and
writes a canonical report even when preparation, build, startup, or health
fails. Jobs, candidate process trees, containers, archives, HTTP exchanges, and
retained logs all have explicit bounds.
The declared Dockerfile must resolve to a regular file inside that exact Git archive. The measured image must also retain the isolated absolute-interpreter runtime command emitted by the scaffold; both candidate modes import Zeroth from the verified wheel location before starting the application module.
A fresh, unprivileged verify job downloads that handoff and uses clean,
exact app and pinned Zeroth checkouts to validate both checkout HEADs and the
source archive against the
externally resolved commit tree, plus its hashes and Docker/OCI
descriptor tree, config, and layers. It emits a digest-bound verdict that
candidate code cannot rewrite. Only after verification succeeds does the
separate attest job
receive id-token: write, attestations: write, and
artifact-metadata: write; it authenticates the verdict before signing the
exact image subject. The app tree is checked out only as immutable comparison
material and is never executed in either post-certification job.
The final report and verifier verdict cross-bind the app commit and tree, source archive digest, exact Zeroth version and commit, image name and digest, SPDX package inventory and subject, certifier wheel, a byte-for-byte inventory of that wheel as installed in the image, locked image requirements, signed provenance predicate, and hashes of all retained evidence, including cleanup inventory and exact workflow-stage outcomes. The installation inventory is measured from a stopped container without executing candidate code. The finalizer cryptographically verifies the bundle against the expected GitHub OIDC issuer, repositories, workflow, and commits before replacing the unsigned predicate. A hand-written or tampered passing report is rejected.
Promote a certified artifact¶
After finalizing the provenance into its DSSE/Sigstore bundle, issue a portable
promotion receipt with release.app_certification.issue_promotion_receipt.
Receipt issuance revalidates every retained evidence digest and re-verifies the
bundle against the explicit repository, signer repository, signer workflow, and
signer commit trust policy. Raw unsigned provenance, incomplete trust policy,
self-authored evidence, and tampered finalization fail closed.
The receipt signs the exact app commit, source digest, immutable image digest,
evidence binding, report digest, tenant/workspace scope, allowed environments,
and expiry. Register it with POST /v1/certifications.
Certification state advances through buildable, test_deployable,
certified, and promoted; invalidated evidence or artifact identity changes
move it to revoked. A test-only receipt remains test-deployable, but production
readiness stays false until a production-capable receipt, or an authorized
environment policy override, is promoted to the exact serving target.
Configure the process-owned serving identity with
ZEROTH_CERTIFICATION__SERVING_APP_COMMIT and
ZEROTH_CERTIFICATION__SERVING_IMAGE_DIGEST; the target comes from the
persisted deployment selected when the service boots. Both identity values are
required together. Then call POST /v1/certifications/{id}/promote with an
empty JSON body. Target, commit, and digest are never accepted from the client.
A target is claimed atomically: retrying the same certification and target is
idempotent, while a competing certification receives HTTP 409. Signature
failures, verifier/storage failures, revocation, unavailable serving identity,
and commit or image mismatches fail closed. Readiness continuously compares the
promoted identity with the serving identity; artifact drift revokes the receipt
and releases its target. Promotion also requires the caller to own the currently
served deployment scope.
Administrators can submit a reasoned, scoped, time-bound exception through
POST /v1/certifications/{id}/override. Overrides cover only receipt expiry or
environment policy; they cannot cover an invalid signature, malformed evidence,
revocation, or artifact identity mismatch. GET /v1/certifications and
GET /v1/certifications/{id} expose the active override, append-only audit
timeline, including the exact promotion target and override expiry captured by
each event, blocker codes, and operator remediation. The deployment console and
readiness response use the same central decision. The console associates a
certification only through an exact promotion target key; app-name similarity
never grants readiness.
Retained diagnostics¶
The workflow retains the canonical JSON report, hashed stage outcomes, hashed
post-cleanup absence inventory, declaration,
source and image archives, SPDX JSON, signed provenance bundle, container
inspection, and container logs for 14 days. The in-repository vendor-dd
reference can be run from Actions → Certify vendor-dd → Run workflow.
When certification runs and fails, its validated per-check diagnostics are
retained instead of being replaced by generic workflow-stage failures.