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, Lifecycle, and Runs 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:

{
  "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"
}
CodeMeaningNotes
ERR_POL_001Policy syntax/compile errorReturned by compile, submit, simulate, run when DSL invalid.
ERR_POL_002Policy not approvedAttempted to run or activate unapproved version.
ERR_POL_003Missing inputsDownstream service unavailable (Concelier/Excititor/SBOM).
ERR_POL_004Determinism violationIllegal API usage (wall-clock, RNG). Triggers incident mode.
ERR_POL_005Unauthorized materialisationIdentity lacks effective:write.
ERR_POL_006Run canceled or timed outIncludes cancellation metadata.

3 · Policy Management

3.1 Create Draft

POST /api/policy/policies
Scopes: policy:author

Request

{
  "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

{
  "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

{
  "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

{
  "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:

{
  "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

{
  "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

{
  "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

{
  "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

{
  "baseVersion": 3,
  "candidateVersion": 4,
  "sbomSet": ["sbom:S-42","sbom:S-318"],
  "env": {"sealed": false},
  "explain": true
}

Response 200

{
  "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

{
  "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"]
      }
    }
  ]
}
FieldDescription
tenantIdMust match the X-Stella-Tenant header.
policyVersionDeterministic policy digest (for example sha256:<hex>). Required for caching.
eventCanonical ledger event payload (ledger_events.event_body).
currentProjectionOptional snapshot of the existing finding projection. Null values are ignored.

Response 200

{
  "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:

{
  "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:

{
  "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).
  • 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).