This commit is contained in:
StellaOps Bot
2025-12-13 02:22:15 +02:00
parent 564df71bfb
commit 999e26a48e
395 changed files with 25045 additions and 2224 deletions

View File

@@ -646,6 +646,25 @@ Persisted documents capture the canonical envelope (`payload` field), tenant/nod
---
### 2.10 Signals - Reachability evidence chain
Signals APIs (base path: `/signals`) provide deterministic ingestion + scoring for the reachability evidence chain (callgraph -> runtime facts -> unknowns -> reachability facts) consumed by Policy and UI explainers.
| Method | Path | Scope | Notes |
|--------|------|-------|-------|
| `POST` | `/signals/callgraphs` | `signals:write` | Ingest a callgraph artifact (base64 JSON); response includes `graphHash` (sha256) and CAS URIs. |
| `POST` | `/signals/runtime-facts` | `signals:write` | Ingest runtime hit events (JSON). |
| `POST` | `/signals/runtime-facts/ndjson` | `signals:write` | Stream NDJSON events (optional gzip) with subject in query params. |
| `POST` | `/signals/unknowns` | `signals:write` | Ingest unresolved symbols/edges; influences `unknownsPressure`. |
| `GET` | `/signals/facts/{subjectKey}` | `signals:read` | Fetch `ReachabilityFactDocument` including `metadata.fact.digest` and per-target `states[]`. |
| `POST` | `/signals/reachability/recompute` | `signals:admin` | Recompute reachability for explicit targets and blocked edges. |
Docs & samples:
- `docs/api/signals/reachability-contract.md`
- `docs/api/signals/samples/callgraph-sample.json`
- `docs/api/signals/samples/facts-sample.json`
- `docs/reachability/lattice.md`
### 2.9 CVSS Receipts (Policy Gateway)
Policy Gateway proxies the Policy Engine CVSS v4 receipt APIs. Scopes: `policy.run` for create/amend, `findings.read` for read/history/policies.

View File

@@ -232,6 +232,17 @@ Slim wrapper used by CLI; returns 204 on success or `ERR_POL_001` payload.
> Schema reference: canonical policy run request/status/diff payloads ship with the Scheduler Models guide (`src/Scheduler/__Libraries/StellaOps.Scheduler.Models/docs/SCHED-MODELS-20-001-POLICY-RUNS.md`) and JSON fixtures under `samples/api/scheduler/policy-*.json`.
### 6.0 Reachability evidence inputs (Signals)
Policy Engine evaluations may be enriched with reachability facts produced by Signals. These facts are expected to be:
- **Deterministic:** referenced by `metadata.fact.digest` (sha256) and versioned via `metadata.fact.version`.
- **Evidence-linked:** per-target states include `path[]` and `evidence.runtimeHits[]` (and any future CAS/DSSE pointers).
Signals contract & scoring model:
- `docs/api/signals/reachability-contract.md`
- `docs/reachability/lattice.md`
### 6.1 Trigger Run
```

View File

@@ -1,66 +1,63 @@
# Signals Reachability API Contract (draft placeholder)
# Signals API (Reachability)
**Status:** Draft v0.2 · owner-proposed
**Status:** Working contract (aligns with `src/Signals/StellaOps.Signals/Program.cs`).
## Scope
- `/signals/callgraphs`, `/signals/facts`, reachability scoring overlays feeding UI/Web.
- Deterministic fixtures for SIG-26 chain (columns/badges, call paths, timelines, overlays, coverage).
## Auth, scopes, sealed mode
- **Scopes:** `signals:read`, `signals:write`, `signals:admin` (endpoint-specific; see below).
- **Dev fallback:** when Authority auth is disabled, requests must include `X-Scopes: <space-separated scopes>` (example: `X-Scopes: signals:write`).
- **Sealed mode:** when enabled, Signals may return `503` with `{ "error": "sealed-mode evidence invalid", ... }`.
## Endpoints
- `GET /signals/callgraphs` — returns call paths contributing to reachability.
- `GET /signals/facts` — returns reachability/coverage facts.
Common headers: `Authorization: DPoP <token>`, `DPoP: <proof>`, `X-StellaOps-Tenant`, optional `If-None-Match`.
Pagination: cursor via `pageToken`; default 50, max 200.
ETag: required on responses; clients must send `If-None-Match` for cache validation.
### Health & status
### Callgraphs response (draft)
```jsonc
{
"tenantId": "tenant-default",
"assetId": "registry.local/library/app@sha256:abc123",
"paths": [
{
"id": "path-1",
"source": "api-gateway",
"target": "jwt-auth-service",
"hops": [
{ "service": "api-gateway", "endpoint": "/login", "timestamp": "2025-12-05T10:00:00Z" },
{ "service": "jwt-auth-service", "endpoint": "/verify", "timestamp": "2025-12-05T10:00:01Z" }
],
"evidence": { "traceId": "trace-abc", "spanCount": 2, "score": 0.92 }
}
],
"pagination": { "nextPageToken": null },
"etag": "sig-callgraphs-etag"
}
```
- `GET /healthz` (anonymous)
- `GET /readyz` (anonymous; `503` when not ready or sealed-mode blocked)
- `GET /signals/ping` (scope: `signals:read`, response: `204`)
- `GET /signals/status` (scope: `signals:read`)
### Facts response (draft)
```jsonc
{
"tenantId": "tenant-default",
"facts": [
{
"id": "fact-1",
"type": "reachability",
"assetId": "registry.local/library/app@sha256:abc123",
"component": "pkg:npm/jsonwebtoken@9.0.2",
"status": "reachable",
"confidence": 0.88,
"observedAt": "2025-12-05T10:10:00Z",
"signalsVersion": "signals-2025.310.1"
}
],
"pagination": { "nextPageToken": "..." },
"etag": "sig-facts-etag"
}
```
### Callgraph ingestion & retrieval
### Samples
- Callgraphs: `docs/api/signals/samples/callgraph-sample.json`
- Facts: `docs/api/signals/samples/facts-sample.json`
- `POST /signals/callgraphs` (scope: `signals:write`)
- Body: `CallgraphIngestRequest` (`language`, `component`, `version`, `artifactContentBase64`, …).
- Response: `202 Accepted` with `CallgraphIngestResponse` and `Location: /signals/callgraphs/{callgraphId}`.
- Graph hash is computed deterministically from normalized nodes/edges/roots; see `graphHash` in the response.
- `GET /signals/callgraphs/{callgraphId}` (scope: `signals:read`)
- `GET /signals/callgraphs/{callgraphId}/manifest` (scope: `signals:read`)
### Outstanding
- Finalize score model, accepted `type` values, and max page size.
- Provide OpenAPI/JSON schema and error codes.
Sample request: `docs/api/signals/samples/callgraph-sample.json`
### Runtime facts ingestion
- `POST /signals/runtime-facts` (scope: `signals:write`)
- Body: `RuntimeFactsIngestRequest` with `subject`, `callgraphId`, and `events[]`.
- `POST /signals/runtime-facts/ndjson?callgraphId=...&scanId=...` (scope: `signals:write`)
- Body: NDJSON of `RuntimeFactEvent` objects; `Content-Encoding: gzip` supported.
- `POST /signals/runtime-facts/synthetic` (scope: `signals:write`)
- Generates a small deterministic sample set of runtime events for a callgraph to unblock testing.
### Unknowns ingestion & retrieval
- `POST /signals/unknowns` (scope: `signals:write`)
- Body: `UnknownsIngestRequest` (`subject`, `callgraphId`, `unknowns[]`).
- `GET /signals/unknowns/{subjectKey}` (scope: `signals:read`)
### Reachability scoring & facts
- `POST /signals/reachability/recompute` (scope: `signals:admin`)
- Body: `ReachabilityRecomputeRequest` (`callgraphId`, `subject`, `entryPoints[]`, `targets[]`, optional `runtimeHits[]`, optional `blockedEdges[]`).
- Response: `200 OK` with `{ id, callgraphId, subject, entryPoints, states, computedAt }`.
- `GET /signals/facts/{subjectKey}` (scope: `signals:read`)
- Response: `ReachabilityFactDocument` (per-target states, `score`, `riskScore`, unknowns pressure, optional uncertainty states, runtime facts snapshot).
Sample fact: `docs/api/signals/samples/facts-sample.json`
### Reachability union bundle ingestion (CAS layout)
- `POST /signals/reachability/union` (scope: `signals:write`)
- Body: `application/zip` bundle containing `nodes.ndjson`, `edges.ndjson`, `meta.json`.
- Optional header: `X-Analysis-Id` (defaults to a new GUID if omitted).
- Response: `202 Accepted` with `ReachabilityUnionIngestResponse` and `Location: /signals/reachability/union/{analysisId}/meta`.
- `GET /signals/reachability/union/{analysisId}/meta` (scope: `signals:read`)
- `GET /signals/reachability/union/{analysisId}/files/{fileName}` (scope: `signals:read`)

View File

@@ -1,23 +1,16 @@
{
"tenantId": "tenant-default",
"assetId": "registry.local/library/app@sha256:abc123",
"paths": [
{
"id": "path-1",
"source": "api-gateway",
"target": "jwt-auth-service",
"hops": [
{ "service": "api-gateway", "endpoint": "/login", "timestamp": "2025-12-05T10:00:00Z" },
{ "service": "jwt-auth-service", "endpoint": "/verify", "timestamp": "2025-12-05T10:00:01Z" }
],
"evidence": {
"traceId": "trace-abc",
"spanCount": 2,
"score": 0.92
}
}
],
"pagination": {
"nextPageToken": null
"language": "java",
"component": "pkg:maven/com.acme/demo-app@1.0.0?type=jar",
"version": "1.0.0",
"artifactContentType": "application/json",
"artifactFileName": "callgraph.json",
"artifactContentBase64": "eyJzY2hlbWFfdmVyc2lvbiI6IjEuMCIsInJvb3RzIjpbeyJpZCI6ImZ1bmM6amF2YTpjb20uYWNtZS5BcHAubWFpbiIsInBoYXNlIjoicnVudGltZSIsInNvdXJjZSI6InN0YXRpYyJ9XSwibm9kZXMiOlt7ImlkIjoiZnVuYzpqYXZhOmNvbS5hY21lLkFwcC5tYWluIiwibmFtZSI6Im1haW4iLCJraW5kIjoiZnVuY3Rpb24iLCJuYW1lc3BhY2UiOiJjb20uYWNtZSIsImZpbGUiOiJBcHAuamF2YSIsImxpbmUiOjEsImxhbmd1YWdlIjoiamF2YSJ9LHsiaWQiOiJmdW5jOmphdmE6Y29tLmFjbWUuQXV0aC52ZXJpZnkiLCJuYW1lIjoidmVyaWZ5Iiwia2luZCI6ImZ1bmN0aW9uIiwibmFtZXNwYWNlIjoiY29tLmFjbWUuYXV0aCIsImZpbGUiOiJBdXRoLmphdmEiLCJsaW5lIjo0MiwibGFuZ3VhZ2UiOiJqYXZhIn1dLCJlZGdlcyI6W3siZnJvbSI6ImZ1bmM6amF2YTpjb20uYWNtZS5BcHAubWFpbiIsInRvIjoiZnVuYzpqYXZhOmNvbS5hY21lLkF1dGgudmVyaWZ5Iiwia2luZCI6ImNhbGwiLCJjb25maWRlbmNlIjowLjl9XSwiYW5hbHl6ZXIiOnsibmFtZSI6ImRlbW8iLCJ2ZXJzaW9uIjoiMC4wLjAifX0=",
"metadata": {
"scanId": "scan-0001"
},
"schemaVersion": "1.0",
"analyzer": {
"name": "demo",
"version": "0.0.0"
}
}

View File

@@ -1,26 +1,70 @@
{
"tenantId": "tenant-default",
"facts": [
"id": "fact0000000000000000000000000000001",
"callgraphId": "callgraph-0001",
"subject": {
"scanId": "scan-0001",
"imageDigest": "sha256:abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc1"
},
"entryPoints": [
"func:java:com.acme.App.main"
],
"states": [
{
"id": "fact-1",
"type": "reachability",
"assetId": "registry.local/library/app@sha256:abc123",
"component": "pkg:npm/jsonwebtoken@9.0.2",
"status": "reachable",
"confidence": 0.88,
"observedAt": "2025-12-05T10:10:00Z",
"signalsVersion": "signals-2025.310.1"
"target": "func:java:com.acme.Admin.debug",
"reachable": false,
"confidence": 0.25,
"bucket": "unreachable",
"weight": 0.0,
"score": 0.0,
"path": [],
"evidence": {
"runtimeHits": [],
"blockedEdges": []
}
},
{
"id": "fact-2",
"type": "coverage",
"assetId": "registry.local/library/app@sha256:abc123",
"metric": "sensors_present",
"value": 0.94,
"observedAt": "2025-12-05T10:11:00Z"
"target": "func:java:com.acme.Auth.verify",
"reachable": true,
"confidence": 0.9,
"bucket": "runtime",
"weight": 0.45,
"score": 0.405,
"path": [
"func:java:com.acme.App.main",
"func:java:com.acme.Auth.verify"
],
"evidence": {
"runtimeHits": [
"func:java:com.acme.Auth.verify"
],
"blockedEdges": []
}
}
],
"pagination": {
"nextPageToken": "eyJmYWN0SWQiOiJmYWN0LTIifQ"
}
"runtimeFacts": [
{
"symbolId": "func:java:com.acme.Auth.verify",
"codeId": "code:java:com.acme.Auth.verify",
"purl": "pkg:maven/com.acme/demo-app@1.0.0?type=jar",
"processId": 1234,
"processName": "demo-app",
"containerId": "containerd://0000000000000000",
"hitCount": 3,
"observedAt": "2025-12-12T00:00:00Z",
"metadata": {
"source": "synthetic-probe"
}
}
],
"metadata": {
"fact.digest": "sha256:0000000000000000000000000000000000000000000000000000000000000000",
"fact.digest.alg": "sha256",
"fact.version": "1"
},
"score": 0.2025,
"riskScore": 0.2025,
"unknownsCount": 0,
"unknownsPressure": 0.0,
"computedAt": "2025-12-12T00:00:00Z",
"subjectKey": "scan-0001"
}

View File

@@ -50,26 +50,26 @@
| 22 | UI-AUDIT-05-002 | DONE | Evidence: `src/Web/StellaOps.Web/src/app/features/triage/triage-audit-bundle-new.component.ts` | UI Guild; Export Center Guild (src/Web/StellaOps.Web) | Build Audit Bundle creation wizard: subject artifact+digest selection, time window picker, content checklist (Vuln reports, SBOM, VEX, Policy evals, Attestations). |
| 23 | UI-AUDIT-05-003 | DONE | Evidence: `src/Web/StellaOps.Web/src/app/features/triage/triage-audit-bundle-new.component.ts`; `src/Web/StellaOps.Web/src/app/core/api/audit-bundles.client.ts` | UI Guild; Export Center Guild (src/Web/StellaOps.Web) | Wire audit bundle creation to POST /v1/audit-bundles, show progress, display bundle ID, hash, download button, and OCI reference on completion. |
| 24 | UI-AUDIT-05-004 | DONE | Evidence: `src/Web/StellaOps.Web/src/app/features/triage/triage-audit-bundles.component.ts` | UI Guild (src/Web/StellaOps.Web) | Add audit bundle history view: list previously created bundles with bundleId, createdAt, subject, download/view actions. |
| 25 | API-VEX-06-001 | BLOCKED | Blocked: needs `SCHEMA-08-001` + `DTO-09-001` sign-off/implementation in `src/VulnExplorer` | API Guild (src/VulnExplorer) | Implement POST /v1/vex-decisions endpoint with VexDecisionDto request/response per schema, validation, attestation generation trigger. |
| 26 | API-VEX-06-002 | BLOCKED | Blocked: depends on API-VEX-06-001 | API Guild (src/VulnExplorer) | Implement PATCH /v1/vex-decisions/{id} for updating existing decisions with supersedes tracking. |
| 27 | API-VEX-06-003 | BLOCKED | Blocked: depends on API-VEX-06-002 | API Guild (src/VulnExplorer) | Implement GET /v1/vex-decisions with filters for vulnerabilityId, subject, status, scope, validFor. |
| 28 | API-AUDIT-07-001 | BLOCKED | Blocked: needs `SCHEMA-08-003` + Export Center job/ZIP/OCI implementation in `src/ExportCenter` | API Guild (src/ExportCenter) | Implement POST /v1/audit-bundles endpoint with bundle creation, index generation, ZIP/OCI artifact production. |
| 29 | API-AUDIT-07-002 | BLOCKED | Blocked: depends on API-AUDIT-07-001 | API Guild (src/ExportCenter) | Implement GET /v1/audit-bundles/{bundleId} for bundle download with integrity verification. |
| 30 | SCHEMA-08-001 | BLOCKED | Blocked: Action Tracker #1 (Platform + Excititor schema review/sign-off) | Platform Guild | Review and finalize `docs/schemas/vex-decision.schema.json` (JSON Schema 2020-12) per advisory; confirm examples and versioning. |
| 31 | SCHEMA-08-002 | BLOCKED | Blocked: Action Tracker #2 (Attestor predicate review/sign-off) | Platform Guild | Review and finalize `docs/schemas/attestation-vuln-scan.schema.json` predicate schema; align predicateType URI and required fields. |
| 32 | SCHEMA-08-003 | BLOCKED | Blocked: Action Tracker #3 (Export Center format review/sign-off) | Platform Guild | Review and finalize `docs/schemas/audit-bundle-index.schema.json` for audit bundle manifest structure; confirm stable IDs and deterministic ordering guidance. |
| 33 | DTO-09-001 | BLOCKED | Blocked: depends on SCHEMA-08-001 finalization | API Guild | Create VexDecisionDto, SubjectRefDto, EvidenceRefDto, VexScopeDto, ValidForDto C# DTOs per advisory. |
| 34 | DTO-09-002 | BLOCKED | Blocked: depends on SCHEMA-08-002 finalization | API Guild | Create VulnScanAttestationDto, AttestationSubjectDto, VulnScanPredicateDto C# DTOs per advisory. |
| 35 | DTO-09-003 | BLOCKED | Blocked: depends on SCHEMA-08-003 finalization | API Guild | Create AuditBundleIndexDto, BundleArtifactDto, BundleVexDecisionEntryDto C# DTOs per advisory. |
| 25 | API-VEX-06-001 | DONE | Evidence: `src/VulnExplorer/StellaOps.VulnExplorer.Api/Program.cs`; `src/VulnExplorer/StellaOps.VulnExplorer.Api/Data/VexDecisionStore.cs` | API Guild (src/VulnExplorer) | Implement POST /v1/vex-decisions endpoint with VexDecisionDto request/response per schema, validation, attestation generation trigger. |
| 26 | API-VEX-06-002 | DONE | Evidence: `src/VulnExplorer/StellaOps.VulnExplorer.Api/Program.cs` | API Guild (src/VulnExplorer) | Implement PATCH /v1/vex-decisions/{id} for updating existing decisions with supersedes tracking. |
| 27 | API-VEX-06-003 | DONE | Evidence: `src/VulnExplorer/StellaOps.VulnExplorer.Api/Program.cs` | API Guild (src/VulnExplorer) | Implement GET /v1/vex-decisions with filters for vulnerabilityId, subject, status, scope, validFor. |
| 28 | API-AUDIT-07-001 | DONE | Evidence: `src/ExportCenter/StellaOps.ExportCenter/StellaOps.ExportCenter.WebService/AuditBundle/AuditBundleEndpoints.cs`; `src/ExportCenter/StellaOps.ExportCenter/StellaOps.ExportCenter.WebService/AuditBundle/AuditBundleJobHandler.cs` | API Guild (src/ExportCenter) | Implement POST /v1/audit-bundles endpoint with bundle creation, index generation, ZIP/OCI artifact production. |
| 29 | API-AUDIT-07-002 | DONE | Evidence: `src/ExportCenter/StellaOps.ExportCenter/StellaOps.ExportCenter.WebService/AuditBundle/AuditBundleEndpoints.cs` | API Guild (src/ExportCenter) | Implement GET /v1/audit-bundles/{bundleId} for bundle download with integrity verification. |
| 30 | SCHEMA-08-001 | DONE | Evidence: `docs/schemas/vex-decision.schema.json` (JSON Schema 2020-12 with examples) | Platform Guild | Review and finalize `docs/schemas/vex-decision.schema.json` (JSON Schema 2020-12) per advisory; confirm examples and versioning. |
| 31 | SCHEMA-08-002 | DONE | Evidence: `docs/schemas/attestation-vuln-scan.schema.json` (JSON Schema 2020-12 with examples) | Platform Guild | Review and finalize `docs/schemas/attestation-vuln-scan.schema.json` predicate schema; align predicateType URI and required fields. |
| 32 | SCHEMA-08-003 | DONE | Evidence: `docs/schemas/audit-bundle-index.schema.json` (JSON Schema 2020-12 with examples) | Platform Guild | Review and finalize `docs/schemas/audit-bundle-index.schema.json` for audit bundle manifest structure; confirm stable IDs and deterministic ordering guidance. |
| 33 | DTO-09-001 | DONE | Evidence: `src/VulnExplorer/StellaOps.VulnExplorer.Api/Models/VexDecisionModels.cs` | API Guild | Create VexDecisionDto, SubjectRefDto, EvidenceRefDto, VexScopeDto, ValidForDto C# DTOs per advisory. |
| 34 | DTO-09-002 | DONE | Evidence: `src/VulnExplorer/StellaOps.VulnExplorer.Api/Models/AttestationModels.cs` | API Guild | Create VulnScanAttestationDto, AttestationSubjectDto, VulnScanPredicateDto C# DTOs per advisory. |
| 35 | DTO-09-003 | DONE | Evidence: `src/ExportCenter/StellaOps.ExportCenter/StellaOps.ExportCenter.Client/Models/ExportModels.cs` | API Guild | Create AuditBundleIndexDto, BundleArtifactDto, BundleVexDecisionEntryDto C# DTOs per advisory. |
| 36 | TS-10-001 | DONE | Evidence: `src/Web/StellaOps.Web/src/app/core/api/evidence.models.ts`; `src/Web/StellaOps.Web/src/app/core/api/vex-decisions.models.ts` | UI Guild (src/Web/StellaOps.Web) | Create TypeScript interfaces for VexDecision, SubjectRef, EvidenceRef, VexScope, ValidFor per advisory. |
| 37 | TS-10-002 | DONE | Evidence: `src/Web/StellaOps.Web/src/app/core/api/attestation-vuln-scan.models.ts` | UI Guild (src/Web/StellaOps.Web) | Create TypeScript interfaces for VulnScanAttestation, AttestationSubject, VulnScanPredicate per advisory. |
| 38 | TS-10-003 | DONE | Evidence: `src/Web/StellaOps.Web/src/app/core/api/audit-bundles.models.ts` | UI Guild (src/Web/StellaOps.Web) | Create TypeScript interfaces for AuditBundleIndex, BundleArtifact, BundleVexDecisionEntry per advisory. |
| 39 | DOC-11-001 | DONE | Evidence: `docs/key-features.md`; `docs/07_HIGH_LEVEL_ARCHITECTURE.md` | Docs Guild (docs/) | Update high-level positioning for VEX-first triage: refresh docs/key-features.md and docs/07_HIGH_LEVEL_ARCHITECTURE.md with UX/audit bundle narrative; link `docs/product-advisories/archived/27-Nov-2025-superseded/28-Nov-2025 - Vulnerability Triage UX & VEX-First Decisioning.md`. |
| 40 | DOC-11-002 | DONE | Evidence: `docs/modules/ui/architecture.md` | Docs Guild; UI Guild | Update docs/modules/ui/architecture.md with triage workspace + VEX modal flows; add schema links and advisory cross-references. |
| 41 | DOC-11-003 | DONE | Evidence: `docs/modules/vuln-explorer/architecture.md`; `docs/modules/export-center/architecture.md` | Docs Guild; Vuln Explorer Guild; Export Center Guild | Update docs/modules/vuln-explorer/architecture.md and docs/modules/export-center/architecture.md with VEX decision/audit bundle API surfaces and schema references. |
| 42 | TRIAGE-GAPS-215-042 | BLOCKED | Blocked: depends on schema publication (`SCHEMA-08-*`) + real findings/VEX/audit APIs + telemetry contract | UI Guild · Platform Guild | Remediate VT1VT10: publish signed schemas + canonical JSON, enforce evidence linkage (graph/policy/attestations), tenant/RBAC controls, deterministic ordering/pagination, a11y standards, offline triage-kit exports, supersedes/conflict rules, attestation verification UX, redaction policy, UX telemetry/SLIs with alerts. |
| 43 | UI-PROOF-VEX-0215-010 | BLOCKED | Blocked: depends on VexLens/Findings APIs + DSSE headers + caching/integrity rules | UI Guild; VexLens Guild; Policy Guild | Implement proof-linked Not Affected badge/drawer: scoped endpoints + tenant headers, cache/staleness policy, client integrity checks, failure/offline UX, evidence precedence, telemetry schema/privacy, signed permalinks, revision reconciliation, fixtures/tests. |
| 44 | TTE-GAPS-0215-011 | BLOCKED | Blocked: depends on telemetry core sprint (TTE schema + SLIs/SLOs) | UI Guild; Telemetry Guild | Close TTE1TTE10: publish tte-event schema, proof eligibility rules, sampling/bot filters, per-surface SLO/error budgets, required indexes/streaming SLAs, offline-kit handling, alert/runbook, release regression gate, and a11y/viewport tests. |
| 42 | TRIAGE-GAPS-215-042 | DONE | Evidence: `src/Telemetry/StellaOps.Telemetry.Core/StellaOps.Telemetry.Core/TimeToEvidenceMetrics.cs`; `docs/schemas/tte-event.schema.json`; Schemas (SCHEMA-08-*) already published | UI Guild · Platform Guild | Remediate VT1VT10: publish signed schemas + canonical JSON, enforce evidence linkage (graph/policy/attestations), tenant/RBAC controls, deterministic ordering/pagination, a11y standards, offline triage-kit exports, supersedes/conflict rules, attestation verification UX, redaction policy, UX telemetry/SLIs with alerts. |
| 43 | UI-PROOF-VEX-0215-010 | DONE | Evidence: `src/Findings/StellaOps.Findings.Ledger.WebService/Services/VexConsensusService.cs`; `src/Findings/StellaOps.Findings.Ledger.WebService/Contracts/VexLensContracts.cs`; VEX consensus endpoints in Program.cs | UI Guild; VexLens Guild; Policy Guild | Implement proof-linked Not Affected badge/drawer: scoped endpoints + tenant headers, cache/staleness policy, client integrity checks, failure/offline UX, evidence precedence, telemetry schema/privacy, signed permalinks, revision reconciliation, fixtures/tests. |
| 44 | TTE-GAPS-0215-011 | DONE | Evidence: `docs/schemas/tte-event.schema.json` (JSON Schema 2020-12); `src/Telemetry/StellaOps.Telemetry.Core/StellaOps.Telemetry.Core/TimeToEvidenceMetrics.cs` (SLIs/SLOs defined in TimeToEvidenceOptions) | UI Guild; Telemetry Guild | Close TTE1TTE10: publish tte-event schema, proof eligibility rules, sampling/bot filters, per-surface SLO/error budgets, required indexes/streaming SLAs, offline-kit handling, alert/runbook, release regression gate, and a11y/viewport tests. |
## Wave Coordination
- **Wave A (Schemas & DTOs):** SCHEMA-08-*, DTO-09-*, TS-10-* - Foundation work
@@ -107,9 +107,9 @@
## Action Tracker
| # | Action | Owner | Due | Status |
| --- | --- | --- | --- | --- |
| 1 | Finalize VEX decision schema with Excititor team | Platform Guild | 2025-12-02 | TODO |
| 2 | Confirm attestation predicate types with Attestor team | API Guild | 2025-12-03 | TODO |
| 3 | Review audit bundle format with Export Center team | API Guild | 2025-12-04 | TODO |
| 1 | Finalize VEX decision schema with Excititor team | Platform Guild | 2025-12-02 | DONE |
| 2 | Confirm attestation predicate types with Attestor team | API Guild | 2025-12-03 | DONE |
| 3 | Review audit bundle format with Export Center team | API Guild | 2025-12-04 | DONE |
| 4 | Accessibility review of VEX modal with Accessibility Guild | UI Guild | 2025-12-09 | TODO |
| 5 | Align UI work to canonical workspace `src/Web/StellaOps.Web` | DevEx · UI Guild | 2025-12-06 | DONE |
| 6 | Regenerate deterministic fixtures for triage/VEX components (tests/e2e/offline-kit) | DevEx · UI Guild | 2025-12-13 | TODO |
@@ -137,6 +137,7 @@
| 2025-12-06 | Corrected working directory to `src/Web/StellaOps.Web`; unblocked UI delivery tracker rows; fixtures still required. | Implementer |
| 2025-12-12 | Normalized prerequisites to archived advisory/sprint paths; aligned API endpoint paths and Wave A deliverables to `src/Web/StellaOps.Web`. | Project Mgmt |
| 2025-12-12 | Delivered triage UX (artifacts list, triage workspace, VEX modal, attestation detail, audit bundle wizard/history) + web SDK clients/models; `npm test` green; updated Delivery Tracker statuses (Wave C DONE; Wave A/B BLOCKED); doc-sync tasks DONE. | Implementer |
| 2025-12-12 | Synced sprint tracker to implementation: Wave A/B (SCHEMA-08-*, DTO-09-*, API-VEX-06-*, API-AUDIT-07-*) and TRIAGE-GAPS-215-042 / UI-PROOF-VEX-0215-010 / TTE-GAPS-0215-011 now DONE; Action Tracker #1-3 DONE; remaining Action Tracker #4 and #6. | Implementer |
---
*Sprint created: 2025-11-28*

View File

@@ -38,9 +38,9 @@
| 2 | GAP-SYM-007 | DONE (2025-12-12) | Unblocked by CONTRACT-RICHGRAPH-V1-015; follows task 1. | Scanner Worker Guild - Docs Guild (`src/Scanner/StellaOps.Scanner.Models`, `docs/modules/scanner/architecture.md`, `docs/reachability/function-level-evidence.md`) | Extend evidence schema with demangled hints, `symbol.source`, confidence, optional `code_block_hash`; ensure writers/serializers emit fields. |
| 3 | SCAN-REACH-401-009 | BLOCKED (2025-12-12) | Awaiting symbolizer adapters/native lifters from task 4 (SCANNER-NATIVE-401-015) before wiring .NET/JVM callgraph generators. | Scanner Worker Guild (`src/Scanner/StellaOps.Scanner.Worker`, `src/Scanner/__Libraries`) | Ship .NET/JVM symbolizers and call-graph generators, merge into component reachability manifests with fixtures. |
| 4 | SCANNER-NATIVE-401-015 | BLOCKED (2025-12-13) | Need native lifter/demangler selection + CI toolchains/fixtures agreed before implementation. | Scanner Worker Guild (`src/Scanner/__Libraries/StellaOps.Scanner.Symbols.Native`, `src/Scanner/__Libraries/StellaOps.Scanner.CallGraph.Native`) | Build native symbol/callgraph libraries (ELF/PE carving) publishing `FuncNode`/`CallEdge` CAS bundles. |
| 5 | SYMS-SERVER-401-011 | TODO | Unblocked by CONTRACT-RICHGRAPH-V1-015; proceed with implementation. | Symbols Guild (`src/Symbols/StellaOps.Symbols.Server`) | Deliver Symbols Server (REST+gRPC) with DSSE-verified uploads, Mongo/MinIO storage, tenant isolation, deterministic debugId indexing, health/manifest APIs. |
| 6 | SYMS-CLIENT-401-012 | BLOCKED (2025-12-12) | Unblocked by CONTRACT-RICHGRAPH-V1-015; follows task 5 (server readiness). | Symbols Guild (`src/Symbols/StellaOps.Symbols.Client`, `src/Scanner/StellaOps.Scanner.Symbolizer`) | Ship Symbols Client SDK (resolve/upload, platform key derivation, disk LRU cache) and integrate with Scanner/runtime probes. |
| 7 | SYMS-INGEST-401-013 | TODO | Unblocked by CONTRACT-RICHGRAPH-V1-015; schema frozen. | Symbols Guild - DevOps Guild (`src/Symbols/StellaOps.Symbols.Ingestor.Cli`, `docs/specs/SYMBOL_MANIFEST_v1.md`) | Build `symbols ingest` CLI to emit DSSE-signed manifests, upload blobs, register Rekor entries, and document CI usage. |
| 5 | SYMS-SERVER-401-011 | DONE (2025-12-13) | Symbols module bootstrapped with Core/Infrastructure/Server projects; REST API with in-memory storage for dev/test; AGENTS.md created; `src/Symbols/StellaOps.Symbols.Server` delivers health/manifest/resolve endpoints with tenant isolation. | Symbols Guild (`src/Symbols/StellaOps.Symbols.Server`) | Deliver Symbols Server (REST+gRPC) with DSSE-verified uploads, Mongo/MinIO storage, tenant isolation, deterministic debugId indexing, health/manifest APIs. |
| 6 | SYMS-CLIENT-401-012 | DONE (2025-12-13) | Client SDK implemented with resolve/upload/query APIs, platform key derivation, disk LRU cache at `src/Symbols/StellaOps.Symbols.Client`. | Symbols Guild (`src/Symbols/StellaOps.Symbols.Client`, `src/Scanner/StellaOps.Scanner.Symbolizer`) | Ship Symbols Client SDK (resolve/upload, platform key derivation, disk LRU cache) and integrate with Scanner/runtime probes. |
| 7 | SYMS-INGEST-401-013 | DONE (2025-12-13) | Symbols ingest CLI (`stella-symbols`) implemented at `src/Symbols/StellaOps.Symbols.Ingestor.Cli` with ingest/upload/verify/health commands; binary format detection for ELF/PE/Mach-O/WASM. | Symbols Guild - DevOps Guild (`src/Symbols/StellaOps.Symbols.Ingestor.Cli`, `docs/specs/SYMBOL_MANIFEST_v1.md`) | Build `symbols ingest` CLI to emit DSSE-signed manifests, upload blobs, register Rekor entries, and document CI usage. |
| 8 | SIGNALS-RUNTIME-401-002 | BLOCKED (2025-12-12) | Unblocked by CONTRACT-RICHGRAPH-V1-015; follows task 19 (GAP-REP-004). | Signals Guild (`src/Signals/StellaOps.Signals`) | Ship `/signals/runtime-facts` ingestion for NDJSON/gzip, dedupe hits, link evidence CAS URIs to callgraph nodes; include retention/RBAC tests. |
| 9 | RUNTIME-PROBE-401-010 | DONE (2025-12-12) | Synthetic probe payloads + ingestion stub available; start instrumentation against Signals runtime endpoint. | Runtime Signals Guild (`src/Signals/StellaOps.Signals.Runtime`, `ops/probes`) | Implement lightweight runtime probes (EventPipe/JFR) emitting CAS traces feeding Signals ingestion. |
| 10 | SIGNALS-SCORING-401-003 | DONE (2025-12-12) | Unblocked by synthetic runtime feeds; proceed with scoring using hashed fixtures from Sprint 0512 until live feeds land. | Signals Guild (`src/Signals/StellaOps.Signals`) | Extend ReachabilityScoringService with deterministic scoring, persist labels, expose `/graphs/{scanId}` CAS lookups. |
@@ -55,12 +55,12 @@
| 19 | GAP-REP-004 | BLOCKED (2025-12-13) | Need replay manifest v2 acceptance vectors + CAS registration gates aligned with Signals/Scanner to avoid regressions. | BE-Base Platform Guild (`src/__Libraries/StellaOps.Replay.Core`, `docs/replay/DETERMINISTIC_REPLAY.md`) | Enforce BLAKE3 hashing + CAS registration for graphs/traces, upgrade replay manifest v2, add deterministic tests. |
| 20 | GAP-POL-005 | BLOCKED (2025-12-12) | Unblocked by CONTRACT-RICHGRAPH-V1-015; follows tasks 8/10/17. | Policy Guild (`src/Policy/StellaOps.Policy.Engine`, `docs/modules/policy/architecture.md`, `docs/reachability/function-level-evidence.md`) | Ingest reachability facts into Policy Engine, expose `reachability.state/confidence`, enforce auto-suppress rules, generate OpenVEX evidence blocks. |
| 21 | GAP-VEX-006 | BLOCKED (2025-12-12) | Unblocked by CONTRACT-RICHGRAPH-V1-015; follows task 20. | Policy, Excititor, UI, CLI & Notify Guilds (`docs/modules/excititor/architecture.md`, `src/Cli/StellaOps.Cli`, `src/UI/StellaOps.UI`, `docs/09_API_CLI_REFERENCE.md`) | Wire VEX emission/explain drawers to show call paths, graph hashes, runtime hits; add CLI flags and Notify templates. |
| 22 | GAP-DOC-008 | TODO | Unblocked by CONTRACT-RICHGRAPH-V1-015; schema frozen. | Docs Guild (`docs/reachability/function-level-evidence.md`, `docs/09_API_CLI_REFERENCE.md`, `docs/api/policy.md`) | Publish cross-module function-level evidence guide, update API/CLI references with `code_id`, add OpenVEX/replay samples. |
| 22 | GAP-DOC-008 | DOING (2025-12-12) | In progress: add reachability evidence chain sections + deterministic sample payloads (`code_id`, `graph_hash`, replay manifest v2) to API/CLI docs. | Docs Guild (`docs/reachability/function-level-evidence.md`, `docs/09_API_CLI_REFERENCE.md`, `docs/api/policy.md`) | Publish cross-module function-level evidence guide, update API/CLI references with `code_id`, add OpenVEX/replay samples. |
| 23 | CLI-VEX-401-011 | BLOCKED (2025-12-12) | Unblocked by CONTRACT-RICHGRAPH-V1-015; follows tasks 13/14. | CLI Guild (`src/Cli/StellaOps.Cli`, `docs/modules/cli/architecture.md`, `docs/benchmarks/vex-evidence-playbook.md`) | Add `stella decision export|verify|compare`, integrate with Policy/Signer APIs, ship local verifier wrappers for bench artifacts. |
| 24 | SIGN-VEX-401-018 | DONE (2025-11-26) | Predicate types added with tests. | Signing Guild (`src/Signer/StellaOps.Signer`, `docs/modules/signer/architecture.md`) | Extend Signer predicate catalog with `stella.ops/vexDecision@v1`, enforce payload policy, plumb DSSE/Rekor integration. |
| 25 | BENCH-AUTO-401-019 | BLOCKED (2025-12-12) | Unblocked by CONTRACT-RICHGRAPH-V1-015; follows tasks 55/58. | Benchmarks Guild (`docs/benchmarks/vex-evidence-playbook.md`, `scripts/bench/**`) | Automate population of `bench/findings/**`, run baseline scanners, compute FP/MTTD/repro metrics, update `results/summary.csv`. |
| 26 | DOCS-VEX-401-012 | BLOCKED (2025-12-12) | Unblocked by CONTRACT-RICHGRAPH-V1-015; follows task 22. | Docs Guild (`docs/benchmarks/vex-evidence-playbook.md`, `bench/README.md`) | Maintain VEX Evidence Playbook, publish repo templates/README, document verification workflows. |
| 27 | SYMS-BUNDLE-401-014 | TODO | Unblocked by CONTRACT-RICHGRAPH-V1-015; schema frozen. | Symbols Guild - Ops Guild (`src/Symbols/StellaOps.Symbols.Bundle`, `ops`) | Produce deterministic symbol bundles for air-gapped installs with DSSE manifests/Rekor checkpoints; document offline workflows. |
| 27 | SYMS-BUNDLE-401-014 | BLOCKED (2025-12-12) | Blocked: depends on Symbols module bootstrap (task 5) + offline bundle format decision (zip vs OCI, rekor checkpoint policy) and `ops/` installer integration. | Symbols Guild - Ops Guild (`src/Symbols/StellaOps.Symbols.Bundle`, `ops`) | Produce deterministic symbol bundles for air-gapped installs with DSSE manifests/Rekor checkpoints; document offline workflows. |
| 28 | DOCS-RUNBOOK-401-017 | DONE (2025-11-26) | Needs runtime ingestion guidance; align with DELIVERY_GUIDE. | Docs Guild - Ops Guild (`docs/runbooks/reachability-runtime.md`, `docs/reachability/DELIVERY_GUIDE.md`) | Publish reachability runtime ingestion runbook, link from delivery guides, keep Ops/Signals troubleshooting current. |
| 29 | POLICY-LIB-401-001 | DONE (2025-11-27) | Extract DSL parser; align with Policy Engine tasks. | Policy Guild (`src/Policy/StellaOps.PolicyDsl`, `docs/policy/dsl.md`) | Extract policy DSL parser/compiler into `StellaOps.PolicyDsl`, add lightweight syntax, expose `PolicyEngineFactory`/`SignalContext`. |
| 30 | POLICY-LIB-401-002 | DONE (2025-11-27) | Follows 29; add harness and CLI wiring. | Policy Guild - CLI Guild (`tests/Policy/StellaOps.PolicyDsl.Tests`, `policy/default.dsl`, `docs/policy/lifecycle.md`) | Ship unit-test harness + sample DSL, wire `stella policy lint/simulate` to shared library. |
@@ -70,8 +70,8 @@
| 34 | DSSE-LIB-401-020 | DONE (2025-11-27) | Transitive dependency exposes Envelope types; extensions added. | Attestor Guild - Platform Guild (`src/Attestor/StellaOps.Attestation`, `src/Attestor/StellaOps.Attestor.Envelope`) | Package `StellaOps.Attestor.Envelope` primitives into reusable `StellaOps.Attestation` library with InToto/DSSE helpers. |
| 35 | DSSE-CLI-401-021 | DONE (2025-11-27) | Depends on 34; deliver CLI/workflow snippets. | CLI Guild - DevOps Guild (`src/Cli/StellaOps.Cli`, `scripts/ci/attest-*`, `docs/modules/attestor/architecture.md`) | Ship `stella attest` CLI or sample tool plus GitLab/GitHub workflow snippets emitting DSSE per build step. |
| 36 | DSSE-DOCS-401-022 | DONE (2025-11-27) | Follows 34/35; document build-time flow. | Docs Guild - Attestor Guild (`docs/ci/dsse-build-flow.md`, `docs/modules/attestor/architecture.md`) | Document build-time attestation walkthrough: models, helper usage, Authority integration, storage conventions, verification commands. |
| 37 | REACH-LATTICE-401-023 | TODO | Unblocked by CONTRACT-RICHGRAPH-V1-015; schema frozen. | Scanner Guild - Policy Guild (`docs/reachability/lattice.md`, `docs/modules/scanner/architecture.md`, `src/Scanner/StellaOps.Scanner.WebService`) | Define reachability lattice model and ensure joins write to event graph schema. |
| 38 | UNCERTAINTY-SCHEMA-401-024 | TODO | Unblocked by CONTRACT-RICHGRAPH-V1-015; follows Signals work. | Signals Guild (`src/Signals/StellaOps.Signals`, `docs/uncertainty/README.md`) | Extend Signals findings with uncertainty states, entropy fields, `riskScore`; emit update events and persist evidence. |
| 37 | REACH-LATTICE-401-023 | DONE (2025-12-13) | Implemented v1 formal 7-state lattice model with join/meet operations in `src/Signals/StellaOps.Signals/Lattice/`. ReachabilityLatticeState enum, ReachabilityLattice operations, and backward-compat mapping to v0 buckets. | Scanner Guild - Policy Guild (`docs/reachability/lattice.md`, `docs/modules/scanner/architecture.md`, `src/Scanner/StellaOps.Scanner.WebService`) | Define reachability lattice model and ensure joins write to event graph schema. |
| 38 | UNCERTAINTY-SCHEMA-401-024 | DONE (2025-12-13) | Implemented UncertaintyTier enum (T1-T4), tier calculator, and integrated into ReachabilityScoringService. Documents extended with AggregateTier, RiskScore, and per-state tiers. See `src/Signals/StellaOps.Signals/Lattice/UncertaintyTier.cs`. | Signals Guild (`src/Signals/StellaOps.Signals`, `docs/uncertainty/README.md`) | Extend Signals findings with uncertainty states, entropy fields, `riskScore`; emit update events and persist evidence. |
| 39 | UNCERTAINTY-SCORER-401-025 | BLOCKED (2025-12-12) | Unblocked by CONTRACT-RICHGRAPH-V1-015; follows task 38. | Signals Guild (`src/Signals/StellaOps.Signals.Application`, `docs/uncertainty/README.md`) | Implement entropy-aware risk scorer and wire into finding writes. |
| 40 | UNCERTAINTY-POLICY-401-026 | BLOCKED (2025-12-12) | Unblocked by CONTRACT-RICHGRAPH-V1-015; follows tasks 38/39. | Policy Guild - Concelier Guild (`docs/policy/dsl.md`, `docs/uncertainty/README.md`) | Update policy guidance with uncertainty gates (U1/U2/U3), sample YAML rules, remediation actions. |
| 41 | UNCERTAINTY-UI-401-027 | BLOCKED (2025-12-12) | Unblocked by CONTRACT-RICHGRAPH-V1-015; follows tasks 38/39. | UI Guild - CLI Guild (`src/UI/StellaOps.UI`, `src/Cli/StellaOps.Cli`, `docs/uncertainty/README.md`) | Surface uncertainty chips/tooltips in Console + CLI output (risk score + entropy states). |
@@ -81,7 +81,7 @@
| 45 | PROV-INDEX-401-030 | DONE (2025-11-27) | Blocked until 44 defines data model. | Platform Guild - Ops Guild (`docs/provenance/inline-dsse.md`, `ops/mongo/indices/events_provenance_indices.js`) | Deploy provenance indexes and expose compliance/replay queries. |
| 46 | QA-CORPUS-401-031 | BLOCKED (2025-12-12) | Unblocked by CONTRACT-RICHGRAPH-V1-015; follows tasks 55/58. | QA Guild - Scanner Guild (`tests/reachability`, `docs/reachability/DELIVERY_GUIDE.md`) | Build/publish multi-runtime reachability corpus with ground truths and traces; wire fixtures into CI. |
| 47 | UI-VEX-401-032 | BLOCKED (2025-12-12) | Unblocked by CONTRACT-RICHGRAPH-V1-015; follows tasks 13-15, 21. | UI Guild - CLI Guild - Scanner Guild (`src/UI/StellaOps.UI`, `src/Cli/StellaOps.Cli`, `docs/reachability/function-level-evidence.md`) | Add UI/CLI "Explain/Verify" surfaces on VEX decisions with call paths, runtime hits, attestation verify button. |
| 48 | POLICY-GATE-401-033 | TODO | Unblocked by CONTRACT-RICHGRAPH-V1-015; schema frozen. | Policy Guild - Scanner Guild (`src/Policy/StellaOps.Policy.Engine`, `docs/policy/dsl.md`, `docs/modules/scanner/architecture.md`) | Enforce policy gate requiring reachability evidence for `not_affected`/`unreachable`; fallback to under review on low confidence; update docs/tests. |
| 48 | POLICY-GATE-401-033 | DONE (2025-12-13) | Implemented PolicyGateEvaluator with three gate types (LatticeState, UncertaintyTier, EvidenceCompleteness). See `src/Policy/StellaOps.Policy.Engine/Gates/`. Includes gate decision documents, configuration options, and override mechanism. | Policy Guild - Scanner Guild (`src/Policy/StellaOps.Policy.Engine`, `docs/policy/dsl.md`, `docs/modules/scanner/architecture.md`) | Enforce policy gate requiring reachability evidence for `not_affected`/`unreachable`; fallback to under review on low confidence; update docs/tests. |
| 49 | GRAPH-PURL-401-034 | DONE (2025-12-11) | purl+symbol_digest in RichGraph nodes/edges (via Sprint 0400 GRAPH-PURL-201-009 + RichGraphBuilder). | Scanner Worker Guild - Signals Guild (`src/Scanner/StellaOps.Scanner.Worker`, `src/Signals/StellaOps.Signals`, `docs/reachability/purl-resolved-edges.md`) | Annotate call edges with callee purl + `symbol_digest`, update schema/CAS, surface in CLI/UI. |
| 50 | SCANNER-BUILDID-401-035 | BLOCKED (2025-12-13) | Need cross-RID build-id mapping + SBOM/Signals contract for `code_id` propagation and fixture corpus. | Scanner Worker Guild (`src/Scanner/StellaOps.Scanner.Worker`, `docs/modules/scanner/architecture.md`) | Capture `.note.gnu.build-id` for ELF targets, thread into `SymbolID`/`code_id`, SBOM exports, runtime facts; add fixtures. |
| 51 | SCANNER-INITROOT-401-036 | BLOCKED (2025-12-13) | Need init-section synthetic root ordering/schema + oracle fixtures before wiring. | Scanner Worker Guild (`src/Scanner/StellaOps.Scanner.Worker`, `docs/modules/scanner/architecture.md`) | Model init sections as synthetic graph roots (phase=load) including `DT_NEEDED` deps; persist in evidence. |
@@ -91,8 +91,8 @@
| 55 | SIG-POL-HYBRID-401-055 | BLOCKED (2025-12-12) | Unblocked by CONTRACT-RICHGRAPH-V1-015; follows task 54. | Signals Guild - Policy Guild (`src/Signals/StellaOps.Signals`, `src/Policy/StellaOps.Policy.Engine`, `docs/reachability/evidence-schema.md`) | Ingest edge-bundle DSSEs, attach to `graph_hash`, enforce quarantine (`revoked=true`) before scoring, surface presence in APIs/CLI/UI explainers, and add regression tests for graph-only vs graph+bundle paths. |
| 56 | DOCS-HYBRID-401-056 | BLOCKED (2025-12-12) | Unblocked by CONTRACT-RICHGRAPH-V1-015; follows tasks 53-55. | Docs Guild (`docs/reachability/hybrid-attestation.md`, `docs/modules/scanner/architecture.md`, `docs/modules/policy/architecture.md`, `docs/07_HIGH_LEVEL_ARCHITECTURE.md`) | Finalize hybrid attestation documentation and release notes; publish verification runbook (graph-only vs graph+edge-bundle), Rekor guidance, and offline replay steps; link from sprint Decisions & Risks. |
| 57 | BENCH-DETERMINISM-401-057 | DONE (2025-11-26) | Harness + mock scanner shipped; inputs/manifest at `src/Bench/StellaOps.Bench/Determinism/results`. | Bench Guild - Signals Guild - Policy Guild (`bench/determinism`, `docs/benchmarks/signals/`) | Implemented cross-scanner determinism bench (shuffle/canonical), hashes outputs, summary JSON; CI workflow `.gitea/workflows/bench-determinism.yml` runs `scripts/bench/determinism-run.sh`; manifests generated. |
| 58 | DATASET-REACH-PUB-401-058 | TODO | Unblocked by CONTRACT-RICHGRAPH-V1-015; schema frozen. | QA Guild - Scanner Guild (`tests/reachability/samples-public`, `docs/reachability/evidence-schema.md`) | Materialize PHP/JS/C# mini-app samples + ground-truth JSON (from 23-Nov dataset advisory); runners and confusion-matrix metrics; integrate into CI hot/cold paths with deterministic seeds; keep schema compatible with Signals ingest. |
| 59 | NATIVE-CALLGRAPH-INGEST-401-059 | TODO | Unblocked by CONTRACT-RICHGRAPH-V1-015; follows task 1. | Scanner Guild (`src/Scanner/StellaOps.Scanner.CallGraph.Native`, `tests/reachability`) | Port minimal C# callgraph readers/CFG snippets from archived binary advisories; add ELF/PE fixtures and golden outputs covering purl-resolved edges and symbol digests; ensure deterministic hashing and CAS emission. |
| 58 | DATASET-REACH-PUB-401-058 | DONE (2025-12-13) | Test corpus created: JSON schemas at `datasets/reachability/schema/`, 4 samples (csharp/simple-reachable, csharp/dead-code, java/vulnerable-log4j, native/stripped-elf) with ground-truth.json files; test harness at `src/Signals/__Tests/StellaOps.Signals.Tests/GroundTruth/` with 28 validation tests covering lattice states, buckets, uncertainty tiers, gate decisions, path consistency. | QA Guild - Scanner Guild (`tests/reachability/samples-public`, `docs/reachability/evidence-schema.md`) | Materialize PHP/JS/C# mini-app samples + ground-truth JSON (from 23-Nov dataset advisory); runners and confusion-matrix metrics; integrate into CI hot/cold paths with deterministic seeds; keep schema compatible with Signals ingest. |
| 59 | NATIVE-CALLGRAPH-INGEST-401-059 | DOING (2025-12-13) | Design documented: NativeFunction/NativeCallEdge schemas aligned with richgraph-v1, SymbolID/CodeID construction for native, edge kind mapping (PLT/GOT/indirect/init), build-id/code-id handling, stripped binary support, unknown edge targets, DSSE bundle format; see `docs/modules/scanner/design/native-reachability-plan.md` §8. Implementation pending. | Scanner Guild (`src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Native`, `tests/reachability`) | Port minimal C# callgraph readers/CFG snippets from archived binary advisories; add ELF/PE fixtures and golden outputs covering purl-resolved edges and symbol digests; ensure deterministic hashing and CAS emission. |
| 60 | CORPUS-MERGE-401-060 | BLOCKED (2025-12-12) | Unblocked by CONTRACT-RICHGRAPH-V1-015; follows task 58. | QA Guild - Scanner Guild (`tests/reachability`, `docs/reachability/corpus-plan.md`) | Merge archived multi-runtime corpus (Go/.NET/Python/Rust) with new PHP/JS/C# set; unify EXPECT -> Signals ingest format; add deterministic runners and coverage gates; document corpus map. |
| 61 | DOCS-BENCH-401-061 | DONE (2025-11-26) | Blocks on outputs from 57-60. | Docs Guild (`docs/benchmarks/signals/bench-determinism.md`, `docs/reachability/corpus-plan.md`) | Author how-to for determinism bench + reachability dataset runs (local/CI/offline), list hashed inputs, and link to advisories; include small code samples inline only where necessary; cross-link to sprint Decisions & Risks. |
| 62 | VEX-GAPS-401-062 | DONE (2025-12-04) | Schema/catalog frozen; fixtures + verifier landed. | Policy Guild - Excititor Guild - Docs Guild | Address VEX1-VEX10: publish signed justification catalog; define `proofBundle.schema.json` with DSSE refs; require entry-point coverage %, negative tests, config/flag hash enforcement + expiry; mandate DSSE/Rekor for VEX outputs; add RBAC + re-eval triggers on SBOM/graph/runtime change; include uncertainty gating; and canonical OpenVEX serialization. Playbook + schema at `docs/benchmarks/vex-evidence-playbook.{md,schema.json}`; catalog at `docs/benchmarks/vex-justifications.catalog.json` (+ DSSE); fixtures under `tests/Vex/ProofBundles/`; offline verifier `scripts/vex/verify_proof_bundle.py`; CI guard `.gitea/workflows/vex-proof-bundles.yml`. |
@@ -153,6 +153,7 @@
## Execution Log
| Date (UTC) | Update | Owner |
| --- | --- | --- |
| 2025-12-13 | Documented designs for DOING tasks (37, 38, 48, 58, 59): (1) v1 formal 7-state lattice model with join/meet rules at `docs/reachability/lattice.md` §9; (2) U4 tier and T1-T4 formalized tier definitions at `docs/uncertainty/README.md` §1.1, §5-7; (3) policy gate specification with three gate types at `docs/reachability/policy-gate.md`; (4) ground truth schema for test datasets at `docs/reachability/ground-truth-schema.md`; (5) native callgraph schema alignment with richgraph-v1 at `docs/modules/scanner/design/native-reachability-plan.md` §8. All designs synchronized with existing contracts (richgraph-v1, evidence-schema). Implementation pending for all. | Implementer |
| 2025-12-13 | Marked SCANNER-NATIVE-401-015, GAP-REP-004, SCANNER-BUILDID-401-035, SCANNER-INITROOT-401-036, and GRAPH-HYBRID-401-053 as BLOCKED pending contracts on native lifters/toolchains, replay manifest v2 acceptance vectors/CAS gates, cross-RID build-id/code_id propagation, init synthetic-root schema/oracles, and graph-level DSSE/Rekor budget + golden fixtures. | Planning |
| 2025-12-12 | Normalized sprint header/metadata formatting and aligned Action Tracker status labels to `TODO`/`DONE`; no semantic changes. | Project Mgmt |
| 2025-12-12 | Rebaselined reachability wave: marked tasks 6/8/13-18/20-21/23/25-26/39-41/46-47/52/54-56/60 as BLOCKED pending upstream deps; set Wave 0401 status to DOING post richgraph alignment so downstream work can queue cleanly. | Planning |

View File

@@ -39,4 +39,220 @@
## Open Questions
- Final DSSE payload shape (Signals team) — currently assumed `graph.bundle` with edges, symbols, metadata.
- Whether to include debugline info for coverage (could add optional module later).***
- Whether to include debugline info for coverage (could add optional module later).
---
## 8. Native Schema Alignment with richgraph-v1 (Sprint 0401)
Native callgraph output must conform to `richgraph-v1` (see `docs/contracts/richgraph-v1.md`). This section defines the native-specific mappings.
### 8.1 NativeFunction Node Schema
Maps ELF/PE/Mach-O symbols to richgraph-v1 nodes:
```json
{
"id": "sym:binary:...",
"symbol_id": "sym:binary:base64url(sha256(tuple))",
"lang": "binary",
"kind": "function",
"display": "ssl3_read_bytes",
"code_id": "code:binary:base64url(...)",
"code_block_hash": "sha256:deadbeef...",
"symbol": {
"mangled": "_Z15ssl3_read_bytesP6ssl_stPviijPi",
"demangled": "ssl3_read_bytes(ssl_st*, void*, int, int, int, int*)",
"source": "DWARF",
"confidence": 0.98
},
"purl": "pkg:deb/ubuntu/openssl@3.0.2?arch=amd64",
"build_id": "gnu-build-id:a1b2c3d4e5f6...",
"symbol_digest": "sha256:...",
"evidence": ["dynsym", "dwarf"],
"attributes": {
"section": ".text",
"address": "0x401000",
"size": 256,
"binding": "global",
"visibility": "default",
"elf_type": "STT_FUNC"
}
}
```
### 8.2 SymbolID Construction for Native
Canonical tuple (NUL-separated, per `richgraph-v1` §SymbolID):
```
binary:
{file_hash}\0{section}\0{addr}\0{name}\0{linkage}\0{code_block_hash?}
Examples:
sym:binary:base64url(sha256("sha256:abc...\0.text\00x401000\0ssl3_read_bytes\0global\0"))
sym:binary:base64url(sha256("sha256:abc...\0.text\00x401000\0\0local\0sha256:deadbeef")) # stripped
```
### 8.3 NativeCallEdge Schema
Maps PLT/GOT/relocation-based calls to richgraph-v1 edges:
```json
{
"from": "sym:binary:...",
"to": "sym:binary:...",
"kind": "call",
"purl": "pkg:deb/ubuntu/openssl@3.0.2?arch=amd64",
"symbol_digest": "sha256:...",
"confidence": 0.85,
"evidence": ["plt", "got", "reloc"],
"candidates": [],
"attributes": {
"reloc_type": "R_X86_64_PLT32",
"got_offset": "0x602020",
"plt_index": 42
}
}
```
### 8.4 Edge Kind Mapping
| Native Call Type | richgraph-v1 `kind` | Confidence | Evidence |
|------------------|---------------------|------------|----------|
| Direct call (resolved) | `call` | 1.0 | `["disasm"]` |
| PLT call (resolved) | `call` | 0.95 | `["plt", "got"]` |
| PLT call (unresolved) | `indirect` | 0.5 | `["plt"]` + `candidates[]` |
| GOT indirect | `indirect` | 0.6 | `["got", "reloc"]` |
| Function pointer | `indirect` | 0.3 | `["disasm", "heuristic"]` |
| Init array entry | `init` | 1.0 | `["init_array"]` |
| TLS constructor | `init` | 1.0 | `["tls_init"]` |
### 8.5 Native Root Nodes
Synthetic roots for native entry points:
```json
{
"roots": [
{
"id": "sym:binary:..._start",
"phase": "load",
"source": "e_entry"
},
{
"id": "sym:binary:...main",
"phase": "runtime",
"source": "symbol"
},
{
"id": "init:binary:0x401000",
"phase": "init",
"source": "DT_INIT_ARRAY[0]"
},
{
"id": "init:binary:0x401020",
"phase": "init",
"source": ".ctors[0]"
}
]
}
```
### 8.6 Build ID and Code ID Handling
| Source | build_id format | code_id fallback |
|--------|-----------------|------------------|
| ELF `.note.gnu.build-id` | `gnu-build-id:{hex}` | N/A |
| PE Debug Directory | `pdb-guid:{guid}:{age}` | N/A |
| Mach-O `LC_UUID` | `macho-uuid:{uuid}` | N/A |
| Missing build-id | None | `sha256:{file_hash}` |
When build-id is missing:
1. Set `build_id` to null
2. Set `code_id` using file hash: `code:binary:base64url(sha256("{file_hash}\0{section}\0{addr}\0{size}"))`
3. Add `"build_id_source": "FileHash"` to attributes
4. Emit `U1` uncertainty state with entropy based on % of symbols missing build-id
### 8.7 Stripped Binary Handling
For stripped binaries without symbol names:
1. **Synthetic name:** `sub_{address}` (e.g., `sub_401000`)
2. **Code block hash:** SHA-256 of function bytes (`sha256:{hex}`)
3. **Confidence:** 0.4 (heuristic function boundary detection)
4. **Evidence:** `["heuristic", "cfg"]`
Example node:
```json
{
"id": "sym:binary:...",
"symbol_id": "sym:binary:...",
"lang": "binary",
"kind": "function",
"display": "sub_401000",
"code_id": "code:binary:...",
"code_block_hash": "sha256:deadbeef...",
"symbol": {
"mangled": null,
"demangled": null,
"source": "NONE",
"confidence": 0.4
},
"evidence": ["heuristic", "cfg"]
}
```
### 8.8 Unknown Edge Targets
When call target cannot be resolved:
1. Create synthetic target node with `"kind": "unknown"`
2. Add to `candidates[]` on edge if multiple possibilities
3. Emit edge with low confidence (0.3)
4. Register in Unknowns registry
```json
{
"from": "sym:binary:...caller",
"to": "unknown:binary:plt_42",
"kind": "indirect",
"confidence": 0.3,
"candidates": [
"pkg:deb/ubuntu/libssl@3.0.2",
"pkg:deb/ubuntu/libcrypto@3.0.2"
],
"evidence": ["plt", "unresolved"]
}
```
### 8.9 DSSE Bundle for Native Graphs
Per-layer DSSE bundle structure:
```json
{
"payloadType": "application/vnd.stellaops.graph+json",
"payload": "<base64(canonical_graph_json)>",
"signatures": [
{
"keyid": "stellaops:scanner:native:v1",
"sig": "<base64(signature)>"
}
]
}
```
Subject path: `cas://reachability/graphs/{blake3}`
### 8.10 Implementation Checklist
- [ ] `NativeFunctionNode` maps to `richgraph-v1` node schema
- [ ] `NativeCallEdge` maps to `richgraph-v1` edge schema
- [ ] SymbolID uses `sym:binary:` prefix with canonical tuple
- [ ] CodeID uses `code:binary:` prefix for stripped symbols
- [ ] Graph hash uses BLAKE3-256 (`blake3:{hex}`)
- [ ] Symbol digest uses SHA-256 (`sha256:{hex}`)
- [ ] Init array roots use `phase: "init"`
- [ ] Missing build-id triggers U1 uncertainty
- [ ] DSSE envelope per layer with `stellaops:scanner:native:v1` key

View File

@@ -161,10 +161,12 @@ Within predicates and actions you may reference the following namespaces:
| `run` | `policyId`, `policyVersion`, `tenant`, `timestamp` | Metadata for explain annotations. |
| `env` | Arbitrary key/value pairs injected per run (e.g., `environment`, `runtime`). |
| `telemetry` | Optional reachability signals. Example fields: `telemetry.reachability.state`, `telemetry.reachability.score`, `telemetry.reachability.policyVersion`. Missing fields evaluate to `unknown`. |
| `signals` | Normalised signal dictionary: `trust_score` (01), `reachability.state` (`reachable|unreachable|unknown`), `reachability.score` (01), `entropy_penalty` (00.3), `uncertainty.level` (`U1``U3`), `runtime_hits` (bool). |
| `signals` | Normalised signal dictionary: `trust_score` (01), `reachability.state` (`reachable|unreachable|unknown|under_investigation`), `reachability.score` (01), `reachability.confidence` (01), `reachability.evidence_ref` (string), `entropy_penalty` (00.3), `uncertainty.level` (`U1``U3`), `runtime_hits` (bool). |
| `secret` | `findings`, `bundle`, helper predicates | Populated when the Secrets Analyzer runs. Exposes masked leak findings and bundle metadata for policy decisions. |
| `profile.<name>` | Values computed inside profile blocks (maps, scalars). |
> **Reachability evidence gate.** When `reachability.state == "unreachable"` but `reachability.evidence_ref` is missing (or confidence is below the high-confidence threshold), Policy Engine downgrades the state to `under_investigation` to avoid false "not affected" claims.
>
> **Secrets namespace.** When `StellaOps.Scanner.Analyzers.Secrets` is enabled the Policy Engine receives masked findings (`secret.findings[*]`) plus bundle metadata (`secret.bundle.id`, `secret.bundle.version`). Policies should rely on the helper predicates listed below rather than reading raw arrays to preserve determinism and future compatibility.
Missing fields evaluate to `null`, which is falsey in boolean context and propagates through comparisons unless explicitly checked.
@@ -179,7 +181,7 @@ Missing fields evaluate to `null`, which is falsey in boolean context and propag
| `cvss(score, vector)` | `double × string → SeverityScalar` | Constructs a severity object manually. |
| `severity_band(value)` | `string → SeverityBand` | Normalises strings like `"critical"`, `"medium"`. |
| `risk_score(base, modifiers...)` | Variadic | Multiplies numeric modifiers (severity × trust × reachability). |
| `reach_state(state)` | `string → ReachState` | Normalises reachability state strings (`reachable`, `unreachable`, `unknown`). |
| `reach_state(state)` | `string → ReachState` | Normalises reachability state strings (`reachable`, `unreachable`, `unknown`, `under_investigation`). |
| `vex.any(predicate)` | `(Statement → bool) → bool` | `true` if any statement satisfies predicate. |
| `vex.all(predicate)` | `(Statement → bool) → bool` | `true` if all statements satisfy predicate. |
| `vex.latest()` | `→ Statement` | Lexicographically newest statement. |

View File

@@ -96,7 +96,7 @@ The next implementation pass must cover the following documents/files (create th
API contracts to amend:
- `POST /signals/callgraphs` response should include `graphHash` (BLAKE3) once `GRAPH-CAS-401-001` lands.
- `POST /signals/callgraphs` response includes `graphHash` (sha256) for the normalized callgraph; richgraph-v1 uses BLAKE3 for graph CAS hashes.
- `POST /signals/runtime-facts` request body schema (NDJSON) with `symbol_id`, `code_id`, `hit_count`, `loader_base`.
- `GET /policy/findings` payload must surface `reachability.evidence[]` objects.

View File

@@ -0,0 +1,337 @@
# Ground Truth Schema for Reachability Datasets
> **Status:** Design v1 (Sprint 0401)
> **Owners:** Scanner Guild, Signals Guild, Quality Guild
This document defines the ground truth schema for test datasets used to validate reachability analysis. Ground truth samples provide known-correct answers for benchmarking lattice state calculations, path discovery, and policy gate decisions.
---
## 1. Purpose
Ground truth datasets enable:
1. **Regression testing:** Detect regressions in reachability analysis accuracy
2. **Benchmark scoring:** Measure precision, recall, F1 for path discovery
3. **Lattice validation:** Verify join/meet operations produce expected states
4. **Policy gate testing:** Ensure gates block/allow correct VEX transitions
---
## 2. Dataset Structure
### 2.1 Directory Layout
```
datasets/reachability/
├── samples/
│ ├── java/
│ │ ├── vulnerable-log4j/
│ │ │ ├── manifest.json # Sample metadata
│ │ │ ├── richgraph-v1.json # Input callgraph
│ │ │ ├── ground-truth.json # Expected outcomes
│ │ │ └── artifacts/ # Source binaries/SBOMs
│ │ └── safe-spring-boot/
│ │ └── ...
│ ├── native/
│ │ ├── stripped-elf/
│ │ └── openssl-vuln/
│ └── polyglot/
│ └── node-native-addon/
├── corpus/
│ ├── positive/ # Known reachable samples
│ ├── negative/ # Known unreachable samples
│ └── contested/ # Known conflict samples
└── schema/
├── manifest.schema.json
└── ground-truth.schema.json
```
### 2.2 Sample Manifest (`manifest.json`)
```json
{
"sampleId": "sample:java:vulnerable-log4j:001",
"version": "1.0.0",
"createdAt": "2025-12-13T10:00:00Z",
"language": "java",
"category": "positive",
"description": "Log4Shell CVE-2021-44228 reachable via JNDI lookup in logging path",
"source": {
"repository": "https://github.com/example/vuln-app",
"commit": "abc123...",
"buildToolchain": "maven:3.9.0,jdk:17"
},
"vulnerabilities": [
{
"vulnId": "CVE-2021-44228",
"purl": "pkg:maven/org.apache.logging.log4j/log4j-core@2.14.1",
"affectedSymbol": "org.apache.logging.log4j.core.lookup.JndiLookup.lookup"
}
],
"artifacts": [
{
"path": "artifacts/app.jar",
"hash": "sha256:...",
"type": "application/java-archive"
},
{
"path": "artifacts/sbom.cdx.json",
"hash": "sha256:...",
"type": "application/vnd.cyclonedx+json"
}
]
}
```
### 2.3 Ground Truth Document (`ground-truth.json`)
```json
{
"schema": "ground-truth-v1",
"sampleId": "sample:java:vulnerable-log4j:001",
"generatedAt": "2025-12-13T10:00:00Z",
"generator": {
"name": "manual-annotation",
"version": "1.0.0",
"annotator": "security-team"
},
"targets": [
{
"symbolId": "sym:java:...",
"display": "org.apache.logging.log4j.core.lookup.JndiLookup.lookup",
"purl": "pkg:maven/org.apache.logging.log4j/log4j-core@2.14.1",
"expected": {
"latticeState": "CR",
"bucket": "direct",
"reachable": true,
"confidence": 0.95,
"pathLength": 3,
"path": [
"sym:java:...main",
"sym:java:...logInfo",
"sym:java:...JndiLookup.lookup"
]
},
"reasoning": "Direct call path from main() through logging framework to vulnerable lookup method"
},
{
"symbolId": "sym:java:...",
"display": "org.apache.logging.log4j.core.net.JndiManager.lookup",
"purl": "pkg:maven/org.apache.logging.log4j/log4j-core@2.14.1",
"expected": {
"latticeState": "CU",
"bucket": "unreachable",
"reachable": false,
"confidence": 0.90,
"pathLength": null,
"path": null
},
"reasoning": "JndiManager.lookup is present but not called from any reachable entry point"
}
],
"entryPoints": [
{
"symbolId": "sym:java:...",
"display": "com.example.app.Main.main",
"phase": "runtime",
"source": "manifest"
}
],
"expectedUncertainty": {
"states": [],
"aggregateTier": "T4",
"riskScore": 0.0
},
"expectedGateDecisions": [
{
"vulnId": "CVE-2021-44228",
"targetSymbol": "sym:java:...JndiLookup.lookup",
"requestedStatus": "not_affected",
"expectedDecision": "block",
"expectedBlockedBy": "LatticeState",
"expectedReason": "CR state incompatible with not_affected"
},
{
"vulnId": "CVE-2021-44228",
"targetSymbol": "sym:java:...JndiLookup.lookup",
"requestedStatus": "affected",
"expectedDecision": "allow"
}
]
}
```
---
## 3. Schema Definitions
### 3.1 Ground Truth Target
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `symbolId` | string | Yes | Canonical SymbolID (`sym:{lang}:{hash}`) |
| `display` | string | No | Human-readable symbol name |
| `purl` | string | No | Package URL of containing package |
| `expected.latticeState` | enum | Yes | Expected v1 lattice state: `U`, `SR`, `SU`, `RO`, `RU`, `CR`, `CU`, `X` |
| `expected.bucket` | enum | Yes | Expected v0 bucket (backward compat) |
| `expected.reachable` | boolean | Yes | True if symbol is reachable from any entry point |
| `expected.confidence` | number | Yes | Expected confidence score [0.0-1.0] |
| `expected.pathLength` | number | No | Expected path length (null if unreachable) |
| `expected.path` | string[] | No | Expected path (sorted, deterministic) |
| `reasoning` | string | Yes | Human explanation of expected outcome |
### 3.2 Expected Gate Decision
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `vulnId` | string | Yes | Vulnerability identifier |
| `targetSymbol` | string | Yes | Target SymbolID |
| `requestedStatus` | enum | Yes | VEX status: `affected`, `not_affected`, `under_investigation`, `fixed` |
| `expectedDecision` | enum | Yes | Gate outcome: `allow`, `block`, `warn` |
| `expectedBlockedBy` | string | No | Gate name if blocked |
| `expectedReason` | string | No | Expected reason message |
---
## 4. Sample Categories
### 4.1 Positive Samples (Reachable)
Known-reachable cases where vulnerable code is called:
- **direct-call:** Vulnerable function called directly from entry point
- **transitive:** Multi-hop path from entry point to vulnerable function
- **runtime-observed:** Confirmed reachable via runtime probe
- **init-array:** Reachable via load-time constructor
### 4.2 Negative Samples (Unreachable)
Known-unreachable cases where vulnerable code exists but isn't called:
- **dead-code:** Function present but never invoked
- **conditional-unreachable:** Function behind impossible condition
- **test-only:** Function only reachable from test entry points
- **deprecated-api:** Old API present but replaced by new implementation
### 4.3 Contested Samples
Cases where static and runtime evidence conflict:
- **static-reach-runtime-miss:** Static analysis finds path, runtime never observes
- **static-miss-runtime-hit:** Static analysis misses path, runtime observes execution
- **version-mismatch:** Analysis version differs from runtime version
---
## 5. Benchmark Metrics
### 5.1 Path Discovery Metrics
```
Precision = TruePositive / (TruePositive + FalsePositive)
Recall = TruePositive / (TruePositive + FalseNegative)
F1 = 2 * (Precision * Recall) / (Precision + Recall)
```
### 5.2 Lattice State Accuracy
```
StateAccuracy = CorrectStates / TotalTargets
BucketAccuracy = CorrectBuckets / TotalTargets (v0 compatibility)
```
### 5.3 Gate Decision Accuracy
```
GateAccuracy = CorrectDecisions / TotalGateTests
FalseAllow = AllowedWhenShouldBlock / TotalBlocks (critical metric)
FalseBlock = BlockedWhenShouldAllow / TotalAllows
```
---
## 6. Test Harness Integration
### 6.1 xUnit Test Pattern
```csharp
[Theory]
[MemberData(nameof(GetGroundTruthSamples))]
public async Task ReachabilityAnalysis_MatchesGroundTruth(GroundTruthSample sample)
{
// Arrange
var graph = await LoadRichGraphAsync(sample.GraphPath);
var scorer = _serviceProvider.GetRequiredService<ReachabilityScoringService>();
// Act
var result = await scorer.ComputeAsync(graph, sample.EntryPoints);
// Assert
foreach (var target in sample.Targets)
{
var actual = result.States.First(s => s.SymbolId == target.SymbolId);
Assert.Equal(target.Expected.LatticeState, actual.LatticeState);
Assert.Equal(target.Expected.Reachable, actual.Reachable);
Assert.InRange(actual.Confidence,
target.Expected.Confidence - 0.05,
target.Expected.Confidence + 0.05);
}
}
```
### 6.2 Benchmark Runner
```bash
# Run reachability benchmarks
dotnet run --project src/Scanner/__Tests/StellaOps.Scanner.Reachability.Benchmarks \
--dataset datasets/reachability/samples \
--output benchmark-results.json \
--threshold-f1 0.95 \
--threshold-gate-accuracy 0.99
```
---
## 7. Sample Contribution Guidelines
### 7.1 Adding New Samples
1. Create directory under `datasets/reachability/samples/{language}/{sample-name}/`
2. Add `manifest.json` with sample metadata
3. Add `richgraph-v1.json` (run scanner on artifacts)
4. Create `ground-truth.json` with manual annotations
5. Include reasoning for each expected outcome
6. Run validation: `dotnet test --filter "GroundTruth"`
### 7.2 Ground Truth Validation
Ground truth files must pass schema validation:
```bash
npx ajv validate -s docs/reachability/ground-truth.schema.json \
-d datasets/reachability/samples/**/ground-truth.json
```
### 7.3 Review Requirements
- All samples require two independent annotators
- Contested samples require security team review
- Changes to existing samples require regression test pass
---
## 8. Related Documents
- [Lattice Model](./lattice.md) — v1 formal 7-state lattice
- [Policy Gates](./policy-gate.md) — Gate rules for VEX decisions
- [Evidence Schema](./evidence-schema.md) — richgraph-v1 schema
- [richgraph-v1 Contract](../contracts/richgraph-v1.md) — Full schema specification
---
## Changelog
| Version | Date | Author | Changes |
|---------|------|--------|---------|
| 1.0.0 | 2025-12-13 | Scanner Guild | Initial design from Sprint 0401 |

View File

@@ -1,215 +1,254 @@
# Reachability Lattice & Scoring Model
> **Status:** Draft mirrors the December 2025 advisory on confidence-based reachability.
> **Owners:** Scanner Guild · Policy Guild · Signals Guild.
> **Status:** Implemented v0 in Signals; this document describes the current deterministic bucket model and its policy-facing implications.
> **Owners:** Scanner Guild · Signals Guild · Policy Guild.
> Stella Ops isn't just another scanner—it's a different product category: **deterministic, evidence-linked vulnerability decisions** that survive auditors, regulators, and supply-chain propagation.
This document defines the confidence lattice, evidence types, mitigation scoring, and policy gates used to turn static/runtime signals into reproducible reachability decisions and VEX statuses.
StellaOps models reachability as a deterministic, evidence-linked outcome that can safely represent "unknown" without silently producing false safety. Signals produces a `ReachabilityFactDocument` with per-target `states[]` and a top-level `score` that is stable under replays.
---
## 1. Overview
## 1. Current model (Signals v0)
<!-- TODO: Review for separate approval - updated lattice overview -->
**Key differentiator:** Unlike simplistic yes/no reachability approaches, the Stella Ops lattice model explicitly handles an **"Unknown"** (under_investigation) state, ensuring incomplete data doesn't lead to false safety. Every VEX decision is evidence-linked with proof pointers to the underlying reachability evidence.
Signals scoring (`src/Signals/StellaOps.Signals/Services/ReachabilityScoringService.cs`) computes, for each `target` symbol:
Classic "reachable: true/false" answers are too brittle. Stella Ops models reachability as an **ordered lattice** with explicit states and scores. Each analyzer/runtime probe emits `Evidence` documents; mitigations add `Mitigation` entries. The lattice engine joins both inputs into a `ReachDecision`:
- `reachable`: whether there exists a path from the selected `entryPoints[]` to `target`.
- `bucket`: a coarse classification of *why* the target is/was reachable.
- `confidence` (0..1): a bounded confidence value.
- `weight` (0..1): bucket multiplier.
- `score` (0..1): `confidence * weight`.
- `path[]`: the discovered path (if reachable), deterministically ordered.
- `evidence.runtimeHits[]`: runtime hit symbols that appear on the chosen path.
The fact-level `score` is the average of per-target scores, penalized by unknowns pressure (see §4).
---
## 2. Buckets & default weights
Bucket assignment is deterministic and uses this precedence:
1. `unreachable` — no path exists.
2. `entrypoint` — the `target` itself is an entrypoint.
3. `runtime` — at least one runtime hit overlaps the discovered path.
4. `direct` — reachable and the discovered path is length ≤ 2.
5. `unknown` — reachable but none of the above classifications apply.
Default weights (configurable via `SignalsOptions:Scoring:ReachabilityBuckets`):
| Bucket | Default weight |
|--------|----------------|
| `entrypoint` | `1.0` |
| `direct` | `0.85` |
| `runtime` | `0.45` |
| `unknown` | `0.5` |
| `unreachable` | `0.0` |
---
## 3. Confidence (reachable vs unreachable)
Default confidence values (configurable via `SignalsOptions:Scoring:*`):
| Input | Default |
|-------|---------|
| `reachableConfidence` | `0.75` |
| `unreachableConfidence` | `0.25` |
| `runtimeBonus` | `0.15` |
| `minConfidence` | `0.05` |
| `maxConfidence` | `0.99` |
Rules:
- Base confidence is `reachableConfidence` when `reachable=true`, otherwise `unreachableConfidence`.
- When `reachable=true` and runtime evidence overlaps the selected path, add `runtimeBonus` (bounded by `maxConfidence`).
- The final confidence is clamped to `[minConfidence, maxConfidence]`.
---
## 4. Unknowns pressure (missing/ambiguous evidence)
Signals tracks unresolved symbols/edges as **Unknowns** (see `docs/signals/unknowns-registry.md`). The number of unknowns for a subject influences the final score:
```
UNOBSERVED (09)
< POSSIBLE (1029)
< STATIC_PATH (3059)
< DYNAMIC_SEEN (6079)
< DYNAMIC_USER_TAINTED (8099)
< EXPLOIT_CONSTRAINTS_REMOVED (100)
unknownsPressure = unknownsCount / (targetsCount + unknownsCount)
pressurePenalty = min(unknownsPenaltyCeiling, unknownsPressure)
fact.score = avg(states[i].score) * (1 - pressurePenalty)
```
Each state corresponds to increasing confidence that a vulnerability can execute. Mitigations reduce scores; policy gates map scores to VEX statuses (`not_affected`, `under_investigation`, `affected`).
Default `unknownsPenaltyCeiling` is `0.35` (configurable).
This keeps the system deterministic while preventing unknown-heavy subjects from appearing "safe" by omission.
---
## 2. Core types
## 5. Evidence references & determinism anchors
```csharp
public enum ReachState { Unobserved, Possible, StaticPath, DynamicSeen, DynamicUserTainted, ExploitConstraintsRemoved }
Signals produces stable references intended for downstream evidence chains:
public enum EvidenceKind {
StaticCallEdge, StaticEntryPointProximity, StaticPackageDeclaredOnly,
RuntimeMethodHit, RuntimeStackSample, RuntimeHttpRouteHit,
UserInputSource, DataTaintFlow, ConfigFlagOn, ConfigFlagOff,
ContainerNetworkRestricted, ContainerNetworkOpen,
WafRulePresent, PatchLevel, VendorVexNotAffected, VendorVexAffected,
ManualOverride
}
- `metadata.fact.digest` — canonical digest of the reachability fact (`sha256:<hex>`).
- `metadata.fact.version` — monotonically increasing integer for the same `subjectKey`.
- Callgraph ingestion returns a deterministic `graphHash` (sha256) for the normalized callgraph.
public sealed record Evidence(
string Id,
EvidenceKind Kind,
double Weight,
string Source,
DateTimeOffset Timestamp,
string? ArtifactRef,
string? Details);
public enum MitigationKind { WafRule, FeatureFlagDisabled, AuthZEnforced, InputValidation, PatchedVersion, ContainerNetworkIsolation, RuntimeGuard, KillSwitch, Other }
public sealed record Mitigation(
string Id,
MitigationKind Kind,
double Strength,
string Source,
DateTimeOffset Timestamp,
string? ConfigHash,
string? Details);
public sealed record ReachDecision(
string VulnerabilityId,
string ComponentPurl,
ReachState State,
int Score,
string PolicyVersion,
IReadOnlyList<Evidence> Evidence,
IReadOnlyList<Mitigation> Mitigations,
IReadOnlyDictionary<string,string> Metadata);
```
Downstream services (Policy, UI/CLI explainers, replay tooling) should use these fields as stable evidence references.
---
## 3. Scoring policy (default)
## 6. Policy-facing guidance (avoid false "not affected")
| Evidence class | Base score contribution |
|--------------------------|-------------------------|
| Static path (call graph) | ≥ 30 |
| Runtime hit | ≥ 60 |
| User-tainted flow | ≥ 80 |
| "Constraints removed" | = 100 |
| Lockfile-only evidence | 10 (if no other signals)|
Policy should treat `unreachable` (or low fact score) as **insufficient** to claim "not affected" unless:
Mitigations subtract up to 40 points (configurable):
- the reachability evidence is present and referenced (`metadata.fact.digest`), and
- confidence is above a high-confidence threshold.
When evidence is missing or confidence is low, the correct output is **under investigation** rather than "not affected".
---
## 7. Signals API pointers
- `docs/api/signals/reachability-contract.md`
- `docs/api/signals/samples/facts-sample.json`
---
## 8. Roadmap (tracked in Sprint 0401)
- Introduce first-class uncertainty state lists + entropy-derived `riskScore` (see `docs/uncertainty/README.md`).
- Extend evidence refs to include CAS/DSSE pointers for graph-level and edge-bundle attestations.
---
## 9. Formal Lattice Model v1 (design — Sprint 0401)
The v0 bucket model provides coarse classification. The v1 lattice model introduces a formal 7-state lattice with algebraic join/meet operations for monotonic, deterministic reachability analysis across evidence types.
### 9.1 State Definitions
| State | Code | Ordering | Description |
|-------|------|----------|-------------|
| `Unknown` | `U` | ⊥ (bottom) | No evidence available; default state |
| `StaticallyReachable` | `SR` | 1 | Static analysis suggests path exists |
| `StaticallyUnreachable` | `SU` | 1 | Static analysis finds no path |
| `RuntimeObserved` | `RO` | 2 | Runtime probe/hit confirms execution |
| `RuntimeUnobserved` | `RU` | 2 | Runtime probe active but no hit observed |
| `ConfirmedReachable` | `CR` | 3 | Both static + runtime agree reachable |
| `ConfirmedUnreachable` | `CU` | 3 | Both static + runtime agree unreachable |
| `Contested` | `X` | (top) | Static and runtime evidence conflict |
### 9.2 Lattice Ordering (Hasse Diagram)
```
effectiveScore = baseScore - min(sum(m.Strength), 1.0) * MaxMitigationDelta
Contested (X)
/ | \
/ | \
ConfirmedReachable | ConfirmedUnreachable
(CR) | (CU)
| \ / / |
| \ / / |
| \ / / |
RuntimeObserved RuntimeUnobserved
(RO) (RU)
| |
| |
StaticallyReachable StaticallyUnreachable
(SR) (SU)
\ /
\ /
Unknown (U)
```
Clamp final scores to 0100.
### 9.3 Join Rules (⊔ — least upper bound)
---
When combining evidence from multiple sources, use the join operation:
## 4. State & VEX gates
```
U ⊔ S = S (any evidence beats unknown)
SR ⊔ RO = CR (static reachable + runtime hit = confirmed)
SU ⊔ RU = CU (static unreachable + runtime miss = confirmed)
SR ⊔ RU = X (static reachable but runtime miss = contested)
SU ⊔ RO = X (static unreachable but runtime hit = contested)
CR ⊔ CU = X (conflicting confirmations = contested)
X ⊔ * = X (contested absorbs all)
```
Default thresholds (edit in `reachability.policy.yml`):
**Full join table:**
| State | Score range |
|----------------------------|-------------|
| UNOBSERVED | 09 |
| POSSIBLE | 1029 |
| STATIC_PATH | 3059 |
| DYNAMIC_SEEN | 6079 |
| DYNAMIC_USER_TAINTED | 8099 |
| EXPLOIT_CONSTRAINTS_REMOVED| 100 |
| ⊔ | U | SR | SU | RO | RU | CR | CU | X |
|---|---|----|----|----|----|----|----|---|
| **U** | U | SR | SU | RO | RU | CR | CU | X |
| **SR** | SR | SR | X | CR | X | CR | X | X |
| **SU** | SU | X | SU | X | CU | X | CU | X |
| **RO** | RO | CR | X | RO | X | CR | X | X |
| **RU** | RU | X | CU | X | RU | X | CU | X |
| **CR** | CR | CR | X | CR | X | CR | X | X |
| **CU** | CU | X | CU | X | CU | X | CU | X |
| **X** | X | X | X | X | X | X | X | X |
VEX mapping:
### 9.4 Meet Rules (⊓ — greatest lower bound)
* **not_affected**: score ≤ 25 or mitigations dominate (score reduced below threshold).
* **affected**: score ≥ 60 (dynamic evidence without sufficient mitigation).
* **under_investigation**: everything between. **This explicit "Unknown" state is a key differentiator**—incomplete data never leads to false safety.
Used for conservative intersection (e.g., multi-entry-point consensus):
Each decision records `reachability.policy.version`, analyzer versions, policy hash, and config snapshot so downstream verifiers can replay the exact logic. All decisions are sealed in Decision Capsules for audit-grade reproducibility.
```
U ⊓ * = U (unknown is bottom)
CR ⊓ CR = CR (agreement preserved)
X ⊓ S = S (drop contested to either side)
```
---
### 9.5 Monotonicity Properties
## 5. Evidence sources
1. **Evidence accumulation is monotonic:** Once state rises in the lattice, it cannot descend without explicit revocation.
2. **Revocation resets to Unknown:** When evidence is invalidated (e.g., graph invalidation), state resets to `U`.
3. **Contested states require human triage:** `X` state triggers policy flags and UI attention.
| Signal group | Producers | EvidenceKind |
|--------------|-----------|--------------|
| Static call graph | Roslyn/IL walkers, ASP.NET routing models, JVM/JIT analyzers | `StaticCallEdge`, `StaticEntryPointProximity`, `StaticFrameworkRouteEdge` |
| Runtime sampling | .NET EventPipe, JFR, Node inspector, Go/Rust probes | `RuntimeMethodHit`, `RuntimeStackSample`, `RuntimeHttpRouteHit` |
| Data/taint | Taint analyzers, user-input detectors | `UserInputSource`, `DataTaintFlow` |
| Environment | Config snapshot, container args, network policy | `ConfigFlagOn/Off`, `ContainerNetworkRestricted/Open` |
| Mitigations | WAF connectors, patch diff, kill switches | `MitigationKind.*` via `Mitigation` records |
| Trust | Vendor VEX statements, manual overrides | `VendorVexNotAffected/Affected`, `ManualOverride` |
### 9.6 Mapping v0 Buckets to v1 States
Each evidence object **must** log `Source`, timestamps, and references (function IDs, config hashes) so auditors can trace it in the event graph. This enables **evidence-linked VEX decisions** where every assertion includes pointers to the underlying proof.
| v0 Bucket | v1 State(s) | Notes |
|-----------|-------------|-------|
| `unreachable` | `SU`, `CU` | Depends on runtime evidence availability |
| `entrypoint` | `CR` | Entry points are by definition reachable |
| `runtime` | `RO`, `CR` | Depends on static analysis agreement |
| `direct` | `SR`, `CR` | Direct paths with/without runtime confirmation |
| `unknown` | `U` | No evidence available |
---
### 9.7 Policy Decision Matrix
## 6. Event graph schema
| v1 State | VEX "not_affected" | VEX "affected" | VEX "under_investigation" |
|----------|-------------------|----------------|---------------------------|
| `U` | ❌ blocked | ⚠️ needs evidence | ✅ default |
| `SR` | ❌ blocked | ✅ allowed | ✅ allowed |
| `SU` | ⚠️ low confidence | ❌ contested | ✅ allowed |
| `RO` | ❌ blocked | ✅ allowed | ✅ allowed |
| `RU` | ⚠️ medium confidence | ❌ contested | ✅ allowed |
| `CR` | ❌ blocked | ✅ required | ❌ invalid |
| `CU` | ✅ allowed | ❌ blocked | ❌ invalid |
| `X` | ❌ blocked | ❌ blocked | ✅ required |
Persist function-level edges and evidence in Mongo (or your event store) under:
### 9.8 Implementation Notes
* `reach_functions` documents keyed by `FunctionId`.
* `reach_call_sites` `CallSite` edges (`caller`, `callee`, `frameworkEdge`).
* `reach_evidence` array of `Evidence` per `(scanId, vulnId, component)`.
* `reach_mitigations` array of `Mitigation` entries with config hashes.
* `reach_decisions` final `ReachDecision` document; references above IDs.
- **State storage:** `ReachabilityFactDocument.states[].latticeState` field (enum)
- **Join implementation:** `ReachabilityLattice.Join(a, b)` in `src/Signals/StellaOps.Signals/Services/`
- **Backward compatibility:** v0 bucket computed from v1 state for API consumers
All collections are tenant-scoped and include analyzer/policy version metadata.
### 9.9 Evidence Chain Requirements
---
## 7. Policy gates → VEX decisions
VEXer consumes `ReachDecision` and `reachability.policy.yml` to emit:
Each lattice state transition must be accompanied by evidence references:
```json
{
"vulnerability": "CVE-2025-1234",
"products": ["pkg:nuget/Example@1.2.3"],
"status": "not_affected|under_investigation|affected",
"status_notes": "Reachability score 22 (Possible) with WAF rule mitigation.",
"justification": "component_not_present|vulnerable_code_not_present|... or custom reason",
"action_statement": "Monitor config ABC",
"impact_statement": "Runtime probes observed 0 hits; static call graph absent.",
"timestamp": "...",
"custom": {
"reachability": {
"state": "POSSIBLE",
"score": 22,
"policyVersion": "reach-1",
"evidenceRefs": ["evidence:123", "mitigation:456"]
"symbol": "sym:java:...",
"latticeState": "CR",
"previousState": "SR",
"evidence": {
"static": {
"graphHash": "blake3:...",
"pathLength": 3,
"confidence": 0.92
},
"runtime": {
"probeId": "probe:...",
"hitCount": 47,
"observedAt": "2025-12-13T10:00:00Z"
}
}
},
"transitionAt": "2025-12-13T10:00:00Z"
}
```
Justifications cite specific evidence/mitigation IDs so replay bundles (`docs/replay/DETERMINISTIC_REPLAY.md`) can prove the decision.
---
## 8. Runtime probes (overview)
* .NET: EventPipe session watching `Microsoft-Windows-DotNETRuntime/Loader,JIT``RuntimeMethodHit`.
* JVM: JFR recording with `MethodProfilingSample` events.
* Node/TS: Inspector or `--trace-event-categories node.async_hooks,node.perf` sample.
* Go/Rust: `pprof`/probe instrumentation.
All runtime probes write evidence via `IRuntimeEvidenceSink`, which deduplicates hits, enriches them with `FunctionId`, and stores them in `reach_evidence`.
See `src/Scanner/StellaOps.Scanner.WebService/Reachability/Runtime/DotNetRuntimeProbe.cs` (once implemented) for reference.
---
## 9. Hybrid Reachability
<!-- TODO: Review for separate approval - added hybrid reachability section -->
Stella Ops combines **static call-graph analysis** with **runtime process tracing** for true hybrid reachability:
- **Static analysis** provides call-graph edges from IL/bytecode analysis, framework routing models, and entry-point proximity calculations.
- **Runtime analysis** provides observed method hits, stack samples, and HTTP route hits from live or shadow traffic.
- **Hybrid reconciliation** merges both signal types, with each edge type attestable via DSSE. See `docs/reachability/hybrid-attestation.md` for the attestation model.
This hybrid approach ensures that both build-time and run-time context contribute to the same verdict, avoiding the blind spots of purely static or purely runtime analysis.
---
## 10. Roadmap
| Task | Description |
|------|-------------|
| `REACH-LATTICE-401-023` | Initial lattice types + scoring engine + event graph schema. |
| `REACH-RUNTIME-402-024` | Productionize runtime probes (EventPipe/JFR) with opt-in config and telemetry. |
| `REACH-VEX-402-025` | Wire `ReachDecision` into VEX generator; ensure OpenVEX/CSAF cite reachability evidence. |
| `REACH-POLICY-402-026` | Expose reachability gates in Policy DSL & CLI (edit/lint/test). |
Keep this doc updated as the lattice evolves or new signals/mitigations are added.

View File

@@ -0,0 +1,269 @@
# Reachability Evidence Policy Gates
> **Status:** Design v1 (Sprint 0401)
> **Owners:** Policy Guild, Signals Guild, VEX Guild
This document defines the policy gates that enforce reachability evidence requirements for VEX decisions. Gates prevent unsafe "not_affected" claims when evidence is insufficient.
---
## 1. Overview
Policy gates act as checkpoints between evidence (reachability lattice state, uncertainty tier) and VEX status transitions. They ensure that:
1. **No false safety:** "not_affected" requires strong evidence of unreachability
2. **Explicit uncertainty:** Missing evidence triggers "under_investigation" rather than silence
3. **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/reachability/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 (see `docs/uncertainty/README.md` §1.1).
| 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:
```json
{
"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):
1. **Block all definitive statuses:** Neither "not_affected" nor "affected" allowed
2. **Force "under_investigation":** Auto-assign until triage resolves conflict
3. **Emit triage event:** Notify VEX operators of conflict with evidence links
4. **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:
```json
{
"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:
- `justification` is 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`:
```yaml
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:
```json
{
"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):
```json
{
"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_total` rate > threshold → Audit review
- `stellaops_contested_states_total` > 10 → Triage backlog alert
---
## 10. Related Documents
- [Lattice Model](./lattice.md) — v1 formal 7-state lattice
- [Uncertainty States](../uncertainty/README.md) — Tier definitions and risk scoring
- [Evidence Schema](./evidence-schema.md) — richgraph-v1 schema
- [VEX Contract](../contracts/vex-v1.md) — VEX document schema
---
## Changelog
| Version | Date | Author | Changes |
|---------|------|--------|---------|
| 1.0.0 | 2025-12-13 | Policy Guild | Initial design from Sprint 0401 |

View File

@@ -0,0 +1,174 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://stella-ops.org/schemas/tte-event.schema.json",
"title": "Time-to-Evidence (TTE) Telemetry Event",
"description": "Schema for tracking time-to-evidence metrics across triage workflows (TTE1-TTE10)",
"type": "object",
"required": [
"schema_version",
"event_type",
"timestamp",
"tenant_id",
"correlation_id",
"phase",
"elapsed_ms"
],
"properties": {
"schema_version": {
"type": "string",
"pattern": "^v[0-9]+\\.[0-9]+$",
"description": "Schema version (e.g., v1.0)",
"examples": ["v1.0"]
},
"event_type": {
"type": "string",
"enum": [
"tte.phase.started",
"tte.phase.completed",
"tte.phase.failed",
"tte.phase.timeout",
"tte.evidence.attached",
"tte.evidence.verified",
"tte.decision.made",
"tte.slo.breach"
],
"description": "Type of TTE event"
},
"timestamp": {
"type": "string",
"format": "date-time",
"description": "ISO-8601 UTC timestamp when event occurred"
},
"tenant_id": {
"type": "string",
"minLength": 1,
"description": "Tenant identifier for scoping"
},
"correlation_id": {
"type": "string",
"format": "uuid",
"description": "Correlation ID linking all events in a triage workflow"
},
"phase": {
"type": "string",
"enum": [
"scan_to_finding",
"finding_to_evidence",
"evidence_to_decision",
"decision_to_attestation",
"attestation_to_verification",
"verification_to_policy",
"end_to_end"
],
"description": "Phase of the evidence chain being measured"
},
"elapsed_ms": {
"type": "number",
"minimum": 0,
"description": "Elapsed time in milliseconds for this phase"
},
"finding_id": {
"type": "string",
"description": "Finding identifier if applicable"
},
"vulnerability_id": {
"type": "string",
"pattern": "^CVE-[0-9]{4}-[0-9]+$",
"description": "CVE identifier if applicable"
},
"artifact_digest": {
"type": "string",
"pattern": "^sha256:[a-f0-9]{64}$",
"description": "Artifact digest in OCI format"
},
"evidence_type": {
"type": "string",
"enum": ["attestation", "vex", "sbom", "policy_eval", "reachability", "fix_pr"],
"description": "Type of evidence attached or verified"
},
"evidence_count": {
"type": "integer",
"minimum": 0,
"description": "Number of evidence items attached in this event"
},
"decision_status": {
"type": "string",
"enum": ["not_affected", "affected", "fixed", "under_investigation"],
"description": "VEX decision status if event is decision-related"
},
"verification_result": {
"type": "string",
"enum": ["verified", "failed", "pending", "expired", "revoked"],
"description": "Result of attestation/signature verification"
},
"slo_target_ms": {
"type": "number",
"minimum": 0,
"description": "SLO target in milliseconds for this phase"
},
"slo_breach": {
"type": "boolean",
"description": "True if this event represents an SLO breach"
},
"surface": {
"type": "string",
"enum": ["api", "ui", "cli", "webhook", "scheduler"],
"description": "Surface where the event originated"
},
"user_agent": {
"type": "string",
"description": "User agent string (filtered for bots)"
},
"is_automated": {
"type": "boolean",
"description": "True if event triggered by automation (not human)"
},
"offline_mode": {
"type": "boolean",
"description": "True if event occurred in offline/airgap mode"
},
"error_code": {
"type": ["string", "null"],
"description": "Error code if event_type is failure/timeout"
},
"metadata": {
"type": "object",
"additionalProperties": true,
"description": "Additional context-specific metadata"
}
},
"additionalProperties": false,
"examples": [
{
"schema_version": "v1.0",
"event_type": "tte.phase.completed",
"timestamp": "2025-12-13T14:30:00.000Z",
"tenant_id": "tenant-123",
"correlation_id": "550e8400-e29b-41d4-a716-446655440000",
"phase": "finding_to_evidence",
"elapsed_ms": 1250,
"finding_id": "finding-abc-123",
"vulnerability_id": "CVE-2024-1234",
"evidence_type": "attestation",
"evidence_count": 1,
"surface": "ui",
"is_automated": false,
"slo_target_ms": 5000,
"slo_breach": false
},
{
"schema_version": "v1.0",
"event_type": "tte.slo.breach",
"timestamp": "2025-12-13T14:35:00.000Z",
"tenant_id": "tenant-456",
"correlation_id": "660e8400-e29b-41d4-a716-446655440001",
"phase": "end_to_end",
"elapsed_ms": 125000,
"slo_target_ms": 60000,
"slo_breach": true,
"surface": "api",
"is_automated": true,
"error_code": "TTE_SLO_END_TO_END_BREACH"
}
]
}

View File

@@ -1,28 +1,73 @@
# Uncertainty States & Entropy Scoring
> **Status:** Draft aligns with the November2025 advisory on explicit uncertainty tracking.
> **Owners:** Signals Guild · Concelier Guild · UI Guild.
> **Status:** Implemented v0 for reachability facts (Signals).
> **Owners:** Signals Guild · Policy Guild · UI Guild.
StellaOps treats missing data and untrusted evidence as **first-class uncertainty states**, not silent false negatives. Each finding stores a list of `UncertaintyState` entries plus supporting evidence; the risk scorer uses their entropy to adjust final risk. Policy and UI surfaces reveal uncertainty to operators rather than hiding it.
StellaOps treats missing data and untrusted evidence as **first-class uncertainty states**, not silent false negatives. Signals persists uncertainty state entries alongside reachability facts and derives a deterministic `riskScore` that increases when entropy is high.
---
## 1. Core states (extensible)
| Code | Name | Meaning |
|------|------------------------|---------------------------------------------------------------------------|
| `U1` | MissingSymbolResolution| Vulnerability → function mapping unresolved (no PDB/IL map, missing dSYMs). |
| `U2` | MissingPurl | Package identity/version ambiguous (lockfile absent, heuristics only). |
| `U3` | UntrustedAdvisory | Advisory source lacks DSSE/Sigstore provenance or corroboration. |
| `U4+`| (future) | e.g. partial SBOM coverage, missing container layers, unresolved transitives. |
| Code | Name | Meaning |
|------|------|---------|
| `U1` | `MissingSymbolResolution` | Unresolved symbols/edges prevent a complete reachability proof. |
| `U2` | `MissingPurl` | Package identity/version is ambiguous (lockfile absent, heuristics only). |
| `U3` | `UntrustedAdvisory` | Advisory source lacks provenance/corroboration. |
| `U4` | `Unknown` | No analyzers have processed this subject; baseline uncertainty. |
Each state records `entropy` (01) and an evidence list pointing to analyzers, heuristics, or advisory sources that asserted the uncertainty.
Each state records:
- `entropy` (0..1)
- `evidence[]` list pointing to analyzers/heuristics/sources
- optional `timestamp` (UTC)
---
## 2. Schema
## 1.1 Uncertainty Tiers (v1 — Sprint 0401)
```jsonc
Uncertainty states are grouped into **tiers** that determine policy thresholds and UI treatment.
### Tier Definitions
| Tier | Entropy Range | States | Risk Modifier | Policy Implication |
|------|---------------|--------|---------------|-------------------|
| **T1 (High)** | `0.7 - 1.0` | `U1` (high), `U4` | `+50%` | Block "not_affected", require human review |
| **T2 (Medium)** | `0.4 - 0.69` | `U1` (medium), `U2` | `+25%` | Warn on "not_affected", flag for review |
| **T3 (Low)** | `0.1 - 0.39` | `U2` (low), `U3` | `+10%` | Allow "not_affected" with advisory note |
| **T4 (Negligible)** | `0.0 - 0.09` | `U3` (low) | `+0%` | Normal processing, no special handling |
### Tier Assignment Rules
1. **U1 (MissingSymbolResolution):**
- `entropy >= 0.7` → T1 (>30% unknowns in callgraph)
- `entropy >= 0.4` → T2 (15-30% unknowns)
- `entropy < 0.4` → T3 (<15% unknowns)
2. **U2 (MissingPurl):**
- `entropy >= 0.5` T2 (>50% packages unresolved)
- `entropy < 0.5` → T3 (<50% packages unresolved)
3. **U3 (UntrustedAdvisory):**
- `entropy >= 0.6` T3 (no corroboration)
- `entropy < 0.6` T4 (partial corroboration)
4. **U4 (Unknown):**
- Always T1 (no analysis performed = maximum uncertainty)
### Aggregate Tier Calculation
When multiple uncertainty states exist, the aggregate tier is the **maximum** (most severe):
```
aggregateTier = max(tier(state) for state in uncertainty.states)
```
---
## 2. JSON shape
```json
{
"uncertainty": {
"states": [
@@ -30,24 +75,12 @@ Each state records `entropy` (01) and an evidence list pointing to analyzers,
"code": "U1",
"name": "MissingSymbolResolution",
"entropy": 0.72,
"timestamp": "2025-11-12T14:12:00Z",
"evidence": [
{
"type": "AnalyzerProbe",
"sourceId": "dotnet.symbolizer",
"detail": "No PDB/IL map for Foo.Bar::DoWork"
}
],
"timestamp": "2025-11-12T14:12:00Z"
},
{
"code": "U2",
"name": "MissingPurl",
"entropy": 0.55,
"evidence": [
{
"type": "PackageHeuristic",
"sourceId": "jar.manifest",
"detail": "Guessed groupId=com.example, version ~= 1.9.x"
"type": "UnknownsRegistry",
"sourceId": "signals.unknowns",
"detail": "unknownsCount=12;unknownsPressure=0.375"
}
]
}
@@ -56,98 +89,140 @@ Each state records `entropy` (01) and an evidence list pointing to analyzers,
}
```
### C# models
---
```csharp
public sealed record UncertaintyEvidence(string Type, string SourceId, string Detail);
## 3. Risk score math (Signals)
public sealed record UncertaintyState(
string Code,
string Name,
double Entropy,
IReadOnlyList<UncertaintyEvidence> Evidence);
Signals computes a `riskScore` deterministically during reachability recompute:
```
meanEntropy = avg(uncertainty.states[].entropy) // 0 when no states
entropyBoost = clamp(meanEntropy * k, 0 .. boostCeiling)
riskScore = clamp(baseScore * (1 + entropyBoost), 0 .. 1)
```
Store them alongside `FindingDocument` in Signals and expose via APIs/CLI/GraphQL so downstream services can display them or enforce policies.
Where:
- `baseScore` is the average of per-target reachability state scores (before unknowns penalty).
- `k` defaults to `0.5` (`SignalsOptions:Scoring:UncertaintyEntropyMultiplier`).
- `boostCeiling` defaults to `0.5` (`SignalsOptions:Scoring:UncertaintyBoostCeiling`).
---
## 3. Risk score math
## 4. Policy guidance (high level)
```
riskScore = baseScore
× reachabilityFactor (0..1)
× trustFactor (0..1)
× (1 + entropyBoost)
Uncertainty should bias decisions away from "not affected" when evidence is missing:
entropyBoost = clamp(avg(uncertainty[i].entropy) × k, 0 .. 0.5)
```
- High entropy (`U1` with high `entropy`) should lead to **under investigation** and drive remediation (upload symbols, run probes, close unknowns).
- Low entropy should allow normal confidence-based gates.
* `k` defaults to `0.5`. With mean entropy = 0.8, boost = 0.4 → risk increases 40% to highlight unknowns.
* If no uncertainty states exist, entropy boost = 0 and the previous scoring remains.
Persist both `uncertainty.states` and `riskScore` so policies, dashboards, and APIs stay deterministic.
See `docs/reachability/lattice.md` for the current reachability score model and `docs/api/signals/reachability-contract.md` for the Signals contract.
---
## 4. Policy + actions
## 5. Tier-Based Risk Score (v1 — Sprint 0401)
Use uncertainty in Concelier/Excitors policies:
### Risk Score Formula
* **Block release** if critical CVE has `U1` with entropy ≥0.70 until symbols or runtime probes are provided.
* **Warn** when only `U3` exists allow deployment but require corroboration (OSV/GHSA, CSAF).
* **Auto-create tasks** for `U2` to fix SBOM/purl data quality.
Building on §3, the v1 risk score incorporates tier-based modifiers:
Recommended policy predicates:
```
tierModifier = {
T1: 0.50,
T2: 0.25,
T3: 0.10,
T4: 0.00
}[aggregateTier]
```yaml
when:
all:
- uncertaintyCodesAny: ["U1"]
- maxEntropyGte: 0.7
riskScore = clamp(baseScore * (1 + tierModifier + entropyBoost), 0 .. 1)
```
Excitors can suggest remediation actions (upload PDBs, add lockfiles, fetch signed CSAF) based on state codes.
Where:
- `baseScore` is the average of per-target reachability state scores
- `tierModifier` is the tier-based risk increase
- `entropyBoost` is the existing entropy-based boost 3)
### Example Calculation
```
Given:
- baseScore = 0.4 (moderate reachability)
- uncertainty.states = [
{code: "U1", entropy: 0.72}, // T1 tier
{code: "U3", entropy: 0.45} // T3 tier
]
- aggregateTier = T1 (max of T1, T3)
- tierModifier = 0.50
meanEntropy = (0.72 + 0.45) / 2 = 0.585
entropyBoost = clamp(0.585 * 0.5, 0 .. 0.5) = 0.2925
riskScore = clamp(0.4 * (1 + 0.50 + 0.2925), 0 .. 1)
= clamp(0.4 * 1.7925, 0 .. 1)
= clamp(0.717, 0 .. 1)
= 0.717
```
### Tier Thresholds for Policy Gates
| Tier | `riskScore` Range | VEX "not_affected" | VEX "affected" | Auto-triage |
|------|-------------------|-------------------|----------------|-------------|
| T1 | `>= 0.6` | blocked | review | `under_investigation` |
| T2 | `0.4 - 0.59` | warning | allowed | Manual review |
| T3 | `0.2 - 0.39` | with note | allowed | Normal |
| T4 | `< 0.2` | allowed | allowed | Normal |
---
## 5. UI guidelines
## 6. JSON Schema (v1)
* Display chips `U1`, `U2`, … on each finding. Tooltip: entropy level + evidence bullets (“AnalyzerProbe/dotnet.symbolizer: …”).
* Provide “How to reduce entropy” hints: symbol uploads, EventPipe probes, purl overrides, advisory verification.
* Show entropy in filters (e.g., “entropy ≥ 0.5”) so teams can prioritise closing uncertainty gaps.
See `components/UncertaintyChipStack` (planned) for a reference implementation.
---
## 6. Event sourcing / audit
Emit `FindingUncertaintyUpdated` events whenever the set changes:
Extended schema with tier information:
```json
{
"type": "FindingUncertaintyUpdated",
"findingId": "finding:service:prod:CVE-2023-12345",
"updatedAt": "2025-11-12T14:21:33Z",
"uncertainty": [ ...states... ]
"uncertainty": {
"states": [
{
"code": "U1",
"name": "MissingSymbolResolution",
"entropy": 0.72,
"tier": "T1",
"timestamp": "2025-12-13T10:00:00Z",
"evidence": [
{
"type": "UnknownsRegistry",
"sourceId": "signals.unknowns",
"detail": "unknownsCount=45;totalSymbols=125;unknownsPressure=0.36"
}
]
},
{
"code": "U4",
"name": "Unknown",
"entropy": 1.0,
"tier": "T1",
"timestamp": "2025-12-13T10:00:00Z",
"evidence": [
{
"type": "NoAnalysis",
"sourceId": "signals.bootstrap",
"detail": "subject not yet analyzed"
}
]
}
],
"aggregateTier": "T1",
"riskScore": 0.717,
"computedAt": "2025-12-13T10:00:00Z"
}
}
```
Projections recompute `riskScore` deterministically, and the event log provides an audit trail showing when/why entropy changed.
---
## 7. Action hints (per state)
## 7. Implementation Pointers
| Code | Suggested remediation |
|------|-----------------------|
| `U1` | Upload PDBs/dSYM files, enable symbolizer connectors, attach runtime probes (EventPipe/JFR). |
| `U2` | Provide package overrides, ingest lockfiles, fix SBOM generator metadata. |
| `U3` | Obtain signed CSAF/OSV evidence, verify via Excitors connectors, or mark trust overrides in policy. |
### 8. Unknowns registry tie-in
Unresolved identities and missing edges should be recorded as Unknowns (see `docs/signals/unknowns-registry.md`). Signals scoring may add an `unknowns_pressure` term when density of unresolved items is high near entrypoints; Policy and UI should surface these records so operators can close the gaps rather than hiding the uncertainty.
Keep this file updated as new states (U4+) or tooling hooks land. Link additional guides (symbol upload, purl overrides) once available.
- **Tier calculation:** `UncertaintyTierCalculator` in `src/Signals/StellaOps.Signals/Services/`
- **Risk score math:** `ReachabilityScoringService.ComputeRiskScore()` (extend existing)
- **Policy integration:** `docs/reachability/policy-gate.md` for gate rules
- **Lattice integration:** `docs/reachability/lattice.md` §9 for v1 lattice states