Here’s a compact, plug‑and‑play blueprint for making **“unknown” a first‑class, auditable state** in your VEX pipeline (fits Stella Ops nicely). # Why this matters (quick) VEX (OpenVEX/CSAF) often forces binary “affected/not_affected.” In practice, evidence is missing, conflicting, or stale. Treating **unknown** as a deliberate, signed, and replayable decision keeps you compliant (CRA/NIS2/DORA) and operationally honest. # Lifecycle & precedence ``` unvalidated → evidence_ingested → proof_anchored → merge_candidate → merged_outcome { affected | not_affected | unknown } → scored → triage ``` * **Default unknown at ingest** if anything is missing/ambiguous. * **Precedence:** latest valid timestamp wins; hard tie → **lexicographic source_id** tie‑break. * Carry a **provenance bundle** with every hop: `{source_id, timestamp, proof_hash}`. * Every **merged outcome** and **score** is **DSSE‑wrapped** and **Rekor‑anchored**. # Four readiness gates (fail fast) 1. **ingest_validation** * Schema‑valid OpenVEX/CSAF + DSSE envelope present. * Reject → `unvalidated` (record reasons). 2. **proof_anchor** * Rekor entry (UUID) + inclusion proof persisted. * Reject if no inclusion proof or log not reachable (offline mode: queue + mark `unknown`). 3. **merge_precheck** * Deterministic timestamp precedence; evidence sufficiency (at least one attestation + SBOM ref). * Reject if conflicts unresolved → stay at `proof_anchored` and set target outcome `unknown`. 4. **scoring_precondition** * `replay_success_ratio` (e.g., ≥0.95) on verification of DSSE/rekor bundles + provenance presence. * Reject if below threshold or provenance gaps → do not score; outcome remains `unknown`. # Deterministic merge rules * Normalize identifiers (CVE, package PURL, image digest). * Collapse equivalent justifications (OpenVEX) and product trees (CSAF). * If any required justification absent or conflicting → **merged_outcome = unknown** with rationale snapshot. * Merge is **idempotent**: same inputs → byte‑identical output and provenance trace. # API surface (minimal) ``` POST /v1/vex/ingest Body: DSSE-envelope { payload: OpenVEX|CSAF, signatures:[] } Resp: { state: "evidence_ingested"|"unvalidated", provenance_bundle, rekor_hint? } POST /v1/vex/merge Body: { product_ref, candidates:[{openvex_or_csaf_ref, provenance_bundle}], strategy:"timestamp_lexi_tiebreak" } Resp: { merged_outcome, provenance_trace[], dsse_signed_merged } POST /v1/score Body: { merged_outcome_ref, policy_id, replay_window } Resp: { signed_score_dsse, replay_verification:{ratio, failures[]}, gate_passed:boolean } GET /v1/triage?outcome=unknown Resp: [{ product_ref, vuln_id, last_timestamp, missing_evidence[], next_actions[] }] ``` # Evidence & storage * **EvidenceLocker** (your module) keeps: raw docs, DSSE envelopes, Rekor inclusion proofs, SBOM/attestation refs, provenance bundles. * Hash all decision artifacts; store `{artifact_hash → Rekor UUID}` map. * Offline/air‑gap: stage to local transparency log; when online, **bridge** to Rekor and backfill inclusion proofs. # Scoring model (example) * Base score source (e.g., CVSS/CVSS‑SR). * Dampener if outcome=`unknown`: apply policy (e.g., cap at 6.9 or bump to triage queue). * Require `replay_success_ratio ≥ threshold` and `provenance.complete=true` before emitting scores. # Acceptance tests (must‑pass) 1. **missing_evidence_defaults_unknown** * Ingest CSAF missing justification → merged outcome is `unknown` with rationale. 2. **dsse_anchor_presence** * Attempt score without Rekor inclusion → gate fails. 3. **timestamp_precedence_tiebreak** * Two equal timestamps from different sources → lexicographic `source_id` decides, deterministic. 4. **merge_idempotence** * Re‑run merge with same inputs → identical `dsse_signed_merged` hash. 5. **scoring_gate_replay_success** * Corrupt one signature in replay set → `replay_success_ratio` drops; scoring blocked. # CLI hints (nice DX) ``` stella vex ingest --file advisories/openvex.json --sign key.pem --rekor-url $REKOR stella vex merge --product my/image:sha256:… --inputs dir:./evidence stella score --policy default --replay-window 30d stella triage --outcome unknown --limit 50 ``` # UI touchpoints (lean) * **Evidence Ingest**: file drop (OpenVEX/CSAF), DSSE status, Rekor anchor badge. * **Merge Review**: side‑by‑side justifications, conflicts, deterministic decision summary. * **Scoring Gate**: replay bar (ratio), provenance checklist. * **Triage (unknown)**: prioritized queue with “missing evidence” chips and one‑click requests. # Drop‑in for Stella Ops modules * **Concelier**: orchestrates gates. * **Attestor**: DSSE wrap/verify. * **EvidenceLocker**: storage + provenance. * **AdvisoryAI**: explanation/triage suggestions (surfacing unknowns first). * **ReleaseOrchestrator**: policy “block if unknown>0 and critical path”. If you want, I can generate: * a ready OpenAPI spec for the 4 endpoints, * the DSSE/Rekor wiring stubs (C#) and a minimal SQLite/Postgres schema, * a Playwright test suite implementing the five acceptance tests.