Files
git.stella-ops.org/docs/api/policy.md
StellaOps Bot 6e45066e37
Some checks failed
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
up
2025-12-13 09:37:15 +02:00

559 lines
15 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Policy Engine REST API
> **Audience:** Backend integrators, platform operators, and CI engineers invoking Policy Engine services programmatically.
> **Base URL:** `/api/policy/*` (internal gateway route) requires OAuth 2.0 bearer token issued by Authority with scopes listed below.
This document is the canonical reference for the Policy Engine REST surface described in Epic2 (Policy Engine v2). Use it alongside the [DSL](../policy/dsl.md), [Lifecycle](../policy/lifecycle.md), and [Runs](../policy/runs.md) guides for end-to-end implementations.
---
## 1·Authentication & Headers
- **Auth:** Bearer tokens (`Authorization: Bearer <token>`) with the following scopes as applicable:
- `policy:read`, `policy:author`, `policy:review`, `policy:approve`, `policy:operate`, `policy:run`, `policy:activate`, `policy:archive`, `policy:simulate`, `policy:runs`
- `findings:read` (for effective findings APIs)
- `effective:write` (service identity only; not exposed to clients)
- **Service identity:** Authority marks the Policy Engine client with `properties.serviceIdentity: policy-engine`. Tokens missing this marker cannot obtain `effective:write`.
- **Tenant:** Supply tenant context via `X-Stella-Tenant`. Tokens without multi-tenant claims default to `default`.
- **Idempotency:** For mutating endpoints, include `Idempotency-Key` (UUID). Retries with same key return original result.
- **Content type:** All request/response bodies are `application/json; charset=utf-8` unless otherwise noted.
---
## 2·Error Model
All errors use HTTP semantics plus a structured payload:
```json
{
"code": "ERR_POL_001",
"message": "Policy syntax error",
"details": [
{"path": "rules[0].when", "error": "Unknown function foo()"}
],
"traceId": "01HDV1C4E9Z4T5G6H7J8",
"timestamp": "2025-10-26T14:07:03Z"
}
```
| Code | Meaning | Notes |
|------|---------|-------|
| `ERR_POL_001` | Policy syntax/compile error | Returned by `compile`, `submit`, `simulate`, `run` when DSL invalid. |
| `ERR_POL_002` | Policy not approved | Attempted to run or activate unapproved version. |
| `ERR_POL_003` | Missing inputs | Downstream service unavailable (Concelier/Excititor/SBOM). |
| `ERR_POL_004` | Determinism violation | Illegal API usage (wall-clock, RNG). Triggers incident mode. |
| `ERR_POL_005` | Unauthorized materialisation | Identity lacks `effective:write`. |
| `ERR_POL_006` | Run canceled or timed out | Includes cancellation metadata. |
---
## 3·Policy Management
### 3.1 Create Draft
```
POST /api/policy/policies
Scopes: policy:author
```
**Request**
```json
{
"policyId": "P-7",
"name": "Default Org Policy",
"description": "Baseline severity + VEX precedence",
"dsl": {
"syntax": "stella-dsl@1",
"source": "policy \"Default Org Policy\" syntax \"stella-dsl@1\" { ... }"
},
"tags": ["baseline","vex"]
}
```
**Response 201**
```json
{
"policyId": "P-7",
"version": 1,
"status": "draft",
"digest": "sha256:7e1d…",
"createdBy": "user:ali",
"createdAt": "2025-10-26T13:40:00Z"
}
```
### 3.2 List Policies
```
GET /api/policy/policies?status=approved&tenant=default&page=1&pageSize=25
Scopes: policy:read
```
Returns paginated list with `X-Total-Count` header.
### 3.3 Fetch Version
```
GET /api/policy/policies/{policyId}/versions/{version}
Scopes: policy:read
```
Returns full DSL, metadata, provenance, simulation artefact references.
### 3.4 Update Draft Version
```
PUT /api/policy/policies/{policyId}/versions/{version}
Scopes: policy:author
```
Body identical to create. Only permitted while `status=draft`.
---
## 4·Lifecycle Transitions
### 4.1 Submit for Review
```
POST /api/policy/policies/{policyId}/versions/{version}:submit
Scopes: policy:author
```
**Request**
```json
{
"reviewers": ["user:kay","group:sec-reviewers"],
"notes": "Simulated on golden SBOM set (diff attached)",
"simulationArtifacts": [
"blob://policy/P-7/v3/simulations/2025-10-26.json"
]
}
```
**Response 202** submission recorded. `Location` header points to review resource.
### 4.2 Review Feedback
```
POST /api/policy/policies/{policyId}/versions/{version}/reviews
Scopes: policy:review
```
**Request**
```json
{
"decision": "approve", // approve | request_changes | comment
"note": "Looks good, ensure incident playbook covers reachability data.",
"blocking": false
}
```
### 4.3 Approve
```
POST /api/policy/policies/{policyId}/versions/{version}:approve
Scopes: policy:approve
```
Body requires approval note and confirmation of compliance gates:
```json
{
"note": "All simulations and determinism checks passed.",
"acknowledgeDeterminism": true,
"acknowledgeSimulation": true
}
```
### 4.4 Activate
```
POST /api/policy/policies/{policyId}/versions/{version}:activate
Scopes: policy:activate, policy:run
```
Marks version as active for tenant; triggers optional immediate full run (`"runNow": true`).
### 4.5 Archive
```
POST /api/policy/policies/{policyId}/versions/{version}:archive
Scopes: policy:archive
```
Request includes `reason` and optional `incidentId`.
---
## 5·Compilation & Validation
### 5.1 Compile
```
POST /api/policy/policies/{policyId}/versions/{version}:compile
Scopes: policy:author
```
**Response 200**
```json
{
"digest": "sha256:7e1d…",
"warnings": [],
"rules": {
"count": 24,
"actions": {
"block": 5,
"warn": 4,
"ignore": 3,
"requireVex": 2
}
}
}
```
### 5.2 Lint / Simulate Quick Check
```
POST /api/policy/policies/{policyId}/lint
Scopes: policy:author
```
Slim wrapper used by CLI; returns 204 on success or `ERR_POL_001` payload.
---
## 6·Run & Simulation APIs
> Schema reference: canonical policy run request/status/diff payloads ship with the Scheduler Models guide (`src/Scheduler/__Libraries/StellaOps.Scheduler.Models/docs/SCHED-MODELS-20-001-POLICY-RUNS.md`) and JSON fixtures under `samples/api/scheduler/policy-*.json`.
### 6.0 Reachability evidence inputs (Signals)
Policy Engine evaluations may be enriched with reachability facts produced by Signals. These facts are expected to be:
- **Deterministic:** referenced by `metadata.fact.digest` (sha256) and versioned via `metadata.fact.version`.
- **Evidence-linked:** per-target states include `path[]`, `evidence.static.graphHash`, `evidence.runtime.hitCount`, and CAS/DSSE pointers.
#### 6.0.1 Core Identifiers
| Identifier | Format | Description |
|------------|--------|-------------|
| `symbol_id` | `sym:{lang}:{base64url}` | Canonical function identity (SHA-256 of tuple) |
| `code_id` | `code:{lang}:{base64url}` | Identity for stripped/name-less code blocks |
| `graph_hash` | `blake3:{hex}` | Content-addressable graph identity |
| `fact.digest` | `sha256:{hex}` | Canonical reachability fact digest |
#### 6.0.2 Lattice States
Policy gates operate on the 8-state reachability lattice:
| State | Code | Policy Treatment |
|-------|------|------------------|
| `Unknown` | `U` | Block `not_affected`, allow `under_investigation` |
| `StaticallyReachable` | `SR` | Allow `affected`, block `not_affected` |
| `StaticallyUnreachable` | `SU` | Low-confidence `not_affected` allowed |
| `RuntimeObserved` | `RO` | `affected` required |
| `RuntimeUnobserved` | `RU` | Medium-confidence `not_affected` allowed |
| `ConfirmedReachable` | `CR` | `affected` required, `not_affected` blocked |
| `ConfirmedUnreachable` | `CU` | `not_affected` allowed |
| `Contested` | `X` | `under_investigation` required |
#### 6.0.3 Evidence Block Schema
When Policy findings include reachability evidence, the following structure is used:
```json
{
"reachability": {
"state": "CR",
"confidence": 0.92,
"evidence": {
"graph_hash": "blake3:a1b2c3d4e5f6...",
"graph_cas_uri": "cas://reachability/graphs/a1b2c3d4e5f6...",
"dsse_uri": "cas://reachability/graphs/a1b2c3d4e5f6....dsse",
"path": [
{"symbol_id": "sym:java:...", "code_id": "code:java:...", "display": "main()"},
{"symbol_id": "sym:java:...", "code_id": "code:java:...", "display": "Logger.error()"}
],
"path_length": 2,
"runtime_hits": 47,
"fact_digest": "sha256:abc123...",
"fact_version": 3
}
}
}
```
#### 6.0.4 Policy Rule Example
```rego
# Allow not_affected only for confirmed unreachable with high confidence
allow_not_affected {
input.reachability.state == "CU"
input.reachability.confidence >= 0.85
input.reachability.evidence.fact_digest != ""
}
# Require affected for confirmed reachable
require_affected {
input.reachability.state == "CR"
}
# Contested states require investigation
require_investigation {
input.reachability.state == "X"
}
```
Signals contract & scoring model:
- `docs/api/signals/reachability-contract.md`
- `docs/reachability/lattice.md`
- `docs/reachability/function-level-evidence.md`
### 6.1 Trigger Run
```
POST /api/policy/policies/{policyId}/runs
Scopes: policy:run
```
**Request**
```json
{
"mode": "incremental", // full | incremental
"runId": "run:P-7:2025-10-26:auto", // optional idempotency key
"sbomSet": ["sbom:S-42","sbom:S-318"],
"env": {"exposure": "internet"},
"priority": "normal" // normal | high | emergency
}
```
**Response 202**
```json
{
"runId": "run:P-7:2025-10-26:auto",
"status": "queued",
"queuedAt": "2025-10-26T14:05:00Z"
}
```
### 6.2 Get Run Status
```
GET /api/policy/policies/{policyId}/runs/{runId}
Scopes: policy:runs
```
Includes status, stats, determinism hash, failure diagnostics.
### 6.3 List Runs
```
GET /api/policy/policies/{policyId}/runs?mode=incremental&status=failed&page=1&pageSize=20
Scopes: policy:runs
```
Supports filtering by `mode`, `status`, `from`/`to` timestamps, `tenant`.
### 6.4 Simulate
```
POST /api/policy/policies/{policyId}/simulate
Scopes: policy:simulate
```
**Request**
```json
{
"baseVersion": 3,
"candidateVersion": 4,
"sbomSet": ["sbom:S-42","sbom:S-318"],
"env": {"sealed": false},
"explain": true
}
```
**Response 200**
```json
{
"diff": {
"added": 12,
"removed": 8,
"unchanged": 657,
"bySeverity": {
"Critical": {"up": 1, "down": 0},
"High": {"up": 3, "down": 4}
}
},
"explainUri": "blob://policy/P-7/simulations/2025-10-26-4-vs-3.json"
}
```
### 6.5 Replay Run
```
POST /api/policy/policies/{policyId}/runs/{runId}:replay
Scopes: policy:runs, policy:simulate
```
Produces sealed bundle for determinism verification; returns location of bundle.
---
## 7·Batch Evaluation API
Deterministic evaluator for downstream services (Findings Ledger, replay tooling, offline exporters). Consumers submit ledger event payloads and receive policy verdicts with rationale lists; no state is persisted in Policy Engine.
```
POST /api/policy/eval/batch
Scopes: policy:simulate (service identities only)
Headers: X-Stella-Tenant, Idempotency-Key (optional)
```
**Request**
```jsonc
{
"tenantId": "acme",
"policyVersion": "sha256:1fb2…",
"items": [
{
"findingId": "acme::artifact-1::CVE-2024-12345",
"eventId": "5d1fcc61-6903-42ef-9285-7f4d3d8f7f69",
"event": { ... canonical ledger payload ... },
"currentProjection": {
"status": "triaged",
"severity": 3.4,
"labels": { "exposure": "runtime" },
"explainRef": "policy://explain/123",
"rationale": ["policy://explain/123"]
}
}
]
}
```
| Field | Description |
|-------|-------------|
| `tenantId` | Must match the `X-Stella-Tenant` header. |
| `policyVersion` | Deterministic policy digest (for example `sha256:<hex>`). Required for caching. |
| `event` | Canonical ledger event payload (`ledger_events.event_body`). |
| `currentProjection` | Optional snapshot of the existing finding projection. Null values are ignored. |
**Response 200**
```jsonc
{
"items": [
{
"findingId": "acme::artifact-1::CVE-2024-12345",
"status": "affected",
"severity": 7.5,
"labels": { "exposure": "runtime" },
"explainRef": "policy://explain/123",
"rationale": [
"policy://explain/123",
"policy://remediation/321"
]
}
],
"cost": {
"units": 1,
"budgetRemaining": 999
}
}
```
Notes:
- Items that cannot be evaluated return `status: null` with an `error` object. Callers should fall back to inline evaluation.
- Policy Engine enforces per-tenant cost budgets; batches that exceed the remaining allowance receive `429 Too Many Requests`.
- Responses are deterministic; clients may cache results by `(tenantId, policyVersion, eventHash, projectionHash)` to support replay/offline parity.
- Standard `ERR_POL_*` payloads surface errors; `ERR_POL_006` indicates the evaluator aborted the batch.
---
## 8·Effective Findings APIs
### 7.1 List Findings
```
GET /api/policy/findings/{policyId}?sbomId=S-42&status=affected&severity=High,Critical&page=1&pageSize=100
Scopes: findings:read
```
Response includes cursor-based pagination:
```json
{
"items": [
{
"findingId": "P-7:S-42:pkg:npm/lodash@4.17.21:CVE-2021-23337",
"status": "affected",
"severity": {"normalized": "High", "score": 7.5},
"sbomId": "sbom:S-42",
"advisoryIds": ["CVE-2021-23337"],
"vex": {"winningStatementId": "VendorX-123"},
"policyVersion": 4,
"updatedAt": "2025-10-26T14:06:01Z"
}
],
"nextCursor": "eyJwYWdlIjoxfQ=="
}
```
### 7.2 Fetch Explain Trace
```
GET /api/policy/findings/{policyId}/{findingId}/explain?mode=verbose
Scopes: findings:read
```
Returns rule hit sequence:
```json
{
"findingId": "P-7:S-42:pkg:npm/lodash@4.17.21:CVE-2021-23337",
"policyVersion": 4,
"steps": [
{"rule": "vex_precedence", "status": "not_affected", "inputs": {"statementId": "VendorX-123"}},
{"rule": "severity_baseline", "severity": {"normalized": "Low", "score": 3.4}}
],
"sealedHints": [{"message": "Using cached EPSS percentile from bundle 2025-10-20"}]
}
```
---
## 9·Events & Webhooks
- `policy.run.completed` emitted with `runId`, `policyId`, `mode`, `stats`, `determinismHash`.
- `policy.run.failed` includes error code, retry count, guidance.
- `policy.lifecycle.*` mirrored from lifecycle APIs (see [Lifecycle guide](../policy/lifecycle.md)).
- Webhook registration occurs via `/api/policy/webhooks` (future work, reserved). For now, integrate with Notifier streams documented in `/docs/notifications/*`.
---
## 10·Compliance Checklist
- [ ] **Scopes enforced:** Endpoint access requires correct Authority scope mapping (see `/src/Authority/StellaOps.Authority/TASKS.md`).
- [ ] **Schemas current:** JSON examples align with Scheduler Models (`SCHED-MODELS-20-001`) and Policy Engine DTOs; update when contracts change.
- [ ] **Error codes mapped:** `ERR_POL_*` table reflects implementation and CI tests cover edge cases.
- [ ] **Pagination documented:** List endpoints specify page/size and cursor semantics; responses include `X-Total-Count` or `nextCursor`.
- [ ] **Idempotency described:** Mutating endpoints mandate `Idempotency-Key`.
- [ ] **Offline parity noted:** Simulate/run endpoints explain `--sealed` behaviour and bundle generation.
- [ ] **Cross-links added:** References to lifecycle, runs, DSL, and observability docs verified.
---
*Last updated: 2025-10-26 (Sprint 20).*