ReachGraph → Policy Gate Integration
Audience: Scanner, ReachGraph, and Release Orchestrator engineers wiring reachability evidence into release promotion gates. Shipped in: Sprint
SPRINT_20260526_019_Scanner_reachability_live_forcing_function.
This guide describes how reachability evidence produced by the Scanner Worker flows into the Release Orchestrator’s reachability promotion gate.
The Scanner has produced reachability evidence locally since Sprint 014, but Sprint 019 wired the two cross-module HTTP hops that make a release policy gate actually consume that evidence — closing out the killer-feature work.
Sequence diagram
+----------------------+ +-------------------------+ +---------------------------+
| Scanner.Worker | | ReachGraph WebService | | ReleaseOrchestrator.WebApi |
| (emit-reports | | POST /v1/reachgraphs | | POST /api/v1/release- |
| stage executor) | | | | orchestrator/evidence|
+----------+-----------+ +------------+-------------+ +-------------+-------------+
| | |
| 1. Reachability stages emit | |
| ReachabilityUnionGraph in | |
| context.Analysis (existing) | |
| | |
| 2. (existing) Publish union+rich | |
| graph to CAS | |
| | |
| 3. Sprint 019 RCH-019-002: | |
| HttpReachGraphPublisher | |
| POST /v1/reachgraphs | |
| X-Tenant-ID: <tenant> | |
| | |
|--------- ReachGraphMinimal ----->| |
| | 4. Server computes BLAKE3 digest, |
| | persists subgraph under tenant |
|<------ 201 Created (digest) -----| |
| | |
| 5. Stamp digest onto | |
| context.Analysis | |
| (ScanAnalysisKeys | |
| .ReachGraphDigest) | |
| | |
| 6. Sprint 019 RCH-019-003: | |
| HttpReleaseEvidencePublisher | |
| canonicalise | |
| DependencyReachabilityReport | |
| once -> sha256 those bytes -> | |
| base64 same bytes (no re- | |
| canonicalisation between hash | |
| and encode) | |
| | |
| POST /api/v1/release-orchestrator/evidence |
| X-StellaOps-TenantId: <tenant> |
| Authorization: Bearer <release:write token> |
| { releaseId, type=stellaops.reachability-report@v1, |
| hash, algorithm, rawContentBase64, description= |
| "ReachGraph digest: <blake3:...>" } |
|----------------------------------------------------------------------> |
| | |
| | 7. Server SHA-256s the bytes again, |
| | compares with client hash. If |
| | mismatch returns 400 |
| | evidence_hash_mismatch. If equal:|
| | persist row in release_evidence |
| | |
|<-------------------------- 201 Created (evidenceId, hash) -------------|
| | |
... time passes, an operator requests a promotion ...
+--------------------------+ +----------------------------------------+
| ReleaseEndpoints. | | CompositeGateDecisionEvaluator |
| RequestPromotion |----->| (Sprint 019 RCH-019-005) |
+--------------------------+ | |
| 1. PolicyDrivenGateDecisionEvaluator |
| contributes approval-policy + |
| optional policy-engine rows |
| |
| 2. ReachabilityGateEvaluator |
| (Sprint 019 RCH-019-006) |
| reads release_evidence rows of |
| type=stellaops.reachability- |
| report@v1, picks latest, applies |
| thresholds, emits 1 |
| reachability-gate row |
| |
| 3. Aggregate into GateDecisionDto |
+-------------------+--------------------+
|
v
release_orchestrator.gate_decisions row written
approval row mutated per outcome
promotion proceeds or blocks
Failure modes
| Failure | Behaviour |
|---|---|
| ReachGraph WebService unreachable | HttpReachGraphPublisher logs warning + continues. Scan succeeds; CAS row is the only reachability artefact. Release-evidence publish still runs (just without the ReachGraph digest: description pointer). |
| Release Orchestrator unreachable | HttpReleaseEvidencePublisher logs warning + continues. Scan succeeds; gate falls through to no-witness branch at next promotion. |
evidence_hash_mismatch (server-side hash != client-side hash) | HttpReleaseEvidencePublisher throws HttpRequestException with the original 400 status. Stage executor logs warning and continues. Indicates a bug in the publisher’s serialise-once invariant — would be caught by HttpReleaseEvidencePublisherTests.PublishAsync_UploadsBytesEqualToHashedBytes_AndPostsToCanonicalEndpoint. |
Scan carries no release.id metadata | Release-evidence publish skipped silently (logged Debug). ReachGraph publish still runs (it has no release dependency). Gate falls through to no-witness. |
ReachabilityGateOptions.Enabled = false | Gate emits no row at all. gateResults[] shape is identical to the pre-019 shape. |
Configuration sketch (Worker side)
Scanner:
Worker:
Reachability:
Enabled: true
PublishToReachGraph: true
ReachGraphBaseUrl: http://reachgraph:8080
PublishReleaseEvidence: true
ReleaseOrchestratorBaseUrl: http://release-orchestrator:8080
The scan submission must carry release.id (and ideally component.id) in its metadata — see ScanMetadataKeys.ReleaseId (Sprint 019 RCH-019-001). Without it, the release-evidence publisher skips and only the ReachGraph row is created.
See also
- Gate spec: docs/modules/release-orchestrator/gates/reachability-gate.md
- Scanner emit point: docs/modules/scanner/callpaths-build-stage-sbom.md
- Composite gate evaluator refactor:
src/ReleaseOrchestrator/__Apps/StellaOps.ReleaseOrchestrator.WebApi/Services/CompositeGateDecisionEvaluator.cs.
