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

@@ -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"
}