Findings gateway contract and historical Web V design
Current serving contract (source verified 2026-10-07)
The serving owner is src/Findings/StellaOps.Findings.WebService. At commit 34b153ca04145478a0a87faf14cb5bf4002689e9, its Program.cs composes the ledger and scoring groups from src/Findings/__Libraries/StellaOps.Findings.Ledger.Application/Endpoints/. Current source routes include /api/findings/v1/ledger/* and /api/findings/v1/scoring/*; use the scoring reference and Findings architecture for the mapped owner contracts.
Ledger write/export and scoring read/write/admin policies share the configured findings:ledger:Authority:RequiredScopes array, defaulting to vuln:operate. Runtime trace reads/writes have separate findings:read / findings:write policies; those runtime scopes do not replace ledger-plane authorization. Tenant resolution follows the validated token/envelope and RequireTenant(); the legacy tenant header does not select a tenant. See API conventions.
The Web V workflow route /ledger/findings/{findingId}/actions, its proposed idempotency TTL, retry budget, ETags and error envelope below remain an unimplemented design. They are not an operator or client contract. Recheck the current host composition and endpoint source before treating any proposed behavior as available. This review makes no live deployment claim.
Historical Web V proxy proposal (2025-12-01; withdrawn guidance 2026-10-07)
The following predecessor design is retained for design history. Its examples, authentication assumptions and delivery guarantees are not supported current instructions; the source-backed contract above takes precedence.
This contract defines how the Stella Ops Web gateway forwards vulnerability-finding workflow actions (open, ack, close, reopen, export) to the Findings Ledger service. It covers the required headers, idempotency and correlation semantics, retry and offline-kit behavior, and the deterministic error envelope clients can rely on.
Status
- Final v1.0 (2025-12-01); validated with Findings Ledger Guild for Sprint 0216.
- Design draft for the gateway proxy surface — partially implemented. The
POST /ledger/findings/{findingId}/actionsgateway path below is a forward Web V contract; the Findings service today exposes its routes under/api/findings/v1/*(e.g./api/findings/v1/ledger/ledger,/api/findings/v1/ledger/{findingId}/...,/api/findings/v1/scoring) — seesrc/Findings/__Libraries/StellaOps.Findings.Ledger.Application/Endpoints/. The scopes have been reconciled against the canonical catalog (StellaOps.Auth.Abstractions/StellaOpsScopes.cs): the service usesfindings:read/findings:write. There is noledger:read/ledger:writescope, and theaud=stellaops-ledgeraudience is illustrative.
Purpose
- Gateway → Findings Ledger forwarding for vulnerability workflow actions (
open,ack,close,reopen,export). - Idempotency and correlation headers, with retry/backoff defaults for offline and offline-kit-safe behavior.
Required Headers
| Name | Requirement | Notes |
|---|---|---|
Authorization: Bearer <jwt> | Required | RS256/ES256 service token. The Findings Ledger service enforces findings:read (reads) and findings:write (writes/appends); see StellaOps.Findings.Ledger.WebService/Program.cs. (There is no ledger:read/ledger:write scope; the aud=stellaops-ledger audience shown elsewhere is illustrative.) |
X-StellaOps-TenantId | Required | Tenant slug/UUID (must align with tenant-auth contract). |
X-StellaOps-Project | Conditional | Required for project-scoped findings. (Legacy alias X-Stella-Project is accepted only when the gateway is started with EnableLegacyHeaders=true.) |
X-Idempotency-Key | Required on POST/PUT | Deterministic BLAKE3-256(base64url(tenant + route + canonical_body)); 44 chars. TTL: 24h. |
X-Correlation-Id | Required | UUID/ULID stable across gateway → ledger → notifier; echoed by responses. |
Content-Type | Required | application/json. |
If-Match | Optional | When present, ledger enforces optimistic concurrency using the last ETag value. |
Behavior
- Delivery semantics: gateway is at-least-once; Findings Ledger guarantees exactly-once per
X-Idempotency-Keywithin 24h TTL. - Retry/backoff (gateway): base delay 500 ms, factor 2, jitter ±20%, max 3 attempts, cap total wait 10 s. Offline kits persist NDJSON (headers+body) and replay on next sync window.
- Timeout: 5 s per attempt; timeout →
ERR_LEDGER_TIMEOUT. - Concurrency: ledger returns
ETagfor each workflow record; gateway includesIf-Matchon retries when available. Mismatch → 409 +ERR_LEDGER_CONFLICT. - Error mapping (deterministic envelope with
trace_id+ echoedX-Correlation-Id):- 400 →
ERR_LEDGER_BAD_REQUEST(propagatedetails). - 404 →
ERR_LEDGER_NOT_FOUND. - 409 →
ERR_LEDGER_CONFLICT. - 429/503 →
ERR_LEDGER_RETRY. - 500+ →
ERR_LEDGER_UPSTREAM.
- 400 →
Payload Contract
{
"action": "ack", // open|ack|close|reopen|export
"finding_id": "f-7e12d9",
"reason_code": "triage_accept",
"comment": "Owner acknowledged risk and started fix",
"attachments": [ { "name": "triage.pdf", "digest": "sha256-..." } ],
"actor": { "subject": "svc-console", "type": "service" },
"metadata": { "policy_version": "2025.11.0", "vex_statement_id": "vex-123" }
}
- Body must be canonical JSON (sorted keys) before hashing for
X-Idempotency-Key. - Maximum size: 64 KiB; larger bodies rejected with 413.
Example Request
curl -X POST https://gateway.stellaops.local/ledger/findings/f-7e12d9/actions \
-H "Authorization: Bearer $LEDGER_TOKEN" \
-H "X-StellaOps-TenantId: acme-tenant" \
-H "X-Correlation-Id: 01HXYZABCD1234567890" \
-H "X-Idempotency-Key: 3cV1..." \
-H "Content-Type: application/json" \
--data '{"action":"ack","finding_id":"f-7e12d9","reason_code":"triage_accept","actor":{"subject":"svc-console","type":"service"}}'
Example Response
{
"status": "accepted",
"ledger_event_id": "ledg-01HF7T4X6E4S7A6PK8",
"etag": "W/\"01-2a9c\"",
"trace_id": "01HXYZABCD1234567890",
"correlation_id": "01HXYZABCD1234567890"
}
Open Questions
- Confirm ledger idempotency TTL (proposed 24h) and whether ETag is returned for optimistic concurrency.
- Confirm expected payload schemas for each workflow action (open/ack/close/export).
- Confirm whether ledger enforces ordering per
tenant_id.
