First-Delivery API
Release Orchestrator derives first-delivery progress from the data its owners already hold. The three read operations below keep three facts apart, each with its own owner and state:
| Fact | Owner | Source |
|---|---|---|
| Observed deployment | Release Orchestrator | Deployment rows in the Release Orchestrator database |
| Decision evidence proof | Release Orchestrator decision evidence seal | Seal intents and Evidence receipts recorded with each committed decision revision, and the seal configuration; read for the first and the latest observed deployment and for the newest captured decision |
| CI evidence receipt | Evidence | Provenance attestations that Evidence holds for release component digests |
Nothing here writes. There is no delivery ledger: every response is derived from owner data on each request, so it survives a refresh or a service restart. The per-actor Platform onboarding checklist is not read and cannot complete a delivery.
Authorization
All operations require release:read (ReleaseOrchestratorPolicies.ReleaseRead) and a resolved tenant; a request without a tenant is rejected with 400. A developer principal (a developer role, or service, repository, image-namespace or environment ownership claims) is scoped on every operation:
| Operation | Developer principal scope | Without ownership claims |
|---|---|---|
| Observed deployment and proof | Deployments of its owned services inside its environment claims | Service ownership or environment claims missing: scope: missing-ownership-claims, observedDeployment.state: unavailable |
| CI evidence receipt | Digests of releases whose components it owns | Service ownership claims missing: scope: missing-ownership-claims, state: unavailable |
| Provenance of one digest | A digest of a release component of a release it owns, or of a deployment it may read; any other digest returns 403 with error: developer_projection_forbidden before Evidence is read | Service ownership claims missing: scope: missing-ownership-claims, status: unavailable; Evidence is not read |
An empty history is never reported as a fact for a caller outside its scope.
The Evidence reads (GET /api/evidence/v1/attestations and POST /api/evidence/v1/attestations:export) run under Release Orchestrator’s own service token with the attest:read scope (ReleaseOrchestrator:Provenance:AttestorServiceTokenScope), not under the caller’s token. The route policy and the developer scoping above are therefore the caller’s only gate to the Evidence metadata these operations return. Evidence audits every export: each CI-receipt read and each provenance read records one Evidence export audit event, under the Release Orchestrator service identity, for every digest whose provenance entry it reads.
Observed deployment and proof
GET /api/v1/release-orchestrator/first-delivery
A deployment counts as an observed delivery only when all of these hold:
- its status is
succeeded(or the earlier spellingcompleted); - it has at least one target, and every target succeeded;
- a completion time is recorded;
- no target reported a digest mismatch (
landingVerification.overallStatusor a target landing ofmismatch).
Everything else is counted under notCounted and never completes a delivery: inProgress (accepted for dispatch, pending, running or paused), failed (failed, cancelled or rolled back), partial (reported successful, but a target did not succeed or no completion time is recorded), zeroTargets (an environment without targets) and landingMismatch.
Response fields:
| Field | Meaning |
|---|---|
scope | tenant, developer-owned or missing-ownership-claims |
observedDeployment.state | observed, not-observed or unavailable |
observedDeployment.first, .latest | The earliest and the most recent observed deployment by completion time: deployment, release, environment, approval, start and completion times, target counts, the deployed digests, and landing (verified, unverified or not-recorded; the deploy agents are the observer) |
observedDeployment.deliveredReleaseIds | Ids of releases with at least one observed deployment, at most 200 (deliveredReleaseIdsTruncated marks more) |
proofCompletion.state | The decision evidence proof of the decision bound to the first observed deployment’s approval, read from the seal intents of that approval. The newest captured decision revision decides: completed (Evidence returned a bundle for that revision and the receipt is recorded), pending (captured, not sealed yet), failed (the last attempt failed; it is retried). Without an intent: disabled when ReleaseOrchestrator:EvidenceSeal:Enabled is false, otherwise unavailable. unavailable also covers a first delivery without an approval and an intent store that could not be read; reason says which. A proof is reported complete only from a recorded receipt. |
proofCompletion.decisionId, .decisionRevision | The decision and its sha256: revision as captured for sealing (absent without an intent) |
proofCompletion.bundleId, .bundleRootHash, .sealedAt | The Evidence bundle that seals decisionRevision, its root hash and when the receipt was recorded; set only when completed |
proofCompletion.attempts, .lastError, .nextAttemptAt | Delivery attempts, the error code of the last failed attempt (for example evidence_http_503, evidence_unreachable, evidence_idempotency_unconfirmed) and the next attempt time |
proofCompletion.earlierRevisions | Earlier captured revisions bound to the same approval; their receipts are never presented as this proof |
latestProofCompletion | The same proof fields for the decision bound to the latest observed deployment’s approval (observedDeployment.latest). The first and the latest delivery each have their own proof; one is never presented as the other’s. |
newestDecisionProof | The same proof fields for the newest decision captured for sealing in the tenant, whether or not a deployment followed it (a denied or not yet deployed promotion included), so a pending or failed seal stays visible during an Evidence outage. approvalId and decisionId name the decision; deploymentId is absent. unavailable with the reason when the tenant has no captured decision; disabled when sealing is switched off and none is captured. A developer principal does not read it: its scope is the deployments it may read, so the field is unavailable with that reason. |
A seal intent read that fails is logged (first_delivery_seal_read_failed) and reported as unavailable; it is never reported as completed or as a decision without an intent.
CI evidence receipt
GET /api/v1/release-orchestrator/first-delivery/ci-receipt
Release Orchestrator checks the distinct sha256 digests of release components, newest releases first, at most 25 per request (truncated marks more), resolving up to four digests at a time. A digest counts as CI evidence received only when:
- Evidence holds an attestation entry of a configured provenance artifact kind (
ReleaseOrchestrator:Provenance:ProvenanceArtifactKinds, defaultslsa-provenance) whose artifact binds exactly to the digest, and - the archived in-toto statement of that attestation names the digest as a subject and records the repository, the commit and the pipeline run.
A scan result, repository or commit lineage recorded on the release component (operator input, registry labels or version sync) and a selected image never count. Receipt is not verification: verification is always not-evaluated; signer trust is checked by verifying the attestation.
| Field | Meaning |
|---|---|
state | received, not-received or unavailable: Evidence could not be read for at least one digest, or the read was truncated and none of the checked digests counts (older digests were not checked), so the answer is unknown |
receipts[] | Received CI evidence, most recent first: release, component, digest, attestation uuid, artifact kind, signer, receipt time, repository, commit, pipeline run, builder, predicate type |
notCounted[] | Every other checked digest with outcome: no-provenance, provenance-unavailable, attribution-not-retained, attribution-incomplete, attribution-unreadable or attribution-unavailable, and componentLineageRecorded when the component carries lineage that does not count |
The statement is read with POST /api/evidence/v1/attestations:export for the one attestation. When Evidence keeps no archive for the entry (no archive store is configured, or the entry was recorded without one), the outcome is attribution-not-retained.
Provenance of one digest
GET /api/v1/release-orchestrator/provenance/{digest}
Returns the provenance attestation Evidence holds for one sha256:<64 hex> digest through the same resolver: scope (tenant, developer-owned or missing-ownership-claims), status (present, absent or unavailable), reason when the status is unavailable and the cause is known, attestationRef, kind, signer (mode, issuer, subject, key id), receivedAt, logStatus, and, when present, attribution with state (recorded, incomplete, not-retained, unreadable or unavailable), the repository, commit, pipeline run, builder and predicate type, the missing fields and the reason. Any other path value returns 400; a developer principal’s digest outside its scope returns 403 (see Authorization).
status is unavailable, never absent, when provenance discovery is switched off (ReleaseOrchestrator:Provenance:Enabled is false) or no provenance artifact kind is configured; reason names the setting and Evidence is not queried.
Evidence matches the attestation list subject filter exactly against the stored artifact sha256 (bare hex), the image digest (sha256:<hex>, optional on submission) or the subject URI, so the resolver queries both the prefixed and the bare form and merges the entries by uuid.
The Console Chain of Custody page renders its Source and Build stages from this read.
Recognized statements
The DSSE payload may be base64 or base64url encoded. Repository and commit come from one source descriptor: the first descriptor that records both. When no descriptor records both, the first repository is reported without a commit and the attribution is incomplete; a repository is never paired with a commit from another descriptor.
| Predicate type | Repository and commit | Pipeline run | Builder |
|---|---|---|---|
https://slsa.dev/provenance/v1 | buildDefinition.resolvedDependencies[] git URI and digest.gitCommit or digest.sha1 (or a hexadecimal URI suffix); buildDefinition.externalParameters.workflow.repository only as the repository when no dependency carries one | runDetails.metadata.invocationId | runDetails.builder.id |
https://slsa.dev/provenance/v0.2 | invocation.configSource or materials[] git URI with digest.sha1 | metadata.buildInvocationId | builder.id |
https://in-toto.io/Link/v1 | materials[] URI git://<repository>@<commit> | environment["pipeline.ref"] | environment["builder.id"] |
