Chain-Completeness API

Release Orchestrator owns deployment and estate state, so it serves one read projection of the chain of custody per deployed digest. For every digest the deployed estate records as running in an environment (a placement), the projection states each of the seven custody stages, Source, Build, Scan, Verdict, Decision, Deploy and Watch, with the observer and observation time of the fact the state rests on. The Console renders this projection on the Estate matrix cells, the Home “Chain of custody” strip, the assurance queue and the Watch stage of the Chain of Custody page (the worst Watch state over the subject digest’s placements); it never derives a chain state from object counts.

GET /api/v1/release-orchestrator/estate/chain-completeness

Nothing here writes, verifies a signature or triggers a verification. The projection is derived from owner reads on every request.

Source qualification

Verified against source 828e64af714918025036d95bc145302be2f79b54 (CCP-106, 2026-10-21). The focused owner/resolver cohort ran 105 cases, all passing, including the response-body interruption refusal and three actual loopback synthetic HTTP cases. These results do not establish deployed Evidence/authentication interoperability or healthy-estate acceptance; those remain the separately reviewed live gate.

Re-verify from a PowerShell session in the repository checkout:

& ./tools/scripts/test-targeted-xunit.ps1 `
  -Project src/ReleaseOrchestrator/__Tests/StellaOps.ReleaseOrchestrator.Integration.Tests/StellaOps.ReleaseOrchestrator.Integration.Tests.csproj `
  -Class @('StellaOps.ReleaseOrchestrator.Integration.Tests.ChainCompletenessEndpointsTests',
           'StellaOps.ReleaseOrchestrator.Integration.Tests.ProvenanceAttestationResolverTests',
           'StellaOps.ReleaseOrchestrator.Integration.Tests.ChainCompletenessOwnerWireTests') `
  -BuildProjectReferences

Dated live proof

R15-RO-1 adopted image sha256:e114b8b9815d0922b31e8eaaed5893000377b5ef12bbd21212ee9248899abf5d from clean source ff9b51d3903eafda52c975e230cfe69caf79ede6 on 2026-10-21 UTC. The actual serving source/payload and complete profile were verified. One approved re-verification of an existing bound Decision packet passed the real content, signature, configured-trust and payload-binding checks. Subsequent packet and chain GETs show that exact Decision as verified.

This proves the deployed Decision branch. The selected Scan export returns no envelope; no supplied-bundle verification receipt was created. Source/Build remain missing, Verdict/Deploy recorded-unsigned and Watch unverifiable. Seven-stage and tenant-wide healthy-estate acceptance remain open. The dated receipt binds the actual identities, requests, checks and limits.

This export result does not establish universal loss of signed material. Source shows the local transparency log retains submitted signed components independently of optional archive storage; a bounded Evidence-owned retrieval contract is required before those components can supply this verification.

Re-observe with an existing authenticated release:read session and resolved tenant: GET /api/v1/release-orchestrator/estate/chain-completeness. Compare the current placement and Decision reference; this GET does not repeat the durable packet verification. A dated verified placement is not an estate reservation.

Authorization

The operation requires release:read (ReleaseOrchestratorPolicies.ReleaseRead) and a resolved tenant; a request without a tenant is rejected with 400. The Evidence reads run under Release Orchestrator’s own service token (attest:read), so a developer principal is scoped: it sees only placements whose establishing deployment it may read (an owned service inside its environment claims). Without service ownership and environment claims the response is scope: missing-ownership-claims, state: unavailable, no placements, and no owner read is made.

Stage states

StateMeaning
verifiedThe exact stage record carries the qualified owner verification described below. This read consumes that result; it does not perform verification.
recorded-signedA signed record exists; this read did not verify its signature.
recorded-unsignedA record exists without a signature.
missingThe owner read answered and holds no record for the stage.
unverifiableA verification ran and did not pass, or an observation contradicts the record (drift).
unavailableThe owner read failed, was switched off or was not made (for example beyond the Evidence digest limit). Never reported as missing.
not-applicablePart of the vocabulary; no current rule emits it.

A placement is broken when a stage is missing or unverifiable.

Stage sources

The projection composes these owner reads. Source, Build, Scan, Verdict and signed Deploy rows below describe their prior states; exact-entry recorded verification can qualify them as described in the next section. This source change does not change the Console client:

StageOwner readStates
SourceThe build-provenance attestation Evidence holds for the digest, and its archived statementrecorded-signed when the statement records repository and commit; missing without a provenance attestation; unavailable when Evidence cannot be read or retains no statement
BuildThe same provenance attestation entryrecorded-signed when present; missing when absent; unavailable when Evidence cannot be read
ScanThe Evidence attestations bound to the digest, any kind containing sbom, scan, cyclonedx, spdx or vulnrecorded-signed when one exists; missing when none; unavailable when Evidence cannot be read, or when it holds more entries than one read returns and none read is a scan
VerdictA signed Evidence attestation whose kind contains verdict, policy or decision, else the release evidence posture. A policy-verdict entry counts only with release-set evaluation scope and typed membership that names the digest (see below)recorded-signed for the attestation; posture verified or partial is recorded-unsigned, missing is missing, verification_failed and replay_mismatch are unverifiable, anything else is unavailable
DecisionThe gate decision bound to the establishing deployment through its approval, with its policy-decision evidence packetverified only when the exact bound packet’s owner status is verified; unverifiable when that verification failed; otherwise the existing signed/unsigned recorded state. Missing approvals or decisions remain missing; unreadable or malformed bound records remain unavailable. An unrelated verified packet and the approval request’s current status never decide it. Existing decision-basis classifications and reason codes remain notes, not gaps.
DeployThat deployment record and its deployment attestation referencerecorded-signed with an attestation; recorded-unsigned otherwise; unavailable when the deployment store cannot be read
WatchThe deployed estate, landing verification and latest estate re-evaluation of the placement’s (environment, release) subjectunverifiable for a known landing digest mismatch or outstanding estate deviation; unavailable for failed, malformed or unestablished owner observations; verified only with the coherent landing and placement-bound current estate proof below. A readable historical record with insufficient proof remains recorded-unsigned, with the missing proof named.

Recorded Evidence verification

Source, Build, Scan, Verdict and a signed Deploy reference also read the exact selected entry’s recorded verification history through GET /api/evidence/v1/attestations/{uuid}. The entry must match the tenant, selected artifact kind and image membership. Presence of a signed record alone retains the table’s recorded state.

The newest receipt is selected by owner verification time, then receipt id; an older passing receipt is never selected instead. It earns verified only when its overall outcome is pass, its supplied bundle is bound to the receipt evidence digest, and exactly one performed signature check and one performed issuer check both report pass. Neither required check may be skipped, duplicated, unknown or warning, and its readable signature count must satisfy actual >= expected >= 1. An overall pass with insufficient assurance retains the prior recorded state and explains why. The newest overall fail is unverifiable. Known empty history retains the prior state; failed, malformed, unbound or unavailable history is unavailable, including a null recording-store result. This read does not invoke the Evidence verifier or change its policy.

A Policy policy-verdict attestation is one signed evaluation of a whole release set, not a per-image verdict. The resolver matches it to a digest only when the entry reports release-set evaluation scope and its typed membership names that digest; the entry’s own scalar document digest never stands for the image. The Verdict stage reason then states that the digest belongs to one whole-release evaluation, including when its recorded verification qualifies. Rows stored with kind policy-verdict before this contract carry no verified statement identity and no typed membership. The resolver therefore does not match them to an image, so they do not establish the Verdict stage; they stay retrievable as recorded, and no backfill or reclassification is performed. Presence remains separate from signature verification.

Only a decision decided no later than the deployment start can have authorized it: when several gate decisions name the approval, the latest such one is bound, and when every decision naming the approval was decided after the deployment started, none is bound (missing, decision-after-deployment-start). A decision row recorded without an approval id is bound only by release, target environment and a decision time no later than the deployment start, and carries the note bound-by-time-window.

The Watch stage reads the same per-subject classification as the deviation and status reads: the latest verdict of each deployed (environment, release) subject in the response’s scope, from one set-based read of release_orchestrator.estate_verdicts, keyed by the identifiers the deployed-estate row carries. A standing regression is one outstanding deviation; a recovered or replaced release is not. On a deviation the stage reason names the evaluation time, the outcome, the authorizing decision with its outcome and the Policy reason; its reference is the verdict id and its observedAt the evaluation time. Absent or unevaluated estate data is never recorded, and neither is a classification the read does not recognize (unavailable, estate-outcome-unknown). On a landing mismatch the stage keeps its code and carries the estate code as a note, so every Watch stage names its estate classification. A developer principal’s read classifies only its own subjects. A deviating placement is broken, so it is also counted in brokenPlacementCount and listed by the Estate broken-chain filter.

Watch earns verified only for a nonempty all-verified landing whose gated digest canonically matches the placed digest. Each target must supply at least one reported or observed digest; every present value must be canonical and equal to that digest, with coherent per-target observations. The latest estate result must belong to the same deployment, environment, release and approval, name the selected authorizing decision with its recorded outcome, and include an evaluated basis covering the placed digest. The evaluation must belong to this placement. Current uses the existing outcome-rank classification; it is not a time-to-live rule. No freshness interval or new risk threshold is added. Mismatch, outstanding deviation and unreadable observations take precedence over a positive state.

Response

FieldMeaning
scopetenant, developer-owned or missing-ownership-claims
state, reasonread, or unavailable with the reason for a caller without a scope
stagesThe seven stage ids in chain order
deployedDigestCount, placementCountDistinct deployed digests and (environment, digest) placements
brokenDigestCount, brokenPlacementCountDigests and placements with a missing or unverifiable stage
stageSummaries[]Per stage, the deployed digests in each state. A digest takes its worst placement state, so a digest with a missing Decision in any environment is never counted as recorded.
evidenceDigestLimit, evidenceTruncatedEvidence is read for at most 50 deployed digests, newest deployments first; the others report Source, Build and Scan unavailable with code not-read-limit
driftThe calling tenant’s agent drift records: observing, drifting and unobserved agents and the running containers on a digest never deployed through Release Orchestrator, reported as part of Watch; state: unavailable with the reason when they cannot be read
placements[]Broken placements first, then by environment protection: protection class (protected 3, gated 2, open 1, unknown 0), production, required approvals, promotion order, environment name, digest

Each placement carries digest, environmentId, environmentName, protection (class, rank, isProduction, requiredApprovals, orderIndex, reason), the establishing deploymentId, releaseId, releaseName, releaseVersion, componentNames, deployedAt, broken, brokenStages, unavailableStages and stages[]. Each stage carries stage, state, code, observer, observedAt, reason, reference (an attestation uuid, decision id, deployment id, release id or, on the Watch stage, an estate verdict id) and notes (on a Watch stage with a landing mismatch, the estate code).

Observers: evidence, release-orchestrator.gate-decisions, release-orchestrator.release-evidence, release-orchestrator.deployments, release-orchestrator.estate-verdicts (the Watch stage over the estate re-evaluation), release-orchestrator.deploy-agents (a landing mismatch) and release-orchestrator.chain-projection (for a stage the projection itself could not read).

Evidence reads

Per evaluated digest the projection makes the provenance reads of the first-delivery API and one subject-wide attestation list read per subject form (prefixed sha256: digest and bare hex), each a single page of 200 entries. A continuation token from Evidence is reported as truncation. Switching provenance discovery off (ReleaseOrchestrator:Provenance:Enabled: false) makes every Evidence stage unavailable with the reason; it is never read as an empty answer.

After stage selection, at most 50 unique entry-detail GETs are admitted across the request, reusing the existing Evidence digest limit. Selection is deterministic over the admitted digest cohorts, stage order and placements; repeated references for the same tenant share one read, including a release-set entry used by several image cohorts. At most four detail reads run concurrently. A reference outside the digest cohort or detail budget is unavailable, with a reason; no empty history is inferred. Existing per-read timeout and caller cancellation apply. No verification, refresh, re-evaluation or write is triggered.

Source: src/ReleaseOrchestrator/__Apps/StellaOps.ReleaseOrchestrator.WebApi/Endpoints/ChainCompletenessEndpoints.cs, Services/ChainCompletenessProjection.cs and, for the Watch stage’s deviation read, Endpoints/EstateEndpoints.cs.