# 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 `) 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).*