Operator Verify Runbook
Audience: operators and QA engineers collecting release-trust evidence from a running Stella Ops stack.
The Gateway owns the operator verification surface under /api/v1/operator/verify/*. These endpoints give operators a single front-door for release-trust evidence — buildinfo rollout, SBOM-to-digest binding, ledger chain integrity, token revocation, CRA provenance, and replay comparison — that previously required per-service APIs, CLI tools, or direct database access. This runbook documents each endpoint’s auth scope, headers, caching, status vocabulary, and the evidence-capture layout.
Evidence captures for Sprint 052 VERIFY-AGG-011 belong under:
docs/qa/feature-checks/runs/2026-05-18-operator-verify/evidence/
For each endpoint, keep one happy-path response and one service-down response. Name files by endpoint and condition, for example buildinfo-happy.json, buildinfo-service-down.json, replay-compare-happy.json, and replay-compare-service-down.json.
Shared Operator Rules
Base URL:
https://stella-ops.local
Authentication:
GET /api/v1/operator/verify/buildinfois anonymous.- Read endpoints require a bearer token with
operator.verify.read. POST /api/v1/operator/verify/replay-comparerequiresoperator.verify.admin;operator.verify.readalone is forbidden.- Local evidence probes may use the
stellaops-clipassword grant. The CLI client bootstrap config and upgrade seed both allowoperator.verify.readandoperator.verify.admin; do not use the browser-onlystella-ops-uiclient for password-grant probes.
Headers:
- Use
Authorization: Bearer <token>on scoped endpoints. - Tenant-scoped endpoints need a resolved Stella Ops tenant. Use
X-StellaOps-TenantId: <tenant>unless the token already carries the tenant claim. The legacyX-StellaOps-Tenantheader is also accepted by the Gateway resolver (see Replay Compare below). - CRA provenance additionally requires the route tenant to match the resolved tenant. A mismatch returns 403.
Status vocabulary:
ok: the upstream service returned usable verification data.missing: the upstream service is unreachable, timed out into the missing path, returned a backend failure, or the queried evidence is not present.timeout: the generic verify aggregator timed out before an endpoint-specific service converted the failure tomissing.unprovenanced: CRA provenance exists as a projection but at least one field lacks source or signer provenance.stale: token revocation propagation telemetry exists but at least one service has stale revocation observation.- Endpoint-specific status values such as
verified,tampered,identical, anddriftare documented below.
Buildinfo Rollout
Endpoint:
GET /api/v1/operator/verify/buildinfo
Auth scope: anonymous.
Cache and timeout:
- Gateway cache: the same cached payload used by
/buildinfo.json, currently public for 30 seconds. - Per-service timeout: inherited from the buildinfo aggregator, currently 5 seconds per service.
Operator-trust gap:
GAP-T1-BUILDINFO-ROLLOUT
Feature-check consumers:
TOPO-001image provenance.TOPO-045fresh-stack deterministic buildinfo membership.TOPO-130andTOPO-134version-skew and rollback buildinfo evidence.
Sample curl:
curl -sk https://stella-ops.local/api/v1/operator/verify/buildinfo \
-o docs/qa/feature-checks/runs/2026-05-18-operator-verify/evidence/buildinfo-happy.json
Representative success response:
{
"generatedAtUtc": "2026-05-18T10:00:00.0000000Z",
"status": "ok",
"services": [
{
"service": "platform",
"status": "ok",
"buildinfo": {
"module": "platform",
"gitSha": "abc123",
"imageBuiltAt": "2026-05-18T08:42:00Z"
}
}
]
}
Representative status:missing response:
{
"generatedAtUtc": "2026-05-18T10:00:00.0000000Z",
"status": "ok",
"services": [
{
"service": "scanner",
"status": "missing"
}
]
}
What missing means:
- Source-wired service running an old container image: wait for the next natural rebuild and container recreate.
- Service is down or unreachable from the Gateway.
- Service still lacks the canonical
MapBuildInfoEndpoint(app, fallbackModuleName: "<service>")wire-up.
Wire-up reminder:
Sprint 052 VERIFY-AGG-002 follows Sprint 041’s BUILDINFO-WIDEN-001 pattern: add the StellaOps.Hosting project reference, map MapBuildInfoEndpoint next to the service’s health/readiness routes, and whitelist /buildinfo.json and /api/v1/buildinfo in any tenant-gating middleware. Sprint 052 did not rebuild images; live missing counts only drop after rebuilt images are deployed.
SBOM By Digest
Endpoint:
GET /api/v1/operator/verify/sbom-by-digest/{sha256}
Auth scope: operator.verify.read.
Tenant/header requirements:
Authorization: Bearer <operator.verify.read token>X-StellaOps-TenantId: <tenant>unless the token carries the tenant claim.
Cache and timeout:
- Gateway cache TTL: 60 seconds.
- Upstream timeout: 5 seconds.
Upstream:
- SBOM Service artifact-link resolver:
/api/v1/artifact-links/resolve?tenant=<tenant>&imageDigest=<digest>&maxDepth=4
Operator-trust gap:
GAP-T1-SBOM-DIGEST-BIND
Feature-check consumers:
TOPO-002SBOM-to-image binding.TOPO-063tampered SBOM rejection, when paired with Scanner import evidence.TOPO-064Stella Ops self-SBOM dogfood evidence.
Sample curl:
TOKEN='<operator.verify.read bearer>'
DIGEST='sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
curl -sk \
-H "Authorization: Bearer $TOKEN" \
-H "X-StellaOps-TenantId: default" \
"https://stella-ops.local/api/v1/operator/verify/sbom-by-digest/$DIGEST" \
-o docs/qa/feature-checks/runs/2026-05-18-operator-verify/evidence/sbom-by-digest-happy.json
Representative success response:
{
"generatedAtUtc": "2026-05-18T10:05:00Z",
"status": "ok",
"digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"sbomIds": [
"sbom:stellaops/scanner-web@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
],
"lastIndexedAt": "2026-05-18T09:58:17Z"
}
Representative missing response for an unknown digest:
{
"generatedAtUtc": "2026-05-18T10:05:00Z",
"status": "missing",
"digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"sbomIds": [],
"lastIndexedAt": null
}
What missing means:
- HTTP 404 with
status:missing: SBOM Service was reachable and no linked SBOM was found for that digest. - HTTP 200 with
status:missing: SBOM Service failed, timed out, returned an invalid response, or was unreachable. - HTTP 400: the digest is not
sha256:<64 hex characters>or no tenant was resolved. - HTTP 401/403: token missing or lacks
operator.verify.read.
Ledger Chain
Endpoint:
GET /api/v1/operator/verify/ledger-chain/{ledgerId}
Auth scope: operator.verify.read.
Header requirements:
Authorization: Bearer <operator.verify.read token>
Cache and timeout:
- Gateway cache TTL: 300 seconds.
- Upstream timeout: 30 seconds.
Upstream:
- Findings Ledger:
/api/v1/findings/ledger/{ledgerId}/chain-verify
Operator-trust gap:
GAP-T2-CHAIN-VERIFY-CLI-ONLY
Feature-check consumers:
TOPO-025audit-ledger tamper detection.TOPO-100,TOPO-102, andTOPO-103chain checks used by time and determinism scenarios.TOPO-111,TOPO-114,TOPO-140,TOPO-143, andTOPO-144when those scenarios need a post-action audit-chain proof.
Sample curl:
TOKEN='<operator.verify.read bearer>'
LEDGER_ID='11111111-1111-1111-1111-111111111111'
curl -sk \
-H "Authorization: Bearer $TOKEN" \
"https://stella-ops.local/api/v1/operator/verify/ledger-chain/$LEDGER_ID" \
-o docs/qa/feature-checks/runs/2026-05-18-operator-verify/evidence/ledger-chain-happy.json
Representative verified response:
{
"generatedAtUtc": "2026-05-18T10:10:00Z",
"status": "verified",
"rootHash": "sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"verifiedAt": "2026-05-18T10:09:59Z",
"entryCount": 128,
"firstTamperedSeq": null
}
Representative tampered response:
{
"generatedAtUtc": "2026-05-18T10:10:00Z",
"status": "tampered",
"rootHash": "sha256:changed",
"verifiedAt": "2026-05-18T10:09:59Z",
"entryCount": 128,
"firstTamperedSeq": 42
}
Representative status:missing response:
{
"generatedAtUtc": "2026-05-18T10:10:00Z",
"status": "missing",
"rootHash": null,
"verifiedAt": null,
"entryCount": 0,
"firstTamperedSeq": null
}
What missing means:
- Findings Ledger returned 404 for the ledger ID.
- Findings Ledger is down, unreachable, or returned a backend failure.
- HTTP 400 from Gateway means
{ledgerId}was not a non-empty GUID.
Token Revocation
Endpoint:
GET /api/v1/operator/verify/token-revocation
Auth scope: operator.verify.read.
Header requirements:
Authorization: Bearer <operator.verify.read token>
Cache and timeout:
- Gateway cache TTL: 15 seconds.
- Authority upstream timeout: 5 seconds.
Upstreams:
- Authority:
/api/v1/authority/revocation/status - Gateway Valkey propagation hashes:
verify:revocation:propagation:*
Operator-trust gap:
GAP-T1-TOKEN-REVOCATION
Feature-check consumers:
TOPO-008token revocation propagation latency.TOPO-023andTOPO-024as supporting scope/DPoP checks when validating operator tokens around revocation testing.
Sample curl:
TOKEN='<operator.verify.read bearer>'
curl -sk \
-H "Authorization: Bearer $TOKEN" \
https://stella-ops.local/api/v1/operator/verify/token-revocation \
-o docs/qa/feature-checks/runs/2026-05-18-operator-verify/evidence/token-revocation-happy.json
Representative success response:
{
"generatedAtUtc": "2026-05-18T10:15:00Z",
"status": "ok",
"bundle": {
"entryCount": 12,
"digest": "sha256:0123456789abcdef",
"issuedAt": "2026-05-18T10:14:30Z",
"signingKid": "authority-revocation-2026-05"
},
"propagation": [
{
"service": "gateway",
"lastSeenRevocationAt": "2026-05-18T10:14:35Z",
"lagSeconds": 25,
"status": "ok"
}
]
}
Representative status:missing response:
{
"generatedAtUtc": "2026-05-18T10:15:00Z",
"status": "missing",
"bundle": {
"entryCount": 0,
"digest": null,
"issuedAt": null,
"signingKid": null
},
"propagation": []
}
Propagation telemetry:
Sprint 052 VERIFY-AGG-006 ships the Authority status endpoint, the Gateway read wrapper, and the gateway-side propagation writer. The Gateway runs a RevocationPropagationBeaconService (BackgroundService) that publishes its own verify:revocation:propagation:gateway Valkey hash entry every 30 s with a 15-minute TTL. The dispatch path is NOT instrumented for revoked-token interception (no safe way to inspect bundle membership at request time without caching bearer values), so the gateway row is a heartbeat-style attestation that the gateway is alive and would observe revocations. Downstream services may add their own IRevocationPropagationWriter implementations in follow-up work; the verify endpoint surfaces additional rows transparently as they appear.
What missing or stale means:
missing: Authority revocation status is down, unreachable, timed out, or returned an invalid/backend failure response.stale: Authority returned a bundle, and at least one propagation entry is older than the current freshness threshold (60 s lag on a bundle older than 5 min).- Empty propagation with
status:"ok"means Authority is healthy but no propagation hash was readable (Valkey is unreachable, the beacon is not running, or both). verify:revocation:propagation:gatewayis the canonical beacon row to inspect first; if it is absent or stale, the gateway itself is the propagation gap, not a downstream service.
CRA Provenance
Endpoint:
GET /api/v1/operator/verify/cra-provenance/{tenantId}
Auth scope: operator.verify.read.
Tenant/header requirements:
Authorization: Bearer <operator.verify.read token>X-StellaOps-TenantId: <tenant>unless the token carries the tenant claim.{tenantId}must exactly match the resolved tenant or Gateway returns 403.
Cache and timeout:
- Gateway cache TTL: 120 seconds.
- Upstream timeout: 10 seconds.
Upstream:
- Policy Engine:
/api/policy/compliance/cra/technical-documentation/provenance/{tenantId}
Operator-trust gap:
GAP-T2-CRA-PROVENANCE
Feature-check consumers:
TOPO-022CRA technical-documentation field provenance.TOPO-028air-gap “no silent zeros” evidence, when CRA pack output is part of the offline proof.
Sample curl:
TOKEN='<operator.verify.read bearer>'
curl -sk \
-H "Authorization: Bearer $TOKEN" \
-H "X-StellaOps-TenantId: default" \
https://stella-ops.local/api/v1/operator/verify/cra-provenance/default \
-o docs/qa/feature-checks/runs/2026-05-18-operator-verify/evidence/cra-provenance-happy.json
Representative success response:
{
"tenantId": "default",
"exportId": "cra-techdoc-20260518-001",
"generatedAtUtc": "2026-05-18T10:20:00Z",
"status": "ok",
"fields": [
{
"path": "manufacturer.name",
"sourceRef": "platform:tenant/default/compliance-profile",
"signerRef": "signer:authority/kid-1",
"signedAt": "2026-05-18T10:19:00Z"
}
],
"unprovenancedFieldCount": 0
}
Representative unprovenanced response:
{
"tenantId": "default",
"exportId": "cra-techdoc-20260518-001",
"generatedAtUtc": "2026-05-18T10:20:00Z",
"status": "unprovenanced",
"fields": [
{
"path": "support.eolDate",
"sourceRef": null,
"signerRef": null,
"signedAt": null
}
],
"unprovenancedFieldCount": 1
}
Representative status:missing response:
{
"tenantId": "default",
"exportId": null,
"generatedAtUtc": null,
"status": "missing",
"fields": [],
"unprovenancedFieldCount": 0
}
What missing or unprovenanced means:
missing: Policy Engine returned 404, is down, is unreachable, or returned a backend failure.unprovenanced: Policy Engine has a projection, but one or more required fields lack source/signing provenance. This is a truthful non-green result, not a transport failure.- HTTP 400: no tenant was resolved.
- HTTP 403: route tenant does not match the resolved tenant.
Replay Compare
Endpoint:
POST /api/v1/operator/verify/replay-compare
Auth scope: operator.verify.admin.
Header requirements:
Authorization: Bearer <operator.verify.admin token>X-StellaOps-TenantId: <tenant>or legacyX-StellaOps-Tenant: <tenant>when the token does not carry the tenant claim.
Cache and timeout:
- Gateway cache: none. Responses include
Cache-Control: no-store. - Upstream timeout: 60 seconds.
Upstream:
- Tester:
/api/v1/tester/replay-and-compare
Operator-trust gap:
GAP-T5-REPLAY-COMPARE
Feature-check consumers:
TOPO-103determinism replay proof.TOPO-040idempotency replay evidence, where compare output is used as a deterministic control.TOPO-044service recovery replay checks.
Sample curl:
TOKEN='<operator.verify.admin bearer>'
curl -sk -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "X-StellaOps-TenantId: default" \
-H "Content-Type: application/json" \
--data '{"runIdA":"run-a","runIdB":"run-b","scope":["evidence-packs","findings","timeline-events","linksets"]}' \
https://stella-ops.local/api/v1/operator/verify/replay-compare \
-o docs/qa/feature-checks/runs/2026-05-18-operator-verify/evidence/replay-compare-happy.json
Representative identical response:
{
"verdict": "identical",
"runIdA": "run-a",
"runIdB": "run-b",
"computedAtUtc": "2026-05-18T10:25:00Z",
"perScopeDiffs": []
}
Representative drift response:
{
"verdict": "drift",
"runIdA": "run-a",
"runIdB": "run-b",
"computedAtUtc": "2026-05-18T10:25:00Z",
"perScopeDiffs": [
{
"scope": "timeline-events",
"expectedHash": "sha256:old",
"actualHash": "sha256:new"
}
]
}
Representative status:missing response:
{
"generatedAtUtc": "2026-05-18T10:25:00Z",
"status": "missing",
"verdict": null,
"perScopeDiffs": []
}
What missing means:
- Tester is down, unreachable, timed out, or returned a 5xx/backend failure.
- Tester 400, 401, and 404 responses are passed through as-is so QA can distinguish invalid request, missing auth, or missing replay run from a service-down condition.
VERIFY-AGG-011 Capture Checklist
For each of the six endpoint sections above:
- Save one happy-path response under the Sprint 052 evidence directory.
- Stop or isolate the relevant backend and save one service-down response.
- Do not rewrite
status:missinginto success; the missing payload is the service-down evidence. - Keep
/buildinfo.jsonbefore/after snapshots next to the endpoint evidence so operators can compare the anonymous buildinfo aggregator with the verify wrapper. - Leave VERIFY-AGG-011 open until all 12 response files and buildinfo snapshots are captured from a live stack.
