Skip to content

Orgabot is in private alpha. Request access, or join our Discord.

← All documentation
Using Orgabot

Build artifacts: transfer, provenance and releases

How CI outputs move between your Mac and remote runners, why every artifact needs a signed grant and attestation, and how releases pin exact outputs.

Last updated

Every CI output Orgabot handles, whether it was built on your Mac, in a local Docker job, on an enrolled native worker, or on a GitHub-hosted runner, is described by the same immutable manifest and travels with the same provenance. A consumer anywhere asks the same questions of it, and the answer never depends on a filename.

What a manifest records

A manifest names the organization, project, target, pipeline run and attempt, the source commit and workflow-definition revision, the OS, architecture and toolchain, the identity that produced it, the content's sha256 digest, size, type, retention and access policy, and the digests of every input it was derived from.

The manifest's identity is the sha256 of its canonical JSON. Change any field and it is a different artifact. The name is a label only: two files called Premail-Setup.exe with different bytes are two artifacts, and nothing looks one up by name.

There is no update. Signing, notarizing, packaging and publishing each produce a new manifest whose input names the digest it came from, so a signed installer's lineage back to the unsigned build is recorded data.

Artifact types and trust

Outputs are stored in separate namespaces per project, per trust class, and per type:

TypeWhat it is
cacheReusable build state. Never an input to signing, promotion or publishing
test_reportJUnit XML test output. Becomes verification evidence only after admission, and only when the project's verification policy names its producer as a trusted verifier. The pass or fail outcome and the check names are read from the report itself
unsigned_buildA build output before signing
signed_releaseA signed or notarized asset
publishedAn asset that was published

The trust class (untrusted, trusted, privileged) is decided by the controller from the observed source before the work is dispatched. Work from a fork is untrusted, and an untrusted artifact or any cache is refused for sign, promote and publish, directly or through any input in its provenance chain.

Provenance: a hash alone proves nothing

A manifest and a matching hash only prove someone can hash. Orgabot requires two signatures:

  1. A dispatch grant, signed by this Mac's controller key before the work runs. It names the exact run, source commit, workflow revision, the worker dispatched, the platform, the inputs by digest, the allowed output types, the highest trust class, and a time window.
  2. An attestation, signed by the worker's own enrolled key after the work runs, binding the manifest digest to that grant.

An artifact whose producer was not the dispatched worker, whose inputs were not in the grant, whose trust exceeds the grant or the worker's enrollment, or that was signed outside the window, is refused.

The controller key is created on first use at ~/.orgabot/build-provenance-key.json with owner-only permissions and never leaves the Mac. Remote targets receive only its public half.

orgabot build-artifacts identity show
orgabot build-artifacts identity enroll win-signer --public-key win-signer.pub.pem \
  --org my-org --project premail --platform windows/x64 --max-trust trusted --transform sign
orgabot build-artifacts identity list
orgabot build-artifacts identity revoke win-signer
orgabot build-artifacts identity anchors

Workers generate their own key pair; only the public key is enrolled. Re-enrolling an id with a different key is refused. Revoke it and enroll a new id. A revoked worker's attestations are refused from then on.

The admission gate

Before an artifact is consumed, signed, promoted or published it passes one gate, the same on the controller and on a remote target:

RefusalMeaning
unsafe_pathThe name is not one safe path segment on every platform
cross_projectIt belongs to another organization or project
unauthorizedThe consumer is not a live enrolled identity named in its access policy
wrong_type, wrong_targetNot the type or target expected
too_largeOver the size limit
tamperedThe bytes or manifest do not match the requested digest
wrong_platformNot the platform expected, or the binary header (PE, ELF, Mach-O, MSI) disagrees with the manifest
staleBuilt from another commit, by a superseded attempt, or too long ago
expiredIts retention lapsed
unattested, forged_grant, forged_attestation, unknown_identity, revoked_identity, out_of_scope, grant_mismatch, trust_exceeds_grant, unbound_input, outside_grant_windowThe provenance does not verify
untrusted_promotionUntrusted work or a cache headed for privileged release work
missingFor sign, promote or publish: an input in its provenance chain was not supplied. A remote target must be handed every ancestor (manifest, bytes and provenance), and checks each one the same way the controller does
orgabot build-artifacts admit <manifestDigest> --org my-org --project premail \
  --consumer win-signer --purpose sign --type unsigned_build --platform windows/x64 \
  --repo MattSenter/premail --revision <full-sha>

Moving artifacts without a tunnel into the Mac

Nothing connects into your Mac. Both sides connect out to an authenticated store and use short-lived credentials the controller mints. Each credential covers one object, one direction, one project and one expected digest, and lives at most 15 minutes.

orgabot build-artifacts staging-key
orgabot build-artifacts stage <manifestDigest> --org my-org --project premail --store <dir> --key-handle secret_...
orgabot build-artifacts fetch <manifestDigest> --org my-org --project premail --store <dir> --key-handle secret_...

staging-key stores the store's key in your secret store and prints only its handle. Share the store location, never the key.

The commands above use a staging directory both machines can reach, such as a shared drive on your network. A GitHub-hosted runner cannot reach a directory on your network, so the transfer engine also speaks to a cloud bucket over short-lived signed HTTPS URLs: downloads resume with range requests, and uploads use the resumable-upload protocol cloud storage provides. Connecting it to a specific bucket in your cloud account is not yet available as a command; see Known limitations below.

Outputs a GitHub-hosted runner uploaded as an Actions artifact are pulled through the governed GitHub App identity:

orgabot build-artifacts import-github MattSenter/premail <runId> --org my-org --project premail

The runner packs outputs as a bundle whose entries are artifacts/<manifestDigest>/manifest.json, provenance.json and content. The directory must match the manifest's own digest, and any other entry refuses the whole bundle. Zip entries that traverse, are absolute, are encrypted, fail their CRC, or expand past the size limit are refused.

Interrupted and expired transfers

A transfer writes to a partial file and resumes from the bytes already on disk, including after the process is killed. An expired credential is re-minted and the transfer continues from where it stopped. A transient failure is retried at the same offset. The finished file is hashed, and a mismatch deletes it. Only a verified file can be ingested, and only an ingested, attested artifact can join a release, so an interrupted or swapped download never becomes part of a release.

Signed transfer URLs and credentials are never logged. Every transfer message is scrubbed of the credential value and of every URL query string.

--egress-limit-bytes caps the bytes a project may transfer in a 30-day window. A transfer that reaches the cap stops with its partial intact and resumes when the window allows.

Releases pin exact outputs

orgabot build-artifacts release create v1-4-0 --org my-org --project premail --repo MattSenter/premail --revision <full-sha>
orgabot build-artifacts release add v1-4-0 windows-installer <signedDigest> --org my-org --project premail --type signed_release --platform windows/x64
orgabot build-artifacts release seal v1-4-0 --org my-org --project premail
orgabot build-artifacts release verify v1-4-0 --org my-org --project premail

A release belongs to one source commit. Adding an output runs the full gate for promote against that commit and walks its whole provenance chain. Each dependency records its manifest digest, content digest, platform, producer, grant, every digest in its chain, and, for a signed asset, the unsigned input it was derived from. A role, once bound to a digest, is never rebound to another.

Sealing re-checks every dependency and signs the record with the controller key. A sealed release is immutable, and release verify re-checks the seal and every byte later, so an output altered on disk after sealing shows up as a problem.

Retention, cleanup and diagnosis

Retention is set when an output is produced: ephemeral outputs last 7 days, standard outputs 90 days, release outputs do not lapse. Anything a release references, and every input in its chain, is kept while the release exists.

orgabot build-artifacts gc --dry-run
orgabot build-artifacts gc --storage-budget-bytes 20000000000
orgabot build-artifacts diagnose <manifestDigest> --org my-org --project premail

Cleanup leaves a record behind, so diagnose can say an artifact was collected, and when and why, rather than just "missing". It also distinguishes never stored, stored in another project, bytes gone, expired, and refused. A GitHub artifact past its GitHub retention is reported as expired at GitHub. When pinned content alone exceeds the storage budget, cleanup reports the project as over budget rather than deleting a pin.

Known limitations

  • No command binds a cloud bucket yet. The signed-URL bucket backend is built and tested against a real HTTP server, but there is no orgabot command that connects it to a bucket in your account. Until there is, stage and fetch use a shared staging directory.
  • Pipeline adapters do not call this yet. The local Docker, native worker and GitHub Actions dispatch backends record their outputs separately today. Routing their outputs through these manifests and grants lands with those adapters.