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


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 docs/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:

6.0.1 Core Identifiers

IdentifierFormatDescription
symbol_idsym:{lang}:{base64url}Canonical function identity (SHA-256 of tuple)
code_idcode:{lang}:{base64url}Identity for stripped/name-less code blocks
graph_hashblake3:{hex}Content-addressable graph identity
fact.digestsha256:{hex}Canonical reachability fact digest

6.0.2 Lattice States

Policy gates operate on the 8-state reachability lattice:

StateCodePolicy Treatment
UnknownUBlock not_affected, allow under_investigation
StaticallyReachableSRAllow affected, block not_affected
StaticallyUnreachableSULow-confidence not_affected allowed
RuntimeObservedROaffected required
RuntimeUnobservedRUMedium-confidence not_affected allowed
ConfirmedReachableCRaffected required, not_affected blocked
ConfirmedUnreachableCUnot_affected allowed
ContestedXunder_investigation required

6.0.3 Evidence Block Schema

When Policy findings include reachability evidence, the following structure is used:

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

The snippet below is illustrative pseudo-policy showing how reachability inputs gate VEX treatment. Authoritative policy is authored in stella-dsl@1 — see the DSL guide.

# 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:

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.

6.6 Orchestrator Job Producer Runtime

The engine-owned orchestration surface is exposed directly under /policy/*. Unlike the stateless batch evaluator below, this path persists orchestrator and worker state in Policy storage.

POST /policy/orchestrator/jobs
Scopes: policy:run

Request

{
  "tenantId": "acme",
  "policyVersion": "sha256:1fb2...",
  "items": [
    {
      "findingId": "acme::artifact-1::CVE-2024-12345",
      "eventId": "5d1fcc61-6903-42ef-9285-7f4d3d8f7f69",
      "event": { "...": "canonical ledger payload" }
    }
  ]
}

Response 200

{
  "jobId": "01HSR2M1D0BP7V2QY3QGJ31Z8V",
  "status": "queued",
  "requestedAt": "2026-04-15T08:30:00Z",
  "completedAt": null,
  "resultHash": null
}

Related endpoints:

POST /policy/orchestrator/jobs/preview
GET /policy/orchestrator/jobs/{jobId}
GET /policy/worker/jobs/{jobId}

Runtime contract:


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-StellaOps-TenantId, 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-StellaOps-TenantId 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:


8 · Effective Findings APIs

8.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=="
}

8.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


10 · Compliance Checklist


Last updated: 2025-10-26 (Sprint 20).