403 lines
10 KiB
Markdown
403 lines
10 KiB
Markdown
# 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 Epic 2 (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.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 · 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"}]
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 8 · 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/*`.
|
||
|
||
---
|
||
|
||
## 9 · 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).*
|