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

FailureBehaviour
ReachGraph WebService unreachableHttpReachGraphPublisher 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 unreachableHttpReleaseEvidencePublisher 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 metadataRelease-evidence publish skipped silently (logged Debug). ReachGraph publish still runs (it has no release dependency). Gate falls through to no-witness.
ReachabilityGateOptions.Enabled = falseGate 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