Reachability Evidence Policy Gates
Status: Design v1 (Sprint 0401) + D5 lattice gate defaults IMPLEMENTED 2026-06-11 (§1a below; ratified decision D5 of
docs/modules/scanner/reachability-sink-strategy.md§10, sprintSPRINT_20260610_002POLICY-VEX). Owners: Policy Guild · Signals Guild · VEX Guild. Audience: Policy and VEX engineers defining how reachability evidence gates VEX status transitions.
This document defines the policy gates that enforce reachability-evidence requirements for VEX decisions. Gates prevent unsafe not_affected claims when the supporting evidence is insufficient.
1a. D5 lattice gate defaults (implemented)
The ratified D5 defaults bind the public reachability lattice (reachable:proven / reachable:likely / not-observed / unknown, strategy doc §7.0) to gate behavior. These are hard rules of the evaluators, not configuration; only the downgrade threshold is configurable, and it rides policy packs.
| Lattice verdict | Gate behavior (default and only) |
|---|---|
reachable:proven | May BLOCK a release. The path witness is machine-checked before the verdict object can exist, so a block is witness-backed by construction. |
reachable:likely | Never auto-blocks. Feeds evidence-weighted scoring (FeedsScoring). |
unknown | Never auto-blocks. Feeds scoring / unknowns budgets. |
not-observed | May only DRAFT a downgrade/suppression, and only when its mandatory coverage statement clears the pack threshold; the resulting VEX not_affected statement stays pending operator countersign (ADR-025) and is never auto-applied. Below threshold: refused, nothing drafted. |
Implementation surfaces:
- Verdict-tier gate (generic core):
ReachabilityVerdictGateinsrc/Scanner/__Libraries/StellaOps.Scanner.Reachability/Vex/ReachabilityVerdictGate.cs— pure, deterministic decision per(image digest, CVE, purl)verdict object:Block/DowngradeDrafted/None+ machine-readable reason. - Thresholds from policy packs, never code:
ReachabilityGateThresholdshas no default instance and requires pack provenance (name/version/sha256).ReachabilityGatePolicyPackLoaderloads — fail-closed — the default packdevops/policies/reachability-verdict-gate.policy.yaml(shipped defaults:minPercentNodesResolved: 80,minEntrypoints: 1). - Live promote-time gate:
PolicyRuntimeEvaluator(src/Policy/StellaOps.Policy.Engine/Services/) treats only proven reachability as blocking proof (FindingReachability.IsReachableProof);reachable:likely/potentially_reachablefindings cannot block regardless of pack rule content. Gate expressions may reference lattice wire names inreachability.stateand constrainreachability.evidence_tier(surface | package-symbols | heuristic | curated). - Drafted-pending-countersign VEX:
VexStatementDraft(…/Vex/VexStatementDraft.cs) — state machinePendingCountersign → Countersigned → Published(+ terminalRejected); publication refuses before touching the channel unless anOperatorCountersign(ADR-025operator-decision@v1) is attached.IVexStatusDeterminermaps the public verdict to VEX status + justification category (not-observed→not_affected+vulnerable_code_not_reachable).
Relationship to the rest of this document: §2–§9 below describe the richer designed gate set (lattice-state/uncertainty-tier/evidence-completeness) from Sprint 0401; the D5 defaults above are the ratified, shipped baseline those gates must remain consistent with (in particular: nothing below reachable:proven may block, and no not_affected may publish uncountersigned).
1. Overview
Policy gates act as checkpoints between evidence (reachability lattice state, uncertainty tier) and VEX status transitions. They ensure that:
- No false safety: “not_affected” requires strong evidence of unreachability
- Explicit uncertainty: Missing evidence triggers “under_investigation” rather than silence
- Audit trail: All gate decisions are logged with evidence references
2. Gate Types
2.1 Lattice State Gate
Guards VEX status transitions based on the v1 lattice state (see docs/modules/reach-graph/guides/lattice.md §9).
| Requested VEX Status | Required Lattice State | Gate Action |
|---|---|---|
not_affected | CU (ConfirmedUnreachable) | ✅ Allow |
not_affected | SU (StaticallyUnreachable) | ⚠️ Allow with warning, requires justification |
not_affected | RU (RuntimeUnobserved) | ⚠️ Allow with warning, requires justification |
not_affected | U, SR, RO, CR, X | ❌ Block |
affected | CR (ConfirmedReachable) | ✅ Allow |
affected | SR, RO | ✅ Allow |
affected | U, SU, RU, CU, X | ⚠️ Warn (potential false positive) |
under_investigation | Any | ✅ Allow (safe default) |
fixed | Any | ✅ Allow (remediation action) |
2.2 Uncertainty Tier Gate
Guards VEX status transitions based on the uncertainty tier (entropy-derived riskScore; see the roadmap note in lattice.md §8).
| Requested VEX Status | Uncertainty Tier | Gate Action |
|---|---|---|
not_affected | T1 (High) | ❌ Block |
not_affected | T2 (Medium) | ⚠️ Warn, require explicit override |
not_affected | T3 (Low) | ⚠️ Allow with advisory note |
not_affected | T4 (Negligible) | ✅ Allow |
affected | T1 (High) | ⚠️ Review required (may be false positive) |
affected | T2-T4 | ✅ Allow |
2.3 Evidence Completeness Gate
Guards based on the presence of required evidence artifacts.
| VEX Status | Required Evidence | Gate Action if Missing |
|---|---|---|
not_affected | graphHash (DSSE-attested) | ❌ Block |
not_affected | pathAnalysis.pathLength >= 0 | ❌ Block |
not_affected | confidence >= 0.8 | ⚠️ Warn if < 0.8 |
affected | graphHash OR runtimeProbe | ⚠️ Warn if neither |
under_investigation | None required | ✅ Allow |
3. Gate Evaluation Order
Gates are evaluated in this order; first blocking gate stops evaluation:
1. Evidence Completeness Gate → Block if required evidence missing
2. Lattice State Gate → Block if state incompatible with status
3. Uncertainty Tier Gate → Block/warn based on tier
4. Confidence Threshold Gate → Warn if confidence below threshold
4. Gate Decision Document
Each gate evaluation produces a decision document:
{
"gateId": "gate:vex:not_affected:2025-12-13T10:00:00Z",
"requestedStatus": "not_affected",
"subject": {
"vulnId": "CVE-2025-12345",
"purl": "pkg:maven/com.example/foo@1.0.0",
"symbolId": "sym:java:..."
},
"evidence": {
"latticeState": "CU",
"uncertaintyTier": "T3",
"graphHash": "blake3:...",
"riskScore": 0.25,
"confidence": 0.92
},
"gates": [
{
"name": "EvidenceCompleteness",
"result": "pass",
"reason": "graphHash present"
},
{
"name": "LatticeState",
"result": "pass",
"reason": "CU allows not_affected"
},
{
"name": "UncertaintyTier",
"result": "pass_with_note",
"reason": "T3 allows with advisory note",
"note": "MissingPurl uncertainty at 35% entropy"
}
],
"decision": "allow",
"advisory": "VEX status allowed with note: T3 uncertainty from MissingPurl",
"decidedAt": "2025-12-13T10:00:00Z"
}
5. Contested State Handling
When lattice state is X (Contested):
- Block all definitive statuses: Neither “not_affected” nor “affected” allowed
- Force “under_investigation”: Auto-assign until triage resolves conflict
- Emit triage event: Notify VEX operators of conflict with evidence links
- Evidence overlay: Show both static and runtime evidence for manual review
Contested Resolution Workflow
1. System detects X state
2. VEX status locked to "under_investigation"
3. Triage event emitted to operator queue
4. Operator reviews:
a. Static evidence (graph, paths)
b. Runtime evidence (probes, hits)
5. Operator provides resolution:
a. Trust static → state becomes SU/SR
b. Trust runtime → state becomes RU/RO
c. Add new evidence → recompute lattice
6. Gate re-evaluates with new state
6. Override Mechanism
Operators with vex:gate:override permission can bypass gates with mandatory fields:
{
"override": {
"gateId": "gate:vex:not_affected:...",
"operator": "user:alice@example.com",
"justification": "Manual review confirms code path is dead code",
"evidence": {
"type": "ManualReview",
"reviewId": "review:2025-12-13:001",
"attachments": ["cas://evidence/review/..."]
},
"approvedAt": "2025-12-13T11:00:00Z",
"expiresAt": "2026-01-13T11:00:00Z"
}
}
Override requirements:
justificationis mandatory and logged- Overrides expire after configurable period (default: 30 days)
- All overrides are auditable and appear in compliance reports
7. Configuration
Gate thresholds are configurable via PolicyGatewayOptions:
PolicyGateway:
Gates:
LatticeState:
AllowSUForNotAffected: true # Allow SU with warning
AllowRUForNotAffected: true # Allow RU with warning
RequireJustificationForWeakStates: true
UncertaintyTier:
BlockT1ForNotAffected: true
WarnT2ForNotAffected: true
EvidenceCompleteness:
RequireGraphHashForNotAffected: true
MinConfidenceForNotAffected: 0.8
MinConfidenceWarning: 0.6
Override:
DefaultExpirationDays: 30
RequireJustification: true
8. API Integration
POST /api/v1/vex/status
Request:
{
"vulnId": "CVE-2025-12345",
"purl": "pkg:maven/com.example/foo@1.0.0",
"status": "not_affected",
"justification": "vulnerable_code_not_present",
"reachabilityEvidence": {
"factDigest": "sha256:...",
"graphHash": "blake3:..."
}
}
Response (gate blocked):
{
"success": false,
"gateDecision": {
"decision": "block",
"blockedBy": "LatticeState",
"reason": "Lattice state SR (StaticallyReachable) incompatible with not_affected",
"currentState": "SR",
"requiredStates": ["CU", "SU", "RU"],
"suggestion": "Submit runtime probe evidence or change to under_investigation"
}
}
9. Metrics & Alerts
The policy gateway emits metrics:
| Metric | Labels | Description |
|---|---|---|
stellaops_gate_decisions_total | gate, result, status | Total gate decisions |
stellaops_gate_blocks_total | gate, reason | Total blocked requests |
stellaops_gate_overrides_total | operator | Total override uses |
stellaops_contested_states_total | vulnId | Active contested states |
Alert conditions:
stellaops_gate_overrides_totalrate > threshold → Audit reviewstellaops_contested_states_total> 10 → Triage backlog alert
10. Related documents
- Lattice model — the v1 formal 7-state lattice and v0 bucket scoring.
- Evidence schema —
richgraph-v1evidence fields. - VEX proof schema — VEX document schema with embedded proofs.
- ReachGraph → Policy Gate integration — how reachability evidence reaches the release promotion gate.
Changelog
| Version | Date | Author | Changes |
|---|---|---|---|
| 1.1.0 | 2026-06-11 | Scanner/Policy Guild | D5 lattice gate defaults implemented (§1a): proven-only blocking, pack-owned downgrade threshold, drafted-pending-countersign VEX (sprint SPRINT_20260610_002 POLICY-VEX) |
| 1.0.0 | 2025-12-13 | Policy Guild | Initial design from Sprint 0401 |
