Endpoint
GET /api/v1/decisions/:id/provenance — admin-gated.
Response
Why a separate endpoint
The bundle is a point-in-time snapshot of every artifact a regulator or auditor needs to reconstruct one decision: the trace itself, the model versions referenced, the rule cascade that produced the candidate set, the audit chain, the build identity (so they can verify the code that ran), and the fairness slice in effect at the time. Returning these via the existing/decision-traces/:id route would either bloat the trace endpoint or
require N+1 fetches. The bundle endpoint runs all the joins server-side
and emits a stable, hash-friendly document.
Reproducible digest
Output is canonicalized — JSON with sorted object keys — so theX-Provenance-Digest is deterministic. The same trace re-emitted
from any pod produces the same digest. Verifiers can re-fetch the
bundle days later and confirm bit-identity.
Cosign signing
When the COSIGN_KEY env var is set, the canonical bundle bytes are signed by spawning the realcosign sign-blob binary (installed in the
platform Docker image) and the detached base64 signature lands in
X-Provenance-Signature. The earlier deterministic placeholder
(hash-of-hash) has been removed. Full mechanics — argv, failure modes,
timeouts, key handling — are on
Provenance signing with cosign.
The header value "unsigned" indicates the bundle was generated
without a usable signing path (no COSIGN_KEY, missing binary, or a
signing failure — the audit row records the typed reason). Consumers
can use this signal to refuse unsigned bundles in compliance
workflows.
Build identity inputs
The route reads these env vars and includes them inbuildIdentity:
When all of
GIT_SHA / GIT_REPO / IMAGE_NAME / IMAGE_DIGEST are set,
buildIdentity.slsaAttestation is populated by the supply-chain
SLSA-provenance builder.
Audit trail
Every bundle emission writes one audit-log row withaction: "decision_provenance_emit", entityId: <traceId>, and
changes: { digestSha256, signed, modelCount, auditLogCount }.
DSAR exports cite these rows.
Honest limits
- Bundle includes per-modelType snapshots (the hash of each
modelState), not the full state. Auditors who need bit-exact model reproduction request the model record separately via the registry endpoint. - Signing is fail-soft by design: a broken signing path degrades to
an
"unsigned"bundle rather than failing the request — see “Cosign signing” above. - Effective-rules cascade is computed at bundle-emission time, not
at decision time. Rule edits between the decision and the bundle
request will surface in the cascade trace; the trace itself
records what rules fired at decision time via
qualificationResults/contactPolicyResults. - PII redaction runs over the trace before signing. The redactor
is
lib/explanations/pii-redact.ts; bugs there could leak PII into provenance bundles. Treat unsigned bundles as untrusted.