# Contract: Artifact Canonical Record (v1) ## Status - Status: DRAFT (2026-02-19) - Owners: Attestor Guild, EvidenceLocker Guild - Consumers: Evidence Thread API, Audit Pack Export, Console UI - Sprint: SPRINT_20260219_009 (CID-03) ## Purpose Define a unified document that aggregates all evidence references for a single artifact identified by `canonical_id`. Today this information is distributed across Attestor (`dsse_envelopes`), Scanner (SBOM refs), VexLens (consensus records), and OCI registries (referrers). The Artifact Canonical Record provides a single query target for: 1. The Evidence Thread API (`GET /api/v1/evidence/thread/{id}`) 2. Audit pack generation (export all evidence for an artifact) 3. Console UI proof chain visualization ## Record Schema ```json { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Artifact Canonical Record v1", "type": "object", "required": ["canonical_id", "format", "sbom_ref", "created_at"], "properties": { "canonical_id": { "type": "string", "description": "sha256: computed per canonical-sbom-id-v1.md", "pattern": "^sha256:[a-f0-9]{64}$" }, "format": { "type": "string", "description": "Canonicalization format identifier", "const": "cyclonedx-jcs:1" }, "sbom_ref": { "type": "string", "description": "Content-addressable reference to the SBOM (CAS URI or OCI ref)", "examples": [ "cas://sbom/inventory/abc123.json", "oci://registry/repo@sha256:abc123" ] }, "attestations": { "type": "array", "description": "All DSSE attestations referencing this artifact", "items": { "type": "object", "required": ["predicate_type", "dsse_digest", "signed_at"], "properties": { "predicate_type": { "type": "string", "description": "Predicate type URI from the predicate registry" }, "dsse_digest": { "type": "string", "description": "SHA-256 of the DSSE envelope body", "pattern": "^sha256:[a-f0-9]{64}$" }, "signer_keyid": { "type": "string", "description": "Key ID of the signer" }, "rekor_entry_id": { "type": "string", "description": "Rekor transparency log entry UUID (null if offline)" }, "rekor_tile": { "type": "string", "description": "Rekor tile URL for inclusion proof verification" }, "signed_at": { "type": "string", "format": "date-time" } } } }, "referrers": { "type": "array", "description": "OCI referrers (symbol bundles, attestation manifests)", "items": { "type": "object", "required": ["media_type", "descriptor_digest"], "properties": { "media_type": { "type": "string", "description": "OCI media type", "examples": [ "application/vnd.stella.symbols+tar", "application/vnd.in-toto+json" ] }, "descriptor_digest": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" }, "registry": { "type": "string", "description": "Registry hostname" } } } }, "vex_refs": { "type": "array", "description": "VEX consensus records targeting this artifact", "items": { "type": "object", "required": ["vulnerability_id", "consensus_status"], "properties": { "vulnerability_id": { "type": "string", "description": "CVE or advisory ID" }, "consensus_status": { "type": "string", "enum": ["affected", "not_affected", "under_investigation", "fixed"] }, "confidence_score": { "type": "number", "minimum": 0, "maximum": 1 }, "consensus_digest": { "type": "string", "description": "SHA-256 of the VexLens consensus record" }, "dsse_digest": { "type": "string", "description": "SHA-256 of the VEX attestation DSSE (if signed)" }, "rekor_tile": { "type": "string", "description": "Rekor tile URL (if anchored)" } } } }, "created_at": { "type": "string", "format": "date-time" }, "updated_at": { "type": "string", "format": "date-time" } } } ``` ## Content Addressing The record itself is content-addressed: ``` record_id := sha256(JCS(record_json)) ``` This enables deduplication and integrity verification of the record. ## Storage Design ### Option A: Materialized View (recommended for v1) Create a PostgreSQL materialized view joining existing tables: ```sql CREATE MATERIALIZED VIEW proofchain.artifact_canonical_records AS SELECT se.bom_digest AS canonical_id, 'cyclonedx-jcs:1' AS format, se.artifact_digest, se.purl, se.created_at, -- Attestations aggregated as JSONB array COALESCE( jsonb_agg(DISTINCT jsonb_build_object( 'predicate_type', de.predicate_type, 'dsse_digest', de.body_hash, 'signer_keyid', de.signer_keyid, 'rekor_entry_id', re.uuid, 'rekor_tile', re.log_id, 'signed_at', de.signed_at )) FILTER (WHERE de.env_id IS NOT NULL), '[]'::jsonb ) AS attestations FROM proofchain.sbom_entries se LEFT JOIN proofchain.dsse_envelopes de ON de.entry_id = se.entry_id LEFT JOIN proofchain.rekor_entries re ON re.env_id = de.env_id GROUP BY se.entry_id, se.bom_digest, se.artifact_digest, se.purl, se.created_at; CREATE UNIQUE INDEX idx_acr_canonical_id ON proofchain.artifact_canonical_records(canonical_id); ``` **Refresh strategy:** `REFRESH MATERIALIZED VIEW CONCURRENTLY` triggered by: - New DSSE envelope submission - New Rekor entry confirmation - Periodic schedule (every 5 minutes) ### Option B: Dedicated Table (for v2 if write patterns emerge) If the record needs to be written to directly (e.g., for referrers and VEX refs that come from different services), migrate to a dedicated table with trigger-based population from upstream sources. ## API Integration ### Evidence Thread Endpoint `GET /api/v1/evidence/thread/{canonical_id}` Returns the Artifact Canonical Record for the given `canonical_id`. The response includes all attestations, referrers, and VEX refs. Query parameters: - `include_attestations` (boolean, default true) - `include_vex` (boolean, default true) - `include_referrers` (boolean, default true) ### Evidence Thread by PURL `GET /api/v1/evidence/thread?purl={purl}` Resolves PURL to `canonical_id` via `sbom_entries` table, then returns the record. ## Offline Mode When Rekor is unavailable (air-gapped deployment): - `rekor_entry_id` and `rekor_tile` fields are `null` - A `transparency_status` field indicates `"offline"` with a `reason` sub-field - Verification uses bundled checkpoints instead of live Rekor queries ## Determinism The record's content is deterministic given frozen inputs: - Attestation arrays sorted by `signed_at` ascending, then `predicate_type` ascending - VEX refs sorted by `vulnerability_id` ascending - Referrers sorted by `media_type` ascending, then `descriptor_digest` ascending ## References - `docs/contracts/canonical-sbom-id-v1.md` - Canonical ID computation - `docs/modules/attestor/architecture.md` - DSSE envelope storage - `docs/modules/evidence-locker/attestation-contract.md` - Evidence Bundle v1 - Endpoint contract: S00-T05-EVID-01