Policy API Reference — Pack Lifecycle

Audience: engineers and operators calling the registered Policy Engine pack/revision API.

Verified against source: 00f64ca4d165fc54e7c291795b65326869b7627d (2026-08-31), Program.MapPolicyPacks, PolicyPackEndpoints and PostgresPolicyPackRepository. Full Policy tests passed1916/1916; this is not live acceptance. See the lifecycle source re-verification commands.

Registered owner API

Paths below are owner-service paths. Derive any gateway prefix from its registered routes; do not prepend the former /api/v1/policies design base.

Method and pathScopeContract
POST /api/policy/packspolicy:editpackId, optional displayName; creates the tenant pack container.
GET /api/policy/packspolicy:readSeparate tenant pack summaries, each retaining its own display metadata and revision numbers even when names differ only by case; not a paginated full-review history.
POST /api/policy/packs/{packId}/revisionspolicy:editExplicit positive version, or omit it to allocate the next revision and use the returned version; initialStatus (Draft or Approved), requiresTwoPersonApproval.
POST /api/policy/packs/{packId}/revisions/{version}/bundlepolicy:editPositive route revision required; compile/store dsl: { syntax, source }, optional signingKeyId and provenance. A positive revision need not be pre-created.
POST /api/policy/packs/{packId}/revisions/{version}/evaluatepolicy:readBody packId must exactly match the route, including case, and version must match; provide the intended subject. A mismatch returns 400 before bundle lookup or evaluation.
POST /api/policy/packs/{packId}/revisions/{version}:activatepolicy:activateBody with optional comment; Approved state and persisted actor threshold checked.
GET /policy/capabilitiespolicy:readsimulation.available, shadowMode.available; source defaults false.

The pack-list parent separation was verified at source 6087e7444f247d567b4b88fa8214b4e094999e93 with PolicyPackVersionAssignedIdentityTests.ListPacks_SeparatesStoredParentsWithSameRevisionAndTenantBoundary. Re-run that method through tools/scripts/test-targeted-xunit.ps1 -BuildProjectReferences in the Policy Engine test project. This scoped check does not re-verify the other endpoint contracts or establish live acceptance.

Use explicit Draft metadata before storing a bundle; omitting initialStatus defaults to Approved. Creating metadata first also resolves the configured activation requirement; upserting an existing revision does not change its stored two-person setting. Follow the lifecycle procedure, including independent governance evidence.

Automatic revision allocation starts at 1 when no positive revision exists, including a parent with zero or negative imported revisions. Otherwise it returns the highest stored revision plus one. Existing imported values are retained; this allocation rule does not add a storage-wide positivity restriction. Use the returned version in the bundle route.

The assigned pack identity and positive allocation floor were verified at source b5c85b895aa7d44d43f10dee248bcde697c79f61. Re-verify with pwsh ./tools/scripts/test-targeted-xunit.ps1 -Project src/Policy/__Tests/StellaOps.Policy.Engine.Tests/StellaOps.Policy.Engine.Tests.csproj -Class "*PolicyPackVersionAssignedIdentityTests" -BuildProjectReferences -Restore. These disposable owner-storage controls do not establish a public import host, trusted signing or live authorization.

For the bundle route, zero or negative revisions return HTTP 400 before compilation or storage, with application/problem+json and error_code: policy.bundle.invalid_revision. No Location is returned on failure. Compiler failures remain HTTP 400 with application/json, success: false and diagnostics. Successful compilation returns a Location naming the requested positive pack/revision tuple.

The positive bundle revision contract was verified at source e2a457c47d96d04656f7e5af140d51c3239bce57. Re-verify with pwsh ./tools/scripts/test-targeted-xunit.ps1 -Project src/Policy/__Tests/StellaOps.Policy.Engine.Tests/StellaOps.Policy.Engine.Tests.csproj -Class "*PolicyPackVersionAssignedIdentityTests" -BuildProjectReferences. This source/fixture check does not establish live authorization or trusted signing.

Evaluation path/body mismatches and missing bundles return the existing application/json ProblemDetails bodies with status 400 and 404 respectively. Case-variant names remain distinct stored parents; a differently cased body cannot select another parent through the route.

The exact evaluation identity and error-response contract was verified at source f7476d456ffa435578775944902b279d721c5b05. Re-verify with pwsh ./tools/scripts/test-targeted-xunit.ps1 -Project src/Policy/__Tests/StellaOps.Policy.Engine.Tests/StellaOps.Policy.Engine.Tests.csproj -Class "*PolicyPackVersionAssignedIdentityTests" -BuildProjectReferences. This source/fixture check uses an unavailable Attestor adapter and does not establish trusted attestation or live authorization.

Tenant context comes from validated identity. A caller-supplied tenant header does not grant tenant access. Use actual authenticated activation identities; do not synthesize approvers. Native HTTP and Router dispatch must both populate Policy’s request-local context; the pack group applies RequireTenantContext() for the latter. Missing context is rejected rather than selecting public, and pack reads carry an explicit tenant predicate even when the database role bypasses RLS. Summary visibility does not prove a revision’s activation state.

The related POST /policy/eval/batch route requires policy:read and the same validated tenant context. Its body tenantId must match that identity exactly: omitted/blank values return 400, mismatches return 403 before evaluation or cache access. An operator project claim of * means no project refinement on both HTTP and Router; it never changes tenant.

Responses and limits

Unavailable Registry workflow and retained requirements

The former /policies/{id}/submit, /approve, /publish, /archive lifecycle listing was a design description, not this host’s registered API. The separate Registry review and publication services are not registered. The Engine’s /policy/shadow/*, /policy/simulations* and promotion-gate compatibility routes return 501.

Reviewer separation, coverage, shadow/determinism evidence and trusted governed publication remain requirements. Do not report invented policy_shadow_required or attestation-gate responses as implemented checks; do not use Approved metadata to waive those obligations. If an approved procedure cannot supply required evidence, hold the governed operation.

Other run/evaluation APIs must be verified against their own mapped owner endpoints; this reference does not claim the former generic run routes, universal ETags, pagination, file:// handlers or automatic Offline Kit approval exports.