Skip to content

Validate and build in CI

The published Apizr 0.4.3 release provides apizr ci, a GitHub composite Action, and canonical GitLab component source. Final 0.4.3 is available from PyPI; the immutable v0.4.3 Action tag selects the released source. The GitLab component is source-only: hosted runtime and catalog publication have not been performed. Qualification installs the exact original candidate wheel.

One static command

With the exact Apizr core installed, any CI system can run:

apizr ci check --project apizr.toml --authorize-project-analysis --output-dir .apizr-ci
apizr ci build-rest --project apizr.toml --authorize-project-analysis --output-dir .apizr-ci-rest
apizr ci build-mcp --project apizr.toml --authorize-project-analysis --output-dir .apizr-ci-mcp

Each invocation validates the project, runs local doctor checks, assesses repository readiness and plans exposure using the existing compiler. Build modes additionally render the selected REST or MCP bundle. They never import the project, invoke its functions, install application dependencies, start a server or acquire Git sources. Checkout belongs to the consuming forge workflow.

The project must declare an exposure policy; its readiness and exposure policies must allow the intended execution mode. This increment renders direct bundles. For example, direct generation requires execution.modes: ["direct"] in the readiness policy and execution.allowed: ["direct"] in the exposure policy. A readiness refusal anywhere in the scanned repository remains a refusal even when an exposure plan for a selected subset exists.

Exactly one analysis authority is required:

  • --authorize-project-analysis creates an in-memory existing operator policy granting only source.analyze for the exact canonical local project root.
  • --operator-policy PATH uses that existing policy exactly, without rewriting it.

Neither option is implicit. Do not commit or rewrite the machine-local .apizr/operator.json generated by apizr init for CI. No CI-specific permission is introduced. No registry, signing, delivery, GitHub or GitLab credential is needed.

Results and refusal

The absent or empty output directory receives an atomically published tree. Its parent must exist; symlinked output roots/parents, traversal and nonempty trees are refused. Repeated runs need separate output roots or an explicit consumer cleanup of its own prior artifacts. Apizr never cleans a workspace.

Artifact Meaning
result.json Strict canonical apizr.ci-result/v1, digests, state and artifact inventory
doctor.json Portable local configuration diagnostics
readiness.json Existing canonical repository readiness report, once available
exposure-plan.json Existing canonical exposure plan, only when valid
bundle/ Build modes: generated files and the existing REST/MCP bundle manifest

Valid refusals retain available evidence. Invalid arguments or an unreadable project can fail before a tree can be produced. Exit codes are 0 success, 1 business refusal, 2 invalid configuration/invocation, 130 SIGINT cancellation; SIGTERM propagates normally. Diagnostics are fixed and do not echo input contents.

Portable artifacts omit timestamps, provider identity, absolute runner paths, operator-policy contents and environment values. Build bundles necessarily contain the explicitly selected source/resources: choose artifact visibility appropriately. The result inventory excludes its own JSON to avoid a recursive digest. Limits are 4,096 retained files including the result, 16 MiB per artifact and 64 MiB total; existing scan/graph/bundle limits still apply.

Use bounded source roots such as [scan] source_roots = ["src"], or explicitly exclude generated output directories in your scan policy. With source_roots = ["."], a previous bundle inside the project is additional source input. CI does not silently alter your scan policy. Identical source/configuration produces identical bytes across repeat runs and both wrappers.

GitHub Actions

The root action.yml is a real composite Action. This example uses the published stable v0.4.3 tag:

permissions:
  contents: read

steps:
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
    with:
      persist-credentials: false
  - uses: Alien6-Studio/outerspace-apizr@v0.4.3
    id: apizr
    with:
      operation: check
      project: apizr.toml
      authorize-analysis: "true"
      apizr-version: "0.4.3"
      python-version: "3.14"
      output-dir: .apizr-ci

Use the immutable release tag shared with the Python release, or pin its exact commit SHA. There are no moving major/minor Action tags. The Action reference and apizr-version are separate identities; choose matching releases deliberately. The published v0.4.3 Action and component source default to 0.4.3. The matching coordinated packages are available from PyPI.

Input Default / behavior
operation check; also build-rest, build-mcp
project apizr.toml, already checked out
authorize-analysis false; explicit "true" opts in
operator-policy Empty; mutually exclusive with the opt-in
output-dir .apizr-ci; bounded workspace-relative path
python-version 3.14; supported minors 3.11, 3.12, 3.13, 3.14
apizr-version Exact core version only

The Action uses immutable actions/setup-python v7.0.0 commit 5fda3b95a4ea91299a34e894583c3862153e4b97, creates an isolated environment and installs exactly outerspace-apizr==<apizr-version> from normal PyPI. It installs no plugins and strips token/environment values from the apizr ci subprocess. There is no arbitrary arguments/script input and no GitHub API call.

Outputs result and artifacts are workspace-relative paths; apizr-version is the verified installed version and state is success or refused when a corresponding tree was produced. Consumers explicitly choose artifact upload and retention. The Action itself performs neither checkout nor artifact upload.

For repository qualification only, paired wheel-path / wheel-sha256 inputs select a regular local candidate wheel matching the Action's qualification version. The v0.4.3 Action accepts exactly 0.4.3 for qualification; historical Action tags retain their original version policies. The Action checks the SHA-256, embedded core distribution/version and installed CLI version, using a snapshot of the original verified bytes. URLs, symlinks and mismatched identities are rejected. This is not the normal public installation route.

GitLab component source

templates/apizr.yml is the canonical, self-contained component source. It is implemented and structurally validated, but not published on GitLab or in the Component Catalog. No hosted GitLab pipeline has qualified it because no GitLab component project is authorized. GitHub source is not a GitLab component registry. Creating a project, mirror, token or catalog release is separate work.

This is syntax only, with a placeholder project on the consumer's GitLab instance; it is not a live component URL:

include:
  - component: $CI_SERVER_FQDN/<alien6-component-project>/apizr@0.4.3
    inputs:
      job-name: apizr-check
      stage: test
      operation: check
      project: apizr.toml
      authorize-analysis: true
      apizr-version: "0.4.3"
      output-dir: .apizr-ci

The source targets GitLab 18.7+. It uses a spec:inputs header separated by ---, typed boolean authorization and operation options, with bounded regexes for job name, stage, version, image and artifact directory. spec:component was audited (generally available in 18.7); this component does not require that optional context or infer a PyPI version from a component reference.

The generated job installs the exact core from normal PyPI and runs the same apizr ci command. Input values enter variables with expand: false and are passed as quoted arguments. The YAML needs no helper from the component project, no third-party component, plugin, Docker installation, Node installation or API.

Its default image is the previously reviewed Linux amd64 Python 3.14.7 image:

python@sha256:51dafde81dbdb6ebde285137a295cf18a47ca95234fe388a343719cb97305b3d

An explicit python-image override must also be digest-pinned and contain Python 3.11–3.14 plus venv. Accepting an override does not qualify that image or another architecture. job-name starts with apizr-; multiple jobs can use distinct names and .apizr-ci-<suffix> output directories. The consumer supplies an existing stage.

Native artifacts archive only the explicit .apizr-ci or .apizr-ci-<suffix> tree on success, for seven days. A failed invocation must not upload pre-existing content from an output conflict. Refusal evidence remains local to the job; there is no automatic upload on failure.

Qualification references

The Forge integrations workflow exercises the real local composite (uses: ./) on Linux Python 3.11–3.14 with original coordinated wheel bytes. Installed-core proofs outside checkout compare API, CLI and Action output bytes. Tests also cover refusals, cancellation, portable results, bounds and shell-injection sentinels. GitLab qualification validates both YAML documents against the vendored official schema at commit 4dc642ade3597f7a8ae8d93091cc49b36f74a08d (SHA-256 ca545816e585b0b2e17cb89a8782d1e93260d215c28c8ebd1427fb19e8fcea8b), checks input interpolation and executes the shell argument construction for parity. This is structural/source qualification, not a hosted GitLab runtime claim.

Official contracts: GitHub composite metadata, GitHub input security, GitLab components, GitLab inputs and GitLab YAML.