Skip to content
GitHub

Catalogue CI contract

The provider-neutral validation, packaging, publication, and evidence contract for an AgentBundle catalogue CI pipeline.

This page defines the portable contract any CI system can implement to validate, package, and publish an AgentBundle catalogue. It names the commands, outputs, exit codes, publication ordering, and responsibility boundaries — without prescribing a CI provider or a specific workflow shape.

Use it to implement the same release flow in GitHub Actions, GitLab CI, Jenkins, or another runner: build without publication secrets, inject a restricted publisher identity only into the upload job, serialize releases, verify with a read identity, and retain a credential-free receipt.

Three parties share responsibility for a catalogue CI pipeline. No party may silently assume another’s obligations.

PartyOwnsNever does
AgentBundle CLICorrect catalogue validation; deterministic archive + SHA256 sidecar; stable JSON output shapes; stable exit-code contractRead secrets; issue network calls; control upload order; manage retention
Organization CIProtected secret storage; publisher and read identities; HTTPS upload; publication serialization (ensuring one release lands at a time); rollback policy; artifact retentionImplement validation logic; expose credentials to build jobs; bypass exit-code signals; skip the admission command
Host RepositoryCI workflow files and trigger config; internal governance gates; tests proving the portable commands work in this repositoryAlter the portable command signatures; branch AgentBundle’s output format

The CI environment must have agentbundle available before any catalogue command runs. Two patterns are equally valid:

Externally managed. The CI step installs agentbundle from a package registry:

pip install agentbundle

Pin the version for reproducibility:

pip install 'agentbundle==<version>'

If the wheel is in an internal Artifactory PyPI repository, the selected Python package client owns authentication. Its credentials are separate from the identity that uploads a catalogue and from the identity that later reads a protected Generic repository.

Vendored. The organization pre-installs agentbundle into a shared CI image or tool layer. The CI step runs catalogue commands without an explicit install.

Either way, confirm the tool is present:

agentbundle --version

Every pipeline run that touches catalogue content must pass the admission command before proceeding to packaging:

agentbundle catalogue verify --root . --format json

--format json writes a machine-readable result to stdout and exits 0 (clean) or 1 (failures). The CI step fails on any non-zero exit, blocking packaging.

catalogue lint runs as an earlier, faster check against pack sources only. It is optional but recommended as a pre-commit or fast-path gate:

agentbundle catalogue lint --root . --format json

After verify exits 0, package the release:

agentbundle catalogue package \
--root . \
--bundle "$BUNDLE" \
--release "$RELEASE" \
--channel "$CHANNEL" \
--output "$OUTPUT"

The command writes three artifacts under $OUTPUT:

$OUTPUT/
catalogues/
$BUNDLE/
releases/
$RELEASE/
catalogue-$RELEASE.tar.gz ← immutable archive
catalogue-$RELEASE.tar.gz.sha256 ← SHA256 sidecar
channels/
$CHANNEL.json ← mutable channel descriptor

The archive is deterministic given the same source content. The SHA256 sidecar is computed over the archive and stored alongside it. The channel descriptor is a small JSON file that records which release the channel currently points to; it is the live pointer consumers use to resolve the latest version.

Packaging refuses to replace a release that already exists. When catalogue-$RELEASE.tar.gz is already under $OUTPUT, the command exits 2, prints the existing archive path on stderr, and writes nothing. Publish each release once, from a fresh output directory. A CI step never passes --force; only a publisher replacing a release by hand does.

To see what changed since the last release, pass the prior archive:

agentbundle catalogue package ... --compare "/path/to/catalogue-$PRIOR.tar.gz" --format json

Re-run agentbundle catalogue verify --archive <path>.tar.gz after packaging to confirm the archive is valid before uploading it.

Run this phase in a protected upload job. Inject a restricted service identity or an organization-configured CI OIDC exchange only after the source and package checks pass. Use a username only when the selected upload client requires one.

The portable controls map to common CI systems as follows:

ControlGitHub Actions exampleGitLab CI exampleJenkins or another runner
Publisher secretEnvironment or organization Actions secretProtected, masked, and hidden CI/CD variableCredential binding or external secret store
Release gateProtected environment and release tagProtected release tagRestricted release job
Serializationconcurrency groupresource_groupDeployment lock or equivalent

These are mappings, not required products or variable names. The host repository owns its workflow syntax.

Upload artifacts in this exact order:

  1. The archive (.tar.gz)
  2. The SHA256 sidecar (.sha256)
  3. The channel descriptor (channels/<channel>.json) — upload last

The channel descriptor is the live pointer. Writing it last minimises the window during which the descriptor references an archive that has not yet landed in the target store. A consumer that resolves the channel between steps 2 and 3 finds no channel pointer and fails cleanly; a consumer that resolves after step 3 finds the archive already present.

Publication serialization — preventing two concurrent releases from interleaving their uploads — is the CI system’s responsibility. Configure release objects as immutable and allow controlled replacement only for channel descriptors.

After the channel descriptor is published, use a read-only identity to download the archive and sidecar from the publication store and verify them locally:

# Download from the store first (provider-specific step)
# Then verify the local copies:
agentbundle catalogue verify \
--archive "/path/to/downloaded/catalogue-$RELEASE.tar.gz" \
--sha256-file "/path/to/downloaded/catalogue-$RELEASE.tar.gz.sha256"

--archive accepts a local filesystem path only; pass the path to the downloaded archive. This confirms the archive round-trips through the publication store without corruption.

Optionally, smoke-test pack installation from the published catalogue to confirm consumer-side resolution. A smoke test against a protected catalogue needs the same reader authentication that end users need; it must not silently reuse the publisher identity.

The CI system is responsible for retaining the evidence needed for rollback, audit, and incident response. Minimum retention artifacts:

  • The archive and SHA256 sidecar for every published release
  • The JSON output of catalogue verify --format json from Phase 2
  • The JSON output of catalogue verify --archive ... from Phase 5

Also record a credential-free publication receipt: source revision, release and channel names, artifact paths, archive digest, CI run reference, and remote verification result. Never include a token, password, username, authenticated URL, or secret-store location.

Retention period and storage location are Organization CI policy, not AgentBundle’s.

All agentbundle catalogue commands follow the same convention:

CodeMeaning
0Success
1Validation or operational failure (errors in the catalogue, archive mismatch, internal error)
2CLI usage error (missing required flag, unknown or abbreviated option); for catalogue package, also a refused replacement of an existing release archive

These codes are stable. A CI step that treats any non-zero exit as a failure gets the correct behaviour. A step that must tell a refused replacement from a usage error reads exit 2 together with the CAT-PKG-002 line on stderr.

agentbundle catalogue lint, agentbundle catalogue verify, and agentbundle catalogue package do not read secrets and do not issue network calls. They operate entirely on the local filesystem.

The three credential paths are independent:

  1. The Python package client authenticates to the repository that serves the agentbundle wheel.
  2. The protected upload job authenticates its upload client with a restricted publisher identity.
  3. AgentBundle’s HTTPS client resolves a protected catalogue read through a four-provider chain: bearer token (AGENTBUNDLE_HTTP_BEARER_TOKEN), JFrog CLI 2.105.0+ profile, exact-machine .netrc record, then anonymous. The chain stops at the first available provider; there is no fallback once a provider is chosen. See the AgentBundle reference’s Protected catalogue authentication section for provider details and failure codes.

AGENTBUNDLE_CA_BUNDLE, HTTPS_PROXY, and NO_PROXY control TLS trust and network routing when install or upgrade resolves a remote catalogue. They are not publication credentials. Keep same-origin redirects, HTTPS-only access, digest verification, and archive extraction confinement enabled.

agentbundle catalogue lint [--root ROOT] [--pack PACK] [--format {table,json}] [--deep]

Validates pack sources against contracts without building. Exits 0 when all checks pass, 1 on any error.

--format json writes to stdout:

{
"schema_version": "1",
"command": "catalogue lint",
"operation": "lint",
"agentbundle_version": "<version>",
"catalogue_schema_version": "<version>",
"ok": true,
"diagnostics": []
}

Human-readable output (table format) goes to stderr and is absent when --format json is used.

agentbundle catalogue verify [--root ROOT] [--pack PACK]
[--archive ARCHIVE] [--sha256-file SHA256_FILE]
[--format {table,json}]

Runs the full source pipeline (source mode) or validates an archive against its SHA256 sidecar and checks the extracted layout (archive mode). Exits 0 on clean, 1 on any failure.

--format json writes the same JSON shape as catalogue lint to stdout.

agentbundle catalogue package --bundle BUNDLE --release RELEASE
--channel CHANNEL --output OUTPUT
[--root ROOT]
[--source-revision SOURCE_REVISION]
[--minimum-agentbundle-version VERSION]
[--published-at DATETIME]
[--flavor {runtime,source}]
[--force] [--compare PATH]
[--format {table,json}]

Required flags: --bundle, --release, --channel, --output. Optional:

FlagMeaning
--rootCatalogue root; defaults to .
--source-revisionVCS revision recorded as audit metadata
--minimum-agentbundle-versionMinimum consumer version required
--published-atISO-8601 timestamp override
--flavorruntime (default) writes the runtime archive; source writes the self-hosted source archive
--forceReplace an existing release archive instead of refusing. First-party CI never passes it
--compare PATHPrior release archive to compare against; runtime flavour only
--formattable (default) or json; json writes one envelope on stdout; runtime flavour only

--flavor source with --compare or --format json exits 2 with a usage message and writes nothing. The subcommand rejects abbreviated options, so --forc exits 2 rather than selecting --force.

Streams. Human output and diagnostic lines go to stderr in both formats. Each diagnostic is one line, <code>: <message>. Stdout carries the JSON envelope alone, and nothing when --format table is used. Text taken from the prior archive is printed with non-printable characters written as escapes.

--format json. On success and on a failure after argument parsing, stdout is one JSON object:

{
"schema_version": 1,
"command": "catalogue",
"operation": "package",
"agentbundle_version": "<version>",
"catalogue_schema_version": 1,
"ok": true,
"diagnostics": []
}

With --compare, a successful run adds a comparison object:

{
"comparison": {
"prior": {"archive": "<--compare value>", "bundle": "<bundle>", "release": "<release>"},
"packs": {"added": [], "removed": [], "changed": [], "unchanged": 0},
"profiles": {"added": [], "removed": [], "changed": [], "unchanged": 0}
}
}

Added and removed pack items are {name, version, digest}; profile items are {name, digest}. A changed pack is {name, version: {from, to}, digest: {from, to}}; a changed profile is {name, digest: {from, to}}. Each list is sorted by name and unchanged is a count. A failed run has ok false, the failing codes in diagnostics, and no comparison. The human comparison on stderr marks a pack whose content changed under an unchanged version with content changed, version unchanged.

Exit codes.

CodeMeaning
0Packaged; with --force, a replaced release also exits 0
1Failure: verification, an unreadable or invalid --compare archive, or an internal error
2Usage error, or a refused replacement of an existing release archive (CAT-PKG-002)

The refusal applies to both flavours and runs before any file is written. The deprecated top-level package-catalogue command is unchanged and exits 1 on the same refusal.

  • agentbundle reference — install the CLI, install a pack, configure the default adapter, source-resolution chain, environment variables. See the agentbundle.md reference guide in your local guides tree, or the project docs site.
  • Catalogue format — what makes a directory a valid catalogue, schema contracts, and validation. See the catalogue-format.md reference guide in your local guides tree, or the project docs site.