# Reachability Analysis Technical Reference **Source Advisories**: - 05-Dec-2025 - Building a Deterministic, Reachability‑First Architecture - 13-Dec-2025 - Designing the Call‑Stack Reachability Engine - 03-Dec-2025 - Reachability Benchmarks and Moat Metrics - 09-Dec-2025 - Caching Reachability the Smart Way - 06-Dec-2025 - Reachability Methods Worth Testing This Week - 04-Dec-2025 - Ranking Unknowns in Reachability Graphs - 02-Dec-2025 - Designing Deterministic Reachability UX - 05-Dec-2025 - Design Notes on Smart‑Diff and Call‑Stack Analysis **Last Updated**: 2025-12-14 --- ## 1. ARCHITECTURE PATTERNS ### 1.1 Core System Architecture **Module Responsibilities** - `StellaOps.Scanner.WebService`: Authoritative for reachability pipeline and lattice computation - Language workers: Stateless compute producing `CallGraph.v1.json` - Runtime collectors: Agent/sidecar emitting evidence events only - Concelier/Excititor: Provide pruned sources; never compute reachability - Authority: Manages replay manifests, crypto profiles - Scheduler: Executes rescan/escalation policies **Architectural Rules** - Scanner = origin of truth for reachability - Concelier/Vexer = prune-preservers only - Authority = replay manifest owner - Scheduler = executor of policies - Postgres = System of Record (SoR) - Valkey = ephemeral only (dedupe, hot cache, rate limits) ### 1.2 Evidence Graph Structure **Node Types** - `Artifact`, `Component`, `Vulnerability`, `Attestation`, `Build`, `Deployment`, `RuntimeSignal` **Edge Types** - `DESCRIBES`, `AFFECTS`, `NOT_AFFECTED_BY`, `FIXED_IN`, `DERIVED_FROM`, `DEPLOYS`, `OBSERVED_AT_RUNTIME` **Edge Signing** - Sign edges, not just nodes (edge = claim) ### 1.3 Determinism Requirements **Input Manifest Structure** ```jsonc { "scannerVersion": "1.3.0", "rulesetId": "stella-default-2025.11", "feeds": { "nvdDigest": "sha256:...", "osvDigest": "sha256:..." }, "sbomDigest": "sha256:...", "policyDigest": "sha256:..." } ``` **Canonicalization Rules** - Sort arrays by stable keys - Normalize paths (POSIX style) - Line endings (LF) - Encodings (UTF-8) - No environment variables in core algorithms - No machine-local files - No system clock inside algorithms ### 1.4 Build Once, Query Many (avoid all-pairs precompute) Separate *graph construction* from *reachability queries*: 1. **Build step (once per artifact/version)** - Produce a deterministic call graph index: `CallGraph.v1.json` (or a binary format with an accompanying canonical JSON manifest and digest). - Persist it content-addressed and bind it into the `ReplayManifest`. 2. **Query step (per entrypoint/sink query)** - Run a bounded search (BFS / bidirectional BFS / A*) over the stored index. - Return: `reachable`, a canonical shortest path, and `why[]` evidence (callsite/method IDs). Rule: never compute full transitive-closure tables unless the graph is proven small and bounded; prefer per-query search + caching keyed by immutable inputs. ### 1.5 Deterministic Reachability Cache Key Cache *query results*, not graph construction: ``` reachabilityCacheKey = ( graphRevisionId, algorithmId, fromSymbolId, toSymbolId, contextHash // entrypoints, flags/env, runtime profile selector ) ``` Cache requirements: - Include the canonical path in cache entries; tie-break with stable neighbor ordering. - Cache negative results explicitly (`reachable=false`) and represent uncertainty (`reachable=null`) without coercion. - Invalidate by construction: when `graphRevisionId` changes, cache keys naturally change. ### 1.6 Compositional Library Summaries (ReachCheck-style option) For third-party libraries, evaluate a compositional approach: - Precompute *library reachability summaries* once per `(purl, version, digest)` offline. - Merge summaries with the application graph at query time. - Store each summary as content-addressed evidence with its own digest and tool version. If using matrix-based transitive-closure summaries, treat them as an optimization layer only: surface any unsoundness/unknowns explicitly and keep all outputs deterministic (no sampling, no nondeterministic parallel traversal ordering). ## 2. DATA CONTRACTS ### 2.1 CallGraph.v1.json Schema ```json { "schema": "stella.callgraph.v1", "scanKey": "uuid", "language": "dotnet|java|node|python|go|rust|binary", "artifacts": [{ "artifactKey": "", "kind": "assembly|jar|module|binary", "sha256": "" }], "nodes": [{ "nodeId": "", "artifactKey": "", "symbolKey": "Namespace.Type::Method()", "visibility": "public|internal|private|unknown", "isEntrypointCandidate": false }], "edges": [{ "from": "nodeId", "to": "nodeId", "kind": "static|heuristic", "reason": "direct_call|virtual_call|reflection_string|di_binding|dynamic_import|unknown", "weight": 1.0 }], "entrypoints": [{ "nodeId": "", "kind": "http|grpc|cli|job|event|unknown", "route": "/api/orders/{id}", "framework": "aspnetcore|minimalapi|spring|express|unknown" }] } ``` ### 2.2 RuntimeEvidence.v1.json Schema ```json { "schema": "stella.runtimeevidence.v1", "scanKey": "uuid", "collectedAt": "2025-12-14T10:00:00Z", "environment": { "os": "linux|windows", "k8s": {"namespace": "", "pod": "", "container": ""}, "imageDigest": "sha256:", "buildId": "" }, "samples": [{ "timestamp": "", "pid": 1234, "threadId": 77, "frames": ["nodeId","nodeId","nodeId"], "sampleWeight": 1.0 }], "loadedArtifacts": [{ "artifactKey": "", "evidence": "loaded_module|mapped_file|jar_loaded" }] } ``` ### 2.3 ReplayManifest.json Schema ```json { "schema": "stella.replaymanifest.v1", "scanId": "uuid", "inputs": { "sbomDigest": "sha256:", "callGraphs": [{"language":"dotnet","digest":"sha256:"}], "runtimeEvidence": [{"digest":"sha256:"}], "concelierSnapshot": "sha256:", "excititorSnapshot": "sha256:", "policyDigest": "sha256:" } } ``` ### 2.4 ProofSpine Data Model ```csharp public sealed record ProofSpine( string SpineId, string ArtifactId, string VulnerabilityId, string PolicyProfileId, IReadOnlyList Segments, string Verdict, string VerdictReason, string RootHash, string ScanRunId, DateTimeOffset CreatedAt, string? SupersededBySpineId ); public sealed record ProofSegment( string SegmentId, string SegmentType, int Index, string InputHash, string ResultHash, string? PrevSegmentHash, DsseEnvelope Envelope, string ToolId, string ToolVersion, string Status ); ``` **Segment Types** - `SBOM_SLICE`: Component relevance - `MATCH`: SBOM-to-vuln mapping - `REACHABILITY`: Symbol reachability - `GUARD_ANALYSIS`: Config/feature flag gates - `RUNTIME_OBSERVATION`: Runtime evidence - `POLICY_EVAL`: Lattice decision ## 3. DATABASE SCHEMAS ### 3.1 Core Reachability Tables (Postgres) ```sql -- Scan tracking CREATE TABLE scan ( scan_id uuid PRIMARY KEY, created_at timestamptz, repo_uri text, commit_sha text, sbom_digest text, policy_digest text, status text ); CREATE INDEX idx_scan_cache ON scan(commit_sha, sbom_digest); -- Artifacts CREATE TABLE artifact ( artifact_id uuid PRIMARY KEY, scan_id uuid REFERENCES scan, artifact_key text, kind text, sha256 text, build_id text, purl text, UNIQUE(scan_id, artifact_key) ); -- Call graph nodes CREATE TABLE cg_node ( scan_id uuid, node_id text, artifact_key text, symbol_key text, visibility text, flags int, PRIMARY KEY(scan_id, node_id) ); -- Call graph edges CREATE TABLE cg_edge ( scan_id uuid, from_node_id text, to_node_id text, kind smallint, reason smallint, weight real, PRIMARY KEY(scan_id, from_node_id, to_node_id, kind, reason) ); CREATE INDEX idx_cg_edge_from ON cg_edge(scan_id, from_node_id); CREATE INDEX idx_cg_edge_to ON cg_edge(scan_id, to_node_id); -- Entrypoints CREATE TABLE entrypoint ( scan_id uuid, node_id text, kind text, framework text, route text, PRIMARY KEY(scan_id, node_id, kind, framework, route) ); -- Runtime samples CREATE TABLE runtime_sample ( scan_id uuid, collected_at timestamptz, env_hash text, sample_id bigserial PRIMARY KEY, timestamp timestamptz, pid int, thread_id int, frames text[], weight real ); -- Symbol-to-component mapping CREATE TABLE symbol_component_map ( scan_id uuid, node_id text, purl text, mapping_kind text, confidence real, PRIMARY KEY(scan_id, node_id, purl) ); -- Reachability results CREATE TABLE reachability_component ( scan_id uuid, purl text, status smallint, confidence real, why jsonb, evidence jsonb, PRIMARY KEY(scan_id, purl) ); CREATE TABLE reachability_finding ( scan_id uuid, cve_id text, purl text, status smallint, confidence real, why jsonb, evidence jsonb, PRIMARY KEY(scan_id, cve_id, purl) ); ``` ### 3.2 Unknowns Ranking Tables ```sql CREATE TABLE unknowns ( unknown_id uuid PRIMARY KEY, pkg_id text, pkg_version text, digest_anchor bytea, unknown_flags jsonb, popularity_p float, potential_e float, uncertainty_u float, centrality_c float, staleness_s float, score float, band text CHECK(band IN ('HOT','WARM','COLD')), graph_slice_hash bytea, evidence_set_hash bytea, normalization_trace jsonb, callgraph_attempt_hash bytea, created_at timestamptz, updated_at timestamptz ); CREATE TABLE deploy_refs ( pkg_id text, image_id text, env text, first_seen timestamptz, last_seen timestamptz ); CREATE TABLE graph_metrics ( pkg_id text PRIMARY KEY, degree_c float, betweenness_c float, last_calc_at timestamptz ); ``` ### 3.3 Proof Spine Tables ```sql CREATE TABLE proof_spines ( spine_id uuid PRIMARY KEY, artifact_id text, vuln_id text, policy_profile_id text, verdict text, verdict_reason text, root_hash text, scan_run_id uuid, created_at timestamptz, superseded_by_spine_id uuid, segment_count int ); CREATE INDEX idx_spine_lookup ON proof_spines(artifact_id, vuln_id, policy_profile_id); CREATE TABLE proof_segments ( segment_id uuid PRIMARY KEY, spine_id uuid REFERENCES proof_spines, idx int, segment_type text, input_hash text, result_hash text, prev_segment_hash text, envelope bytea, tool_id text, tool_version text, status text, created_at timestamptz ); ``` ## 4. ALGORITHMS ### 4.1 Reachability Status Classification **Status Values** - `UNREACHABLE`: No path from entrypoints - `POSSIBLY_REACHABLE`: Graph incomplete/dynamic behavior - `REACHABLE_STATIC`: Static path exists - `REACHABLE_PROVEN`: Runtime evidence confirms **Confidence Scoring (Deterministic)** Base scores: ``` UNREACHABLE → 0.05 POSSIBLY_REACHABLE → 0.35 REACHABLE_STATIC → 0.70 REACHABLE_PROVEN → 0.95 ``` Modifiers: ``` +0.10 if path uses only static edges -0.15 if path includes reflection_string|dynamic_import +0.10 if runtime evidence hits affected component -0.10 if NO_ENTRYPOINTS_DISCOVERED Clamp to [0, 1] ``` ### 4.2 Reachability Computation Algorithm ``` Inputs: - Call graph nodes/edges + entrypoints - Runtime evidence (optional) - SBOM with purls - Vulnerability facts (CVE ↔ purl/version) - VEX statements Steps: 1. Build adjacency list for cg_edge.kind in (static, heuristic) 2. Optional: Compress SCCs (Tarjan/Kosaraju) 3. Seed from entrypoints; if empty → mark POSSIBLY_REACHABLE with NO_ENTRYPOINTS_DISCOVERED 4. Traverse reachable nodes; track: - firstSeenFromEntrypoint[node] - pathWitness[node] 5. Map reachable nodes to purls via symbol_component_map: Priority order: a. Exact binary symbol → package metadata b. Assembly/jar/module to SBOM component (hash/purl) c. Heuristics: namespace prefixes, import paths, jar manifest 6. Runtime evidence upgrade: - Mark frame nodes as "executed" - Mint runtime edges: consecutive frames → runtime_minted - Upgrade to REACHABLE_PROVEN if executed node maps to affected purl 7. Compute confidence using deterministic formula ``` ### 4.3 Unknowns Ranking Algorithm **Score Formula** ``` Score = clamp01( wP·P + # Popularity impact wE·E + # Exploit consequence potential wU·U + # Uncertainty density wC·C + # Graph centrality wS·S # Evidence staleness ) ``` **Default Weights** ``` wP = 0.25 (deployment impact) wE = 0.25 (potential consequence) wU = 0.25 (uncertainty density) wC = 0.15 (graph centrality) wS = 0.10 (evidence staleness) ``` **Heuristics** ``` P = min(1, log10(1 + deployments)/log10(1 + 100)) U = sum of flags, capped at 1.0: +0.30 if no provenance anchor +0.25 if version_range +0.20 if conflicting_feeds +0.15 if missing_vector +0.10 if unreachable source advisory S = min(1, age_days / 14) ``` **Band Assignment** ``` Score ≥ 0.70 → HOT (immediate rescan + VEX escalation) 0.40 ≤ Score < 0.70 → WARM (scheduled rescan 12-72h) Score < 0.40 → COLD (weekly batch) ``` ### 4.4 Node ID Computation (.NET) **Primary (IL-based)** ``` nodeId = SHA256(MVID + ":" + metadataToken + ":" + arity + ":" + signatureShape) ``` **Fallback (source-only)** ``` nodeId = SHA256(projectPath + ":" + file + ":" + span + ":" + symbolDisplayString) ``` **External nodes** ``` nodeId = SHA256("ext:" + artifactKey + ":" + symbolKey) ``` ### 4.5 Canonical Symbol Key Format ``` {Namespace}.{TypeName}[`Arity][+Nested]::{MethodName}[`Arity]({ParamType1},{ParamType2},...) ``` Rules: - Use `System.*` full names for BCL types - Use `+` for nested types (metadata style) - Use backtick arity for generic type/method definitions - Arrays: `System.String[]` - Byref: `System.String&` ### 4.6 Reachability Cache Algorithm ```csharp public readonly record struct ReachKey( string AlgoSig, // e.g., "RTA@sha256:…" string InputsHash, // SBOM slice + policy + versions string CallPathHash // normalized query graph ); public sealed class ReachCache { private readonly ConcurrentDictionary>> _memo = new(); public Task GetOrComputeAsync( ReachKey key, Func> compute, CancellationToken ct) { var lazy = _memo.GetOrAdd(key, _ => new Lazy>( () => compute(), LazyThreadSafetyMode.ExecutionAndPublication)); return lazy.Value.ContinueWith(t => { if (t.IsCompletedSuccessfully) return t.Result; _memo.TryRemove(key, out _); throw t.Exception ?? new Exception("reachability failed"); }, ct); } } ``` **Operational rules** - Canonical everything: sort nodes/edges, normalize paths - Cache scope: per-scan, per-workspace, or per-feed version - TTL: 15-60 minutes or evict after pipeline completes - Max-entries cap - Concurrency: `Lazy>` coalesces duplicate in-flight calls ### 4.7 Proof Spine Construction ``` 1. Sbomer produces SBOM_SLICE segment, DSSE-signs it 2. Scanner produces MATCH segment 3. Reachability produces REACHABILITY segment 4. Guard Analyzer produces GUARD_ANALYSIS segment 5. Vexer evaluates lattice, produces POLICY_EVAL segment 6. ProofSpineBuilder: - Sorts segments by predetermined order - Chains PrevSegmentHash - Computes RootHash = hash(concat of segment hashes) - Stores ProofSpine with deterministic SpineId ``` ## 5. API CONTRACTS ### 5.1 Scanner Ingestion Endpoints ``` POST /api/scans Returns: scanId POST /api/scans/{scanId}/callgraphs Body: CallGraph.v1.json Header: Content-Digest (for idempotency) POST /api/scans/{scanId}/runtimeevidence Body: RuntimeEvidence.v1.json POST /api/scans/{scanId}/sbom Body: CycloneDX/SPDX POST /api/scans/{scanId}/compute-reachability Idempotent trigger ``` ### 5.2 Query Endpoints ``` GET /api/scans/{scanId}/reachability/components?purl=... GET /api/scans/{scanId}/reachability/findings?cve=... GET /api/scans/{scanId}/reachability/explain?cve=...&purl=... Returns: why[], path witness, sample refs ``` ### 5.3 Export Endpoints ``` GET /api/scans/{scanId}/exports/sarif GET /api/scans/{scanId}/exports/cdxr # CycloneDX reachability extension GET /api/scans/{scanId}/exports/openvex ``` ### 5.4 Proof Spine Endpoints ``` GET /graph/runtime/{podId}/lineage GET /graph/image/{digest}/vex GET /spines/{spineId} Returns: ProofSpine with all segments ``` ## 6. .NET IMPLEMENTATION PATTERNS ### 6.1 .NET Worker (Roslyn + IL) Required Features **Call Graph Extraction** - Direct invocation edges: `InvocationExpressionSyntax` - Object creation edges: constructors - Delegate invocation: record heuristic edge when target unresolved - Virtual/interface dispatch: record `virtual_call` edge to declared method - Async/await: connect logical caller → awaited method **Entrypoint Discovery** - `Program.Main` (classic) - ASP.NET Core: - Controllers: `[ApiController]`, route attributes, action methods - Minimal APIs: `MapGet/MapPost/MapMethods` patterns - gRPC: `MapGrpcService()` and service methods - Hosted services: `IHostedService`, `BackgroundService.ExecuteAsync` **Reflection and DI Heuristics** - `Type.GetType("…")`, `Assembly.GetType`, `GetMethod("…")`, `Invoke` - `services.AddTransient()` / `AddScoped` / `AddSingleton` - `Activator.CreateInstance`, `ServiceProvider.GetService` - Produce heuristic edges with `reason = di_binding` or `reflection_string` ### 6.2 NuGet Dependencies ```xml ``` ### 6.3 Roslyn IL Opcodes for Call Detection ``` Recognize as "calls": - call - callvirt - newobj - jmp - ldftn - ldvirtftn ``` ### 6.4 MSBuild Workspace Loading Pattern ```csharp using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.MSBuild; var ws = MSBuildWorkspace.Create(); var sln = await ws.OpenSolutionAsync(@"path\to.sln"); foreach (var proj in sln.Projects) foreach (var doc in proj.Documents) { var model = await doc.GetSemanticModelAsync(); var root = await doc.GetSyntaxRootAsync(); foreach (var node in root.DescendantNodes() .OfType()) { var sym = model.GetSymbolInfo(node).Symbol as IMethodSymbol; if (sym != null) { // record edge: caller -> sym } } } ``` ## 7. CONFIGURATION PATTERNS ### 7.1 Crypto Profiles ```csharp public interface ICryptoProfile { string ProfileId { get; } byte[] Sign(byte[] data, string keyId); bool Verify(byte[] data, byte[] signature, string keyId); } // Implementations: FipsProfile, GostProfile, EidasProfile, SmProfile, PqcProfile ``` **Profile Metadata** - Algorithm - Key ID - Profile name **Key Rotation** - Keys have validity intervals - Spines keep KeyId in each DSSE signature - Authority maintains trust table: which keys trusted for which SegmentType and time window ### 7.2 Offline Bundle Format **Required Contents** - SBOM + feeds + policy bundle + key material - Manifest with hashes of all contents - Replay manifest for deterministic rerun **Format** - Zip/tar + manifest - Each entry content-addressed ### 7.3 Cache Configuration (appsettings) ```json { "Scanner": { "Reach": { "Cache": { "MaxEntries": 10000, "TtlMinutes": 60, "EvictOnPipelineComplete": true } } } } ``` ## 8. METRICS AND THRESHOLDS ### 8.1 Performance SLOs (v1) ``` Medium service (100k LOC .NET) static graph: < 2 minutes Reachability compute: < 30 seconds Query GET finding: < 200ms p95 ``` ### 8.2 Quality Metrics **Proof verification** - % verified proofs - Proof verification failures - Proof age since last verification - % entries with valid inclusion proof (Rekor) **Unknowns triage** - Hot/Warm/Cold distribution - Rescan success rate after N attempts - Evidence staleness distribution **Drift detection** - Runtime edges not in static graph (above threshold → emit COVERAGE_DRIFT warning) ### 8.3 Benchmark Metrics (Moat) | Metric | Target | |--------|--------| | Time-to-evidence: SBOM → signed call-graph | < 5 minutes for 100k LOC service | | SBOM-diff false positive rate under dependency churn | < 5% change in reachability status for non-code changes | | Deterministic priority scoring under air-gap replay | Bit-identical results given same inputs | | Proof verification time | < 200ms p95 | ## 9. TESTING PATTERNS ### 9.1 Golden Corpus Requirements **Mandatory Test Cases** 1. Minimal ASP.NET controller with reachable endpoint → vulnerable lib call 2. Vulnerable lib present but never called → unreachable 3. Reflection-based activation → "possible" unless runtime proves 4. BackgroundService job case 5. Version range ambiguity 6. Mismatched epoch/backport 7. Missing CVSS vector 8. Conflicting severity vendor/NVD 9. Unanchored filesystem library **Assertions per Test** - Reachability status - At least one `why[]` reason - Deterministic confidence within ±0.01 - Segments with expected status (verified/partial/invalid) ### 9.2 Replay Manifest Tests Given manifest containing: - feed hashes - rules version - normalization logic - lattice rules Assert: ranking/reachability recomputes identically (byte-for-byte) ### 9.3 Signature Tampering Tests - Flip byte in DSSE payload → UI must show `invalid` - Mark key untrusted → segments show `untrusted-key` - Entire spine marked as compromised ## 10. SPECIFICATION COMPLIANCE ### 10.1 SBOM Standards **CycloneDX** - Version: 1.6 (ECMA-424) and 1.7 (current) - Media type: include version parameter - Ingest: validate against ECMA-424/1.7 schemas **SPDX** - Version: 3.0.1 - Validate against canonical spec **Ingestion Rules** - Accept only `*.cdx.json` and `*.spdx.json` - Hard fail others - Store: raw bytes + parsed form + normalized canonical form ### 10.2 VEX Standards **OpenVEX** - Minimal, SBOM-agnostic - Predicate type: `https://openvex.dev/ns/v0.2.0` **CSAF VEX** - Alternative format for interoperability **Required VEX Fields** - status: `not_affected|affected|fixed|under_investigation` - justification - timestamp - author - link to supporting evidence ### 10.3 Attestation Standards **DSSE (Dead Simple Signing Envelope)** - Use for all signed artifacts - Payload: canonical JSON - Envelope: signature + key metadata + profile **in-toto** - Statement structure - Predicate types: - SBOM: `https://spdx.dev/Document` - VEX: OpenVEX predicate URI - Custom: reachability predicates **Cosign Integration** ```bash cosign attest --yes --type https://spdx.dev/Document \ --predicate sbom.spdx.json \ --key cosign.key \ "${IMAGE_DIGEST}" ``` ### 10.4 Rekor Integration **CLI Verification** ```bash rekor-cli verify --rekor_server https://rekor.sigstore.dev \ --signature artifact.sig \ --public-key cosign.pub \ --artifact artifact.bin ``` **Persistence per Entry** - Rekor UUID - Log index - Integrated time - Inclusion proof data ## 11. CLI COMMANDS ### 11.1 Worker CLI ```bash # Artifacts-first scan stella-worker-dotnet scan \ --scanKey 00000000-0000-0000-0000-000000000000 \ --assemblies ./artifacts/bin/Release \ --out ./callgraph.json # Build-and-scan stella-worker-dotnet scan \ --scanKey ... \ --sln ./src/MySolution.sln \ --configuration Release \ --tfm net10.0 \ --buildMode build \ --out ./callgraph.json # Upload to scanner.webservice stella-worker-dotnet scan \ --scanKey ... \ --assemblies ./artifacts/bin/Release \ --upload https://scanner/api/scans/{scanId}/callgraphs \ --apiKey $STELLA_API_KEY ``` ### 11.2 Replay CLI ```bash stellaops scan \ --replay-manifest \ --artifact \ --vuln \ --explain ``` ### 11.3 Reachability Query CLI ```bash stella scan graph --lang dotnet --sln path.sln --out graph.scc.json stella scan runtime --target pod/myservice --duration 30s --out stacks.json stella reachability join \ --graph graph.scc.json \ --runtime stacks.json \ --sbom bom.cdx.json \ --out reach.cdxr.json ``` ## 12. DEVELOPER CHECKLIST ### 12.1 Per-Feature Definition of Done For any feature touching scans, VEX, or evidence: - [ ] Deterministic: input manifest defined, canonicalization applied, golden fixture(s) added - [ ] Evidence: outputs DSSE-wrapped and linked - [ ] Reachability/Lattice: runs only in allowed services, records algorithm IDs - [ ] Crypto: calls through profile abstraction, tests for ≥2 profiles - [ ] Graph: lineage edges added, node/edge IDs stable and queryable - [ ] UX/API: API to retrieve structured evidence - [ ] Tests: unit + golden + integration test with full SBOM → scan → VEX chain ### 12.2 Determinism Checklist - [ ] Input manifest type defined and versioned - [ ] Canonicalization applied before hashing/signing - [ ] Output stored with `inputsDigest` and `algoDigest` - [ ] At least one golden fixture proves determinism - [ ] No environment variables in core algorithm - [ ] No machine-local files - [ ] No system clock inside algorithms ### 12.3 Reachability Implementation Checklist - [ ] Reachability algorithms only in Scanner.WebService - [ ] Cache lazy and keyed by deterministic inputs - [ ] Output includes explicit evidence pointers - [ ] UI endpoints expose reachability state in structured form - [ ] All modifiers recorded in `why[]` ### 12.4 Crypto-Sovereign Checklist - [ ] No direct crypto calls in feature code; only via profile layer - [ ] All attestations carry algorithm + key id + profile - [ ] Offline bundle type exists for this workflow - [ ] Tests for at least 2 different crypto profiles ### 12.5 Policy/Lattice Checklist - [ ] Facts and policies serialized separately - [ ] Lattice code in allowed services only - [ ] Merge strategies named and versioned - [ ] Artifacts record which lattice algorithm used ### 12.6 Proof-Linked VEX Checklist - [ ] VEX schema includes pointers to all upstream artifacts (sbomDigest, scanId, reachMapDigest, policyDigest, signerKeyId) - [ ] No duplication of SBOM/scan content inside VEX - [ ] DSSE used as standard envelope type ### 12.7 Unknowns Triage Checklist - [ ] Persist all traces for deterministic replay - [ ] Ranking depends only on manifest-declared parameters - [ ] All uncertainty factors are explicit flags - [ ] Scoring reproducible under identical inputs - [ ] Scheduler decision table deterministic and tested - [ ] API exposes full reasoning --- **Document Version**: 1.0 **Target Platform**: .NET 10, PostgreSQL ≥16, Angular v17