Findings Ledger Schema Catalog (FL1–FL3)

Scope: Versioned canonical schemas for ledger events, projections, and exports.
Status: v1.0.0 sealed (2025-12-02) — breaking changes require new minor/major version tags.

1) Ledger event envelope — ledger.event.v1

FieldTypeNotes
event.iduuidV7 GUID allowed.
event.typestring (ledger_event_type)See schema.md §2.2.
event.tenantstringPartition key.
event.chainIduuidDerived when absent (tenant :: policyVersion), see workflow-inference.md.
event.sequencelongGapless per chain, starts at 1.
event.policyVersionstringSHA-256 digest of policy bundle; propagated into exports and DSSE.
event.findingobjectid, artifactId, vulnId.
event.actorobjectid, type (`system
event.occurredAtstring (UTC ISO-8601 ms)Domain clock.
event.recordedAtstring (UTC ISO-8601 ms)Service TimeProvider.
event.payloadobjectMutation-specific body.
event.evidenceBundleRefstring?DSSE/capsule id (optional).
event.airgap.bundleobject?See airgap-provenance.md.
event_hashchar(64)sha256(canonicalJson) lower-hex.
previous_hashchar(64)All-zero for chain genesis.
merkle_leaf_hashchar(64)`sha256(event_hash

Canonicalisation: UTF-8, sorted keys, lower-case enums, ISO-8601 UTC with millisecond precision, arrays stable-order. Any field addition bumps minor version.

NIS2 incident payload - nis2.incident.ledger.v1

nis2.incident.ledger.v1 is the event.payload.nis2 schema used by nis2.incident.{opened|classified|reported|closed} ledger events. It keeps incident state, classification evidence, reachability subgraph refs, VEX consensus refs, report-transmission evidence, closure data, and Evidence Locker incident-mode retention links inside the canonical ledger event body.

Replay rules:

2) Finding projection — ledger.projection.v1

FieldTypeNotes
tenantIdstringPartition key.
findingIdstringStable identity.
policyVersionstringHash of active policy bundle.
statusstring`affected
severitynumber0–10, 3 decimal places.
riskScorenumber0–10, 3 decimal places.
riskSeveritystring`low
riskProfileVersionstringVersion/hash from Risk Engine.
riskExplanationIduuid?Links to explain bundle.
labelsjsonKEV/runtime flags, sorted keys.
currentEventIduuidSource ledger event.
explainRefstring?Object storage / DSSE reference.
policyRationalejsonArray of rationale refs.
updatedAtstring UTCProjection timestamp.
cycleHashchar(64)sha256(canonicalProjectionJson); used in exports.

Projection deterministic hash recipe: serialize projection record with sorted keys (excluding updatedAt jitter) and hash via SHA-256. The replay harness recomputes and compares.

3) Export payloads — export.v1

Shapes share headers: policyVersion, projectionVersion (cycle hash), filtersHash, pageToken, observedAt, provenance (ledgerRoot, projectorVersion, policyHash, optional dsseDigest).

Canonical vs compact

Record fields

Filters hash: sha256(sortedQueryString); stored alongside fixtures for replayability.

4) Versioning rules

5) Reference artefacts

6) Rekor Entry Reference — rekor.entry.ref.v1 (Sprint: SPRINT_20260112_004_FINDINGS)

FieldTypeNotes
logIndexlong?Position in the Rekor log.
logIdstring?Log identifier (hex-encoded public key hash).
uuidstring?Unique entry identifier.
integratedTimelong?Unix epoch seconds when entry was integrated.
integratedTimeRfc3339string? (UTC ISO-8601)RFC3339 formatted integrated time for display/sorting.
entryUrlstring?Full URL to the Rekor entry for UI linking. It is present only when supplied by the pointer payload or derived from an explicitly configured Rekor base URL.

Usage: Attached to AttestationPointer records and evidence graph signature metadata. The integratedTimeRfc3339 field provides human-readable timestamps and deterministic sorting. The entryUrl enables direct linking from UI components when the deployment has an approved Rekor mirror or external endpoint configured.

Offline mode: When operating in air-gapped environments, entryUrl is null unless a local Rekor mirror URL is configured or the stored pointer already contains a URL. The integratedTime remains authoritative for timestamp verification.

7) Per-case VEX-consensus trust override — findings.vex_statement_trust_override (Sprint: SPRINT_20260629_001 WS3)

Tenant-scoped table backing the per-case override that lets an operator UNTRUST a VEX-consensus verdict for a specific (vuln, product[, source]). Default-TRUST: in the absence of any row a consensus verdict is trusted (subject to the global EvidenceWeightedScore:TrustConsensusVexByDefault gate); an explicit enabled row with trusted=false suppresses the auto-cap for that case. Migration migrations/003_vex_statement_trust_override.sql (embedded, idempotent, RLS via findings_ledger_app.require_current_tenant()).

ColumnTypeNotes
iduuidgen_random_uuid() default.
tenant_idtextPartition key; PK is (tenant_id, id).
vuln_idtextVulnerability id (e.g. CVE-…).
product_keytextComponent purl / product key the consensus applies to.
vex_sourcetext?Optional winning-source scope; NULL = any source for this (vuln, product).
trustedboolfalse = untrust (the only reason to create a row); default false.
reasontext?Operator rationale.
actor_idtext?Operator subject id.
created_at / updated_attimestamptzApp-managed.

Uniqueness: partial unique indexes — (tenant_id, vuln_id, product_key, vex_source) where vex_source IS NOT NULL, and (tenant_id, vuln_id, product_key) where vex_source IS NULL (one wildcard row per pair). A source-scoped row wins over the wildcard at resolution time.

CRUD surface: /findings/vex-trust-overridesGET list / GET {id} (scope findings:read), POST / PUT {id} / DELETE {id} (scope findings:write); tenant from the stellaops:tenant claim (RequireTenant()). A mutation evicts all cached EWS scores (the override keys on (vuln, product), not a finding id).

Apply path: the Findings evidence bridge (AnchoredFindingEvidenceProvider) stamps a trusted not_affected/fixed VexLens consensus onto FindingEvidence.VexConsensus{Status,Source} so the EWS calculator caps the score to 0/Watchlist — gated by (a) the global flag, (b) operator-decision precedence (IDecisionService history), and © this per-case untrust store.