# Ledger Attestations Storage & Query Contract (LEDGER-OBS-54-001) Status: PrepComplete (2025-11-20) Owners: Findings Ledger Guild · Provenance Guild ## Goal Provide a deterministic storage/view contract so the `/v1/ledger/attestations` endpoint can be implemented without further design work. ## Table (proposed) - Name: `ledger_attestations` - Partitioning: tenant-scoped (same strategy as `ledger_events`). - Columns: - `tenant_id` (text, not null) - `attestation_id` (uuid, not null) - `artifact_id` (text, not null) — OCI digest or SBOM id - `finding_id` (text, null) - `verification_status` (text, not null; `verified|failed|unknown`) - `verification_time` (timestamptz, not null) - `dsse_digest` (text, not null; lowercase sha256) - `rekor_entry_id` (text, null) - `evidence_bundle_ref` (text, null) - `ledger_event_id` (uuid, not null) — source ledger event linking the attestation - `recorded_at` (timestamptz, not null) - `merkle_leaf_hash` (text, not null) - `root_hash` (text, not null) - `cycle_hash` (text, not null) - `projection_version` (text, not null) ## Indexes / ordering - PK: `(tenant_id, attestation_id)` - Paging index: `(tenant_id, recorded_at, attestation_id)` to back deterministic sort `recorded_at ASC, attestation_id ASC`. - Lookup indexes: - `(tenant_id, artifact_id, recorded_at DESC)` - `(tenant_id, finding_id, recorded_at DESC)` - `(tenant_id, verification_status, recorded_at DESC)` ## Query contract for `/v1/ledger/attestations` - Filters map to indexed columns: `artifactId`, `findingId`, `attestationId`, `status`, `sinceRecordedAt`, `untilRecordedAt`. - Pagination token encodes `{ recordedAt, attestationId, filtersHash }`; server must reject mismatched hash. - Response fields align 1:1 with columns above; no joins required. - Determinism: sort strictly by `(recorded_at ASC, attestation_id ASC)`; no server clocks in payload. ## Migration notes - Add table and indexes in the same migration (see `src/Findings/StellaOps.Findings.Ledger/migrations/004_ledger_attestations.sql`). - Backfill from existing provenance/verification store (if present) into this table with recorded_at = original verification timestamp. - Ensure writes/coalescing happen via ledger projections to keep `ledger_event_id`/`cycle_hash` consistent. ## Observability - Logs: `ledger.attestations.query` (tenant, filtersHash, limit, duration_ms, result_count). - Metrics: `ledger_attestations_queries_total{tenant,status}`, `ledger_attestations_failures_total{reason}`; reuse endpoint spans already defined in prep doc. ## Artefact location - Storage contract: `docs/modules/findings-ledger/prep/ledger-attestations-storage.md` - HTTP contract: `docs/modules/findings-ledger/prep/ledger-attestations-http.md`