> ## Documentation Index
> Fetch the complete documentation index at: https://bintzgavin-apastra-14.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Delivery and promotion

> How to promote approved prompt versions to production, pin them in consuming apps, and roll back safely.

Promotion is the transition from "merged and tested" to "approved for production". It binds an immutable prompt artifact — identified by content digest — to a delivery channel. Downstream apps pin the promoted digest so they always run exactly what was tested.

## The full promotion flow

From author to downstream pin, the complete lifecycle looks like this:

<Steps>
  <Step title="Author a prompt change">
    Edit the prompt spec in `promptops/prompts/`. Run a local smoke eval using the `apastra-eval` skill.
  </Step>

  <Step title="Open a pull request">
    Push the change and open a PR. CI triggers the regression gate on the `promptops/**` changes.
  </Step>

  <Step title="CI runs suites and regression gate">
    The `regression-gate.yml` workflow fetches the regression report from the `promptops-artifacts` branch, evaluates it against the policy, and posts a step summary. If any blocker rule fails, the required status check fails and merge is blocked.
  </Step>

  <Step title="Human review">
    CODEOWNERS review approves the PR. An approval state record is written to the `promptops-artifacts` branch, recording the revision ref, decision, and whether all checks passed.
  </Step>

  <Step title="Merge">
    The PR merges to `main`. The `promptops-artifacts` branch now holds the approval record for this revision.
  </Step>

  <Step title="Tag and release">
    When you're ready to release, push a tag (e.g., `v1.2.0`). The `prompt-release.yml` workflow triggers `immutable-release.yml`, which packages `promptops/` as `promptops.tar.gz`, computes a SHA-256 digest, attests build provenance, and creates an immutable GitHub Release.
  </Step>

  <Step title="Promotion record appended">
    The `promote.yml` workflow (triggered manually or by the release publish event) verifies the approval record, then writes an append-only promotion record to the `promotions/` directory on the `promptops-artifacts` branch. The record binds the digest to a channel (e.g., `prod`).
  </Step>

  <Step title="Delivery syncs to targets">
    The `deliver.yml` workflow reads the promotion record, resolves the channel and digest, iterates over `promptops/delivery/*.yaml` delivery targets, and executes the sync for each target matching the channel.
  </Step>

  <Step title="Downstream apps pin the digest">
    Consuming apps update their `consumption.yaml` to pin the new digest or tag. The resolver uses this pin to fetch the exact artifact that was promoted.
  </Step>

  <Step title="Rollback = promote a prior digest">
    If a production issue arises, roll back by promoting the previous known-good digest to the `prod` channel. No editing of existing records — append a new promotion record pointing to the prior digest.
  </Step>
</Steps>

## The consumption manifest

Apps declare their prompt dependencies in `promptops/manifests/consumption.yaml`. This file maps local prompt names to resolution configurations:

```yaml theme={null}
version: "1.0"
prompts:
  summarize-v1:
    id: summarize-v1
    pin: "abc123"  # commit SHA, tag, or semver
  classify-email:
    id: classify-email-v1
    pin: "v1.2.0"
    model: "claude-opus-4"
  experimental-feature:
    id: new-prompt-v1
    override: "./local-prompts/new-prompt-v1.yaml"  # local dev override
defaults:
  model: "claude-sonnet-4"
  provider: "anthropic"
```

### Fields

| Field                     | Required | Purpose                                             |
| ------------------------- | -------- | --------------------------------------------------- |
| `version`                 | Yes      | Manifest schema version                             |
| `prompts`                 | Yes      | Map of local names to resolution config             |
| `prompts.<name>.id`       | Yes      | Stable prompt ID to resolve                         |
| `prompts.<name>.pin`      | No       | Commit SHA, tag, or semver to pin to                |
| `prompts.<name>.override` | No       | Path to a local file overriding the resolved prompt |
| `prompts.<name>.model`    | No       | Model override for this specific prompt             |
| `defaults.model`          | No       | Default model for all prompts in this manifest      |
| `defaults.provider`       | No       | Default provider                                    |

## Resolution order

The resolver applies this precedence when resolving a prompt ID:

<Steps>
  <Step title="Local override">
    If `override` is set on the prompt entry, the resolver uses the local file path. This is for fast local iteration — a developer can point to a local copy without changing the pin.
  </Step>

  <Step title="Workspace path">
    If the prompt exists in the same repo under `promptops/prompts/<id>.yaml`, the resolver uses it. This is the default for same-repo topologies.
  </Step>

  <Step title="Git ref">
    If `pin` is set to a commit SHA or tag, the resolver fetches the prompt spec from that ref. This is the default for separate-repo topologies and governed production pins.
  </Step>

  <Step title="Packaged artifact">
    If a release asset, OCI artifact, or registry wrapper is configured, the resolver fetches from the packaged artifact. Used for org-wide distribution with digest-addressed immutability.
  </Step>
</Steps>

<Note>
  The local override path is designed for local development only. Never commit an `override` that points to a local absolute path — it will break for other developers and in CI.
</Note>

## Promotion records

A promotion record is an append-only JSON file that binds an approved digest to a channel at a point in time. Records are committed to the `promotions/` directory on the `promptops-artifacts` branch and are never modified.

```json theme={null}
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "timestamp": "2026-04-09T14-00-00Z",
  "digest": "sha256:abc123...",
  "channel": "prod",
  "approver": "jsmith",
  "evidence_refs": [
    "https://github.com/your-org/prompts/actions/runs/1234567890"
  ]
}
```

### Fields from the promotion record schema

| Field       | Required | Purpose                                      |
| ----------- | -------- | -------------------------------------------- |
| `version`   | Yes      | The approved version (semver or tag)         |
| `channel`   | Yes      | Target channel (e.g., `prod`, `staging`)     |
| `digest`    | Yes      | Content digest of the promoted artifact      |
| `evidence`  | No       | Links to regression reports or run artifacts |
| `timestamp` | No       | ISO 8601 timestamp of the promotion          |

The `promote.yml` workflow enforces that a promotion can only proceed if an approval state record exists for the target digest with `decision: "approved"` and `checks_passed: true`. This prevents promoting unapproved revisions.

## Packaging options

<CardGroup cols={2}>
  <Card title="Git ref (tag/SHA)" icon="code-branch">
    **Pinning surface:** `git+...#<sha>` or tag in manifest

    **Best for:** Dev, early adoption, internal use

    Zero publishing overhead. Requires Git access. npm and pip both support Git/VCS dependency forms natively, so language-specific wrappers are optional.
  </Card>

  <Card title="GitHub Release asset" icon="package">
    **Pinning surface:** Release tag + asset digest

    **Best for:** Governed releases with low infrastructure

    Tag lineage with easy download. Can be made immutable. Not natively digest-addressed unless you add digests to release notes (which `immutable-release.yml` does automatically).
  </Card>

  <Card title="OCI artifact" icon="box">
    **Pinning surface:** `name@sha256:...`

    **Best for:** Org-wide artifact distribution

    Digest-addressed by design. Supports tag + digest references. Supports associated artifacts and referrers. Requires a container registry.
  </Card>

  <Card title="npm/PyPI wrapper" icon="cube">
    **Pinning surface:** Semver in ecosystem

    **Best for:** Mature orgs with a broad consumer base

    Best app ergonomics — standard package manager tooling. Requires a publishing pipeline. Overhead is higher than Git refs.
  </Card>
</CardGroup>

### Packaging table

| Mode                 | Pinning surface                    | Strengths                                    | When to use                      |
| -------------------- | ---------------------------------- | -------------------------------------------- | -------------------------------- |
| Git ref (tag/SHA)    | `git+...#<sha>` or tag in manifest | Zero publishing; simplest                    | Default — dev and internal use   |
| GitHub Release asset | Release tag + asset digest         | Tag lineage; easy download; can be immutable | Governed releases, low infra     |
| OCI artifact         | `name@sha256:...`                  | Digest-addressed; org-wide distribution      | Org-wide artifact distribution   |
| npm/PyPI wrapper     | Semver in ecosystem                | Best app ergonomics                          | Mature orgs, broad consumer base |

## Delivery targets

A delivery target is a declarative YAML file in `promptops/delivery/` that describes how to sync an approved version to a downstream system. The `deliver.yml` workflow iterates over these files and executes the sync for each target matching the promoted channel.

```yaml theme={null}
type: github_pr
repo: your-org/your-app
channel: prod
```

The delivery target schema requires `type` and `repo`. Supported types include:

* `github_pr` — open a PR to a downstream app repo updating its `consumption.yaml`
* `oci_registry` — push the OCI artifact to an internal registry namespace
* `config_store` — sync to a config store used by edge or runtime systems

<Info>
  Delivery is adapters, not a hosted platform. The system does not assume any particular delivery target — it defines the contract (promotion record in, sync executed out) and lets you implement the adapter for your infrastructure.
</Info>

## Rollback

Rollback is a promotion to a prior digest — not an edit to existing records.

To roll back:

1. Find the previous promotion record for the `prod` channel in `promotions/` on the `promotions-artifacts` branch.
2. Run the `promote.yml` workflow with the prior digest and channel `prod`.
3. The workflow writes a new promotion record binding the prior digest to `prod`.
4. The `deliver.yml` workflow syncs the prior digest to all `prod` delivery targets.
5. Downstream apps update their `consumption.yaml` pin to the prior digest.

This approach preserves the full audit trail. The history shows the rollback event as a distinct promotion record, with the original promoter and timestamp intact.

<Warning>
  Never edit or delete existing promotion records. The append-only constraint is what makes the audit trail trustworthy. Rollback is a forward operation — a new record pointing to an older digest — not a rewind.
</Warning>

## Immutable releases

The `immutable-release.yml` workflow creates GitHub Releases that cannot be modified after publication. The key steps:

```yaml theme={null}
- name: Package Prompts
  id: package
  run: |
    tar -czvf promptops.tar.gz promptops/
    DIGEST=$(sha256sum promptops.tar.gz | awk '{print $1}')
    echo "digest=$DIGEST" >> $GITHUB_OUTPUT

- name: Attest Build Provenance
  uses: actions/attest-build-provenance@...
  with:
    subject-path: 'promptops.tar.gz'

- name: Create GitHub Release
  run: |
    gh release create "$TAG_NAME" promptops.tar.gz \
      --title "Release $TAG_NAME" \
      --notes "Immutable release for $TAG_NAME. Digest: $DIGEST"
```

The digest in the release notes is the SHA-256 of the bundle. Consumers can verify the bundle's integrity by recomputing the digest after download.

Build provenance attestation (via `actions/attest-build-provenance`) records the builder identity, workflow run, and input materials. This enables supply-chain verification for downstream consumers.
