SPRINT_3600_0001_0001 - Reachability Drift Detection Master Plan
This commit is contained in:
249
docs/db/triage_schema.sql
Normal file
249
docs/db/triage_schema.sql
Normal file
@@ -0,0 +1,249 @@
|
||||
-- Stella Ops Triage Schema (PostgreSQL)
|
||||
-- System of record: PostgreSQL
|
||||
-- Ephemeral acceleration: Valkey (not represented here)
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- Extensions
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
|
||||
-- Enums
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'triage_lane') THEN
|
||||
CREATE TYPE triage_lane AS ENUM (
|
||||
'ACTIVE',
|
||||
'BLOCKED',
|
||||
'NEEDS_EXCEPTION',
|
||||
'MUTED_REACH',
|
||||
'MUTED_VEX',
|
||||
'COMPENSATED'
|
||||
);
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'triage_verdict') THEN
|
||||
CREATE TYPE triage_verdict AS ENUM ('SHIP', 'BLOCK', 'EXCEPTION');
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'triage_reachability') THEN
|
||||
CREATE TYPE triage_reachability AS ENUM ('YES', 'NO', 'UNKNOWN');
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'triage_vex_status') THEN
|
||||
CREATE TYPE triage_vex_status AS ENUM ('affected', 'not_affected', 'under_investigation', 'unknown');
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'triage_decision_kind') THEN
|
||||
CREATE TYPE triage_decision_kind AS ENUM ('MUTE_REACH', 'MUTE_VEX', 'ACK', 'EXCEPTION');
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'triage_snapshot_trigger') THEN
|
||||
CREATE TYPE triage_snapshot_trigger AS ENUM (
|
||||
'FEED_UPDATE',
|
||||
'VEX_UPDATE',
|
||||
'SBOM_UPDATE',
|
||||
'RUNTIME_TRACE',
|
||||
'POLICY_UPDATE',
|
||||
'DECISION',
|
||||
'RESCAN'
|
||||
);
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'triage_evidence_type') THEN
|
||||
CREATE TYPE triage_evidence_type AS ENUM (
|
||||
'SBOM_SLICE',
|
||||
'VEX_DOC',
|
||||
'PROVENANCE',
|
||||
'CALLSTACK_SLICE',
|
||||
'REACHABILITY_PROOF',
|
||||
'REPLAY_MANIFEST',
|
||||
'POLICY',
|
||||
'SCAN_LOG',
|
||||
'OTHER'
|
||||
);
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- Core: finding (caseId == findingId)
|
||||
CREATE TABLE IF NOT EXISTS triage_finding (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
asset_id uuid NOT NULL,
|
||||
environment_id uuid NULL,
|
||||
asset_label text NOT NULL, -- e.g. "prod/api-gateway:1.2.3"
|
||||
purl text NOT NULL, -- package-url
|
||||
cve_id text NULL,
|
||||
rule_id text NULL,
|
||||
first_seen_at timestamptz NOT NULL DEFAULT now(),
|
||||
last_seen_at timestamptz NOT NULL DEFAULT now(),
|
||||
UNIQUE (asset_id, environment_id, purl, cve_id, rule_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_triage_finding_last_seen ON triage_finding (last_seen_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS ix_triage_finding_asset_label ON triage_finding (asset_label);
|
||||
CREATE INDEX IF NOT EXISTS ix_triage_finding_purl ON triage_finding (purl);
|
||||
CREATE INDEX IF NOT EXISTS ix_triage_finding_cve ON triage_finding (cve_id);
|
||||
|
||||
-- Effective VEX (post-merge), with preserved provenance pointers
|
||||
CREATE TABLE IF NOT EXISTS triage_effective_vex (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
finding_id uuid NOT NULL REFERENCES triage_finding(id) ON DELETE CASCADE,
|
||||
status triage_vex_status NOT NULL,
|
||||
source_domain text NOT NULL, -- "excititor"
|
||||
source_ref text NOT NULL, -- stable ref string (preserve prune source)
|
||||
pruned_sources jsonb NULL, -- array of pruned items with reasons (optional)
|
||||
dsse_envelope_hash text NULL,
|
||||
signature_ref text NULL, -- rekor/ledger ref
|
||||
issuer text NULL,
|
||||
valid_from timestamptz NOT NULL DEFAULT now(),
|
||||
valid_to timestamptz NULL,
|
||||
collected_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_triage_effective_vex_finding ON triage_effective_vex (finding_id, collected_at DESC);
|
||||
|
||||
-- Reachability results
|
||||
CREATE TABLE IF NOT EXISTS triage_reachability_result (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
finding_id uuid NOT NULL REFERENCES triage_finding(id) ON DELETE CASCADE,
|
||||
reachable triage_reachability NOT NULL,
|
||||
confidence smallint NOT NULL CHECK (confidence >= 0 AND confidence <= 100),
|
||||
static_proof_ref text NULL, -- evidence ref (callgraph slice / CFG slice)
|
||||
runtime_proof_ref text NULL, -- evidence ref (runtime hits)
|
||||
inputs_hash text NOT NULL, -- hash of inputs used to compute reachability
|
||||
computed_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_triage_reachability_finding ON triage_reachability_result (finding_id, computed_at DESC);
|
||||
|
||||
-- Risk/lattice result (scanner.webservice output)
|
||||
CREATE TABLE IF NOT EXISTS triage_risk_result (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
finding_id uuid NOT NULL REFERENCES triage_finding(id) ON DELETE CASCADE,
|
||||
policy_id text NOT NULL,
|
||||
policy_version text NOT NULL,
|
||||
inputs_hash text NOT NULL,
|
||||
score int NOT NULL CHECK (score >= 0 AND score <= 100),
|
||||
verdict triage_verdict NOT NULL,
|
||||
lane triage_lane NOT NULL,
|
||||
why text NOT NULL, -- short narrative
|
||||
explanation jsonb NULL, -- structured lattice explanation for UI diffing
|
||||
computed_at timestamptz NOT NULL DEFAULT now(),
|
||||
UNIQUE (finding_id, policy_id, policy_version, inputs_hash)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_triage_risk_finding ON triage_risk_result (finding_id, computed_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS ix_triage_risk_lane ON triage_risk_result (lane, computed_at DESC);
|
||||
|
||||
-- Signed Decisions (mute/ack/exception), reversible by revoke
|
||||
CREATE TABLE IF NOT EXISTS triage_decision (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
finding_id uuid NOT NULL REFERENCES triage_finding(id) ON DELETE CASCADE,
|
||||
kind triage_decision_kind NOT NULL,
|
||||
reason_code text NOT NULL,
|
||||
note text NULL,
|
||||
policy_ref text NULL, -- optional: policy that allowed decision
|
||||
ttl timestamptz NULL,
|
||||
actor_subject text NOT NULL, -- Authority subject (sub)
|
||||
actor_display text NULL,
|
||||
signature_ref text NULL, -- DSSE signature reference
|
||||
dsse_hash text NULL,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
revoked_at timestamptz NULL,
|
||||
revoke_reason text NULL,
|
||||
revoke_signature_ref text NULL,
|
||||
revoke_dsse_hash text NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_triage_decision_finding ON triage_decision (finding_id, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS ix_triage_decision_kind ON triage_decision (kind, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS ix_triage_decision_active ON triage_decision (finding_id) WHERE revoked_at IS NULL;
|
||||
|
||||
-- Evidence artifacts (hash-addressed, signed)
|
||||
CREATE TABLE IF NOT EXISTS triage_evidence_artifact (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
finding_id uuid NOT NULL REFERENCES triage_finding(id) ON DELETE CASCADE,
|
||||
type triage_evidence_type NOT NULL,
|
||||
title text NOT NULL,
|
||||
issuer text NULL,
|
||||
signed boolean NOT NULL DEFAULT false,
|
||||
signed_by text NULL,
|
||||
content_hash text NOT NULL,
|
||||
signature_ref text NULL,
|
||||
media_type text NULL,
|
||||
uri text NOT NULL, -- object store / file path / inline ref
|
||||
size_bytes bigint NULL,
|
||||
metadata jsonb NULL,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
UNIQUE (finding_id, type, content_hash)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_triage_evidence_finding ON triage_evidence_artifact (finding_id, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS ix_triage_evidence_type ON triage_evidence_artifact (type, created_at DESC);
|
||||
|
||||
-- Snapshots for Smart-Diff (immutable records of input/output changes)
|
||||
CREATE TABLE IF NOT EXISTS triage_snapshot (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
finding_id uuid NOT NULL REFERENCES triage_finding(id) ON DELETE CASCADE,
|
||||
trigger triage_snapshot_trigger NOT NULL,
|
||||
from_inputs_hash text NULL,
|
||||
to_inputs_hash text NOT NULL,
|
||||
summary text NOT NULL,
|
||||
diff_json jsonb NULL, -- optional: precomputed diff
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
UNIQUE (finding_id, to_inputs_hash, created_at)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_triage_snapshot_finding ON triage_snapshot (finding_id, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS ix_triage_snapshot_trigger ON triage_snapshot (trigger, created_at DESC);
|
||||
|
||||
-- Current-case view: latest risk + latest reachability + latest effective VEX
|
||||
CREATE OR REPLACE VIEW v_triage_case_current AS
|
||||
WITH latest_risk AS (
|
||||
SELECT DISTINCT ON (finding_id)
|
||||
finding_id, policy_id, policy_version, inputs_hash, score, verdict, lane, why, computed_at
|
||||
FROM triage_risk_result
|
||||
ORDER BY finding_id, computed_at DESC
|
||||
),
|
||||
latest_reach AS (
|
||||
SELECT DISTINCT ON (finding_id)
|
||||
finding_id, reachable, confidence, static_proof_ref, runtime_proof_ref, computed_at
|
||||
FROM triage_reachability_result
|
||||
ORDER BY finding_id, computed_at DESC
|
||||
),
|
||||
latest_vex AS (
|
||||
SELECT DISTINCT ON (finding_id)
|
||||
finding_id, status, issuer, signature_ref, source_domain, source_ref, collected_at
|
||||
FROM triage_effective_vex
|
||||
ORDER BY finding_id, collected_at DESC
|
||||
)
|
||||
SELECT
|
||||
f.id AS case_id,
|
||||
f.asset_id,
|
||||
f.environment_id,
|
||||
f.asset_label,
|
||||
f.purl,
|
||||
f.cve_id,
|
||||
f.rule_id,
|
||||
f.first_seen_at,
|
||||
f.last_seen_at,
|
||||
r.policy_id,
|
||||
r.policy_version,
|
||||
r.inputs_hash,
|
||||
r.score,
|
||||
r.verdict,
|
||||
r.lane,
|
||||
r.why,
|
||||
r.computed_at AS risk_computed_at,
|
||||
coalesce(re.reachable, 'UNKNOWN'::triage_reachability) AS reachable,
|
||||
re.confidence AS reach_confidence,
|
||||
v.status AS vex_status,
|
||||
v.issuer AS vex_issuer,
|
||||
v.signature_ref AS vex_signature_ref,
|
||||
v.source_domain AS vex_source_domain,
|
||||
v.source_ref AS vex_source_ref
|
||||
FROM triage_finding f
|
||||
LEFT JOIN latest_risk r ON r.finding_id = f.id
|
||||
LEFT JOIN latest_reach re ON re.finding_id = f.id
|
||||
LEFT JOIN latest_vex v ON v.finding_id = f.id;
|
||||
|
||||
COMMIT;
|
||||
Reference in New Issue
Block a user