3.6 KiB
3.6 KiB
Ledger Risk Schema Prep — PREP-LEDGER-RISK-66-001/002
Status: Prep complete (2025-11-20) Owners: Findings Ledger Guild · Risk Engine Guild Scope: Contract + data model for PREP-LEDGER-RISK-66-001/002 (risk scoring fields and deterministic upsert).
Field definitions (canonical finding projection)
risk_score(numeric, 0–100, 2dp) — monotonic per(finding_id, profile_version); computed by Risk Engine.risk_severity(enum) — derived mapping:critical >= 90,high >= 70,medium >= 40,low >= 10,informational < 10.risk_profile_version(string) — semantic version of the scoring policy/profile; required.risk_explanation_id(uuid/string) — pointer to Risk Engine explanation payload stored in Risk service (not duplicated in ledger).risk_event_sequence(long) — ledger sequence of the last applied risk event; enforces monotonic updates.risk_updated_at(ISO-8601 UTC) — when the score was last written.
Storage and indexes (MongoDB)
- Collection:
findings(existing). Add fields above to the projection document. - Unique compound index:
{ tenant: 1, finding_id: 1, risk_profile_version: 1 }. - Query helper index for exports/UI:
{ tenant: 1, risk_severity: 1, risk_score: -1, observed_at: -1 }. - TTL: none; scores are historical but superseded by deterministic upsert described below.
Deterministic upsert flow (LEDGER-RISK-66-002)
- Risk Engine emits
RiskScoreAppliedevent with{tenant, finding_id, profile_version, score, explanation_id, event_sequence}. - Handler loads current projection by
(tenant, finding_id); compares(profile_version, event_sequence):- If incoming
event_sequence< storedrisk_event_sequence→ ignore (idempotent). - If equal → idempotent update allowed only when score/severity unchanged.
- If greater → write new values and set
risk_event_sequence = event_sequence.
- If incoming
- All writes recorded in ledger append with same event_sequence for audit; projection updates deterministic by sequence ordering.
- Exports (
/ledger/export/findings) surface these fields; snapshot bundles reuse the same shape.
API/SDK contract hooks
- OAS baseline will mark all four fields in the finding shapes (canonical + compact) as optional today, required once migrations finish.
/ledger/export/findingsfilters:risk_profile_version(already reserved), addrisk_severityandrisk_score_min/maxin the next OAS bump.- UI/SDK must treat missing
risk_profile_versionas “not yet scored”.
Migration/rollout plan (LEDGER-RISK-66-001)
- Step 1: Add fields and indexes behind feature flag
RiskScoringEnabled(default off). - Step 2: Backfill for latest profile per tenant using Risk Engine batch export; write via deterministic upsert to enforce ordering.
- Step 3: Enable streaming ingestion of
RiskScoreAppliedevents; monitor lag via metricledger_risk_score_apply_lag_seconds. - Step 4: Flip default for
RiskScoringEnabledto on after backfill success criteria:- 99.9% of existing findings have
risk_profile_versionpopulated. - No rejected events due to sequence regressions in the last 24h.
- 99.9% of existing findings have
- Step 5: Update OAS/SDK to mark fields required; notify UI/Export consumers.
Observability
- Log:
ledger.risk.applywith tenant, finding_id, profile_version, score, event_sequence, applied (bool). - Metrics:
ledger_risk_apply_total{result};ledger_risk_score_latest{severity}gauges per tenant. - Tracing: span
ledger.risk.applytaggingprofile_version,event_sequence,idempotent.
Handoff
- This document is the prep artefact for PREP-LEDGER-RISK-66-001/002. Implementation tasks wire schema + deterministic upsert and extend exports/OAS accordingly.