Policy Lifecycle & Approvals

Audience: Policy authors, reviewers, security approvers and operators. Scope: The registered Policy Engine pack/revision API and the governance work it does not implement. Verified against source: 00f64ca4d165fc54e7c291795b65326869b7627d (2026-08-31). Full Policy tests passed1916/1916; source verification is not a receipt for a deployed policy or approval. Re-verification commands are in §10.

New or significantly changed policies still require review, coverage, shadow evidence and determinism checks before enforcement. These requirements are not waived by the available metadata/activation API. The Engine does not register the separate Registry submit/review/shadow/publish workflow. If an explicitly approved procedure cannot supply required evidence, hold promotion/activation and record the gap; never invent a successful review, shadow run or attestation.

See the overview, DSL reference, governance guide and API reference. Distinguish design requirements from registered endpoints.

1 · Registered lifecycle and its limits

Program.cs registers IPolicyPackRepository as PostgresPolicyPackRepository and calls MapPolicyPacks(). Policy owns the durable policy.packs and policy.pack_versions state. The exposed revision states are Draft, Approved and Active:

Explicit Draft metadata -> compile/store bundle -> explicit Approved metadata -> activation
                                                       |
                                  configured two-person activation: first actor = pending

This is the operational sequence over the actual API, not an enforced review state machine.

2 · Authorization

Engine operationRequired scope
Create pack, upsert revision metadata, compile/store bundlepolicy:edit
List pack summaries, evaluate a specified revision, read capabilitiespolicy:read
Activate an approved revisionpolicy:activate

Use an Authority-issued identity for the intended tenant. Tenant identity comes from validated identity, not a caller-supplied tenant header. Use real authenticated operator identities for activation; never fabricate an actor to satisfy the distinct-actor check. Other Policy scopes do not make Registry review or publication available.

Both native HTTP and Router-dispatched pack operations require that validated identity in Policy’s request-local tenant context. Router invokes the endpoint delegate without the HTTP tenant middleware, so the pack group’s RequireTenantContext() filter is required. Missing context must fail closed; it must not select public. Pack queries also predicate the captured tenant explicitly: an RLS-enabled table does not isolate a database-owner connection that bypasses RLS. A token’s tenant is not proof of the tenant under which an older request stored data.

An exact operator project wildcard * is normalized to no project refinement consistently on both transports. Pack evaluation’s cache and correlation identity include the captured tenant and exact pack/version; content-equivalent revisions do not share response identities or attestation references across those boundaries.

3 · Durable Engine procedure

Mutate only the approved policy/target. Record runtime/source identity, tenant, pack, positive integer revision and reviewed source/bundle digest. Keep secret values in the existing credential channel, not examples, logs or receipts.

3.1 Create the pack and explicit Draft metadata

  1. POST /api/policy/packs accepts packId and optional displayName. Retain the returned identity; creating the container does not compile a policy.
  2. POST /api/policy/packs/{packId}/revisions accepts explicit positive version, initialStatus: "Draft" and the approved requiresTwoPersonApproval requirement. Do not omit status: the DTO defaults to Approved.
  3. Verify the returned status and requiresTwoPersonApproval.

PolicyEngine:Activation:ForceTwoPersonApproval takes precedence; otherwise an explicit request wins over DefaultRequiresTwoPersonApproval. The requirement is persisted on first creation: upserting an existing revision does not change it. Direct bundle storage can create a Draft with the repository’s false requirement, so create metadata first. Do not assume a later configuration change retrofits existing revisions.

3.2 Compile and retain the exact bundle

POST /api/policy/packs/{packId}/revisions/{version}/bundle accepts dsl with syntax and source, optional signingKeyId, and optional provenance. Use reviewed stella-dsl@1 source. Check HTTP success and response success, diagnostics, digest, size and source/artifact metadata. Compilation failure returns HTTP 400.

The repository stores source text/syntax, payload, digest and compilation metadata. Reload recompiles persisted source. Storage does not approve the Draft; replacing reviewed bundle bytes cannot inherit the old review.

Signing limit: PolicyBundleService.Sign is an explicit deterministic offline-testing signature stub. Its sig:sha256:... value is not DSSE or an operator signature; this method sets no attestation reference. It does not satisfy trusted publication or approval evidence.

3.3 Verify content and real governance evidence

Run applicable lint, coverage and deterministic checks against the exact source. POST /api/policy/packs/{packId}/revisions/{version}/evaluate accepts matching packId, version and the intended subject. It rejects path/body mismatch and returns 404 when the bundle is missing. It evaluates a specified bundle, not a Registry simulation or shadow run.

Retain actual reviewer decisions, rationale and required shadow/coverage results. Evaluation success is not authorization to activate. Missing evidence remains a gap under the governing procedure; the metadata endpoint does not validate attachments or reviewer quorum.

3.4 Record Approved metadata explicitly

After governing review requirements are satisfied, upsert the same explicit revision with initialStatus: "Approved" and verify its response. This is a metadata operation, not Registry submission/review or a DSSE publication. Do not infer approval from compilation success or the storage column name is_published.

3.5 Activate with the configured actor threshold

POST /api/policy/packs/{packId}/revisions/{version}:activate accepts a JSON body containing optional comment and requires policy:activate.

ResponseMeaning
202, pending_second_approvalFirst distinct approval recorded; not active yet.
200, activatedThreshold met and pack active-version pointer changed.
200, already_activeAlready active; not a new approval or forcing proof.
400, not approved / approval already recordedDraft or duplicate actor refused.
404Pack or revision missing.

When required, a second legitimate identity in the same tenant must approve. Actor IDs are compared case-insensitively. This is not an author-versus-reviewer separation check: preserve stronger organizational separation as a real prerequisite. Retain the response and approvals; neither pending status nor a repeated identity completes activation.

3.6 Verify the actual consumer separately

Activation updates Policy state and records the attempt. It does not enqueue a full run, promote a Release Orchestrator environment or prove consumer selection of the intended bundle. Verify exact pack/revision/digest and positive/negative forcing cases through the approved consumer workflow. Metadata success is not release-gate acceptance.

3.7 Archival and rollback boundary

MapPolicyPacks has no archive transition. A previous approved revision can be a rollback candidate through the same activation endpoint, but retained approvals and actual responses must be checked; do not assume a fresh quorum or automatic run. Retain incident/change approval and verify the consumer separately. Do not edit database rows, erase approvals or invent an archive endpoint to finish the procedure.

4 · Tooling availability

5 · Audit and persistence evidence

PostgresPolicyPackRepository stores revision metadata and activation approvals in policy.packs / policy.pack_versions. Pack creation, revision metadata changes and activation carry endpoint audit metadata. PolicyActivationAuditor emits structured attempt logs when PolicyEngine:Activation:EmitAuditLogs is enabled.

Retain actual responses, actor/tenant, source/bundle digests, governing approval and consumer receipts. This path does not establish Registry reviewer comments, submission attachments, publish/archive events or signed approval history. A separate post-evaluation verdict attestation is not a policy-approval/publication attestation.

6 · Required governance, unavailable automation

Requirement retainedCurrent Engine limit
Tenant/scope and Approved activation stateRegistered checks; not full lifecycle governance.
Reviewer sign-off, separation and unresolved commentsRegistry workflow unregistered; Approved metadata is not evidence.
Shadow, coverage and twin-run determinismRequired for relevant changes; no registered shadow promotion gate validates them here.
Reason/ticket and trusted DSSE for governed publicationRequired when publishing; the compile signature stub does not satisfy them.
Incident rollback, preservation and consumer acceptanceSeparate approved operation, not automatic run/archive choreography.

GET /policy/capabilities requires policy:read and reports simulation.available and shadowMode.available; both source defaults are false. Registered shadow/simulation and promotion-gate compatibility handlers return HTTP 501. Flipping capability flags does not register a backend. Keep future Registry requirements explicit until durable registration and real positive/negative evidence are delivered together.

7 · Offline and air-gap boundary

The lifecycle still requires the local Policy owner service and durable store. Air-gap operation does not enable Registry review/shadow/publication. Preserve exact source, inputs, digests and trusted signatures through the approved offline procedure; this API does not establish automatic Offline Kit export of Registry approval records.

8 · Incident response

Retain current active identity/evidence, stop promotion on failed required checks and follow §3.7. This lifecycle does not implement automatic incident sampling, retention changes or evidence exports; use their actual owner controls and verify separately.

9 · Completion checklist

10 · Source re-verification

From the repository root:

rg -n 'PostgresPolicyPackRepository|MapPolicyPacks|MapPolicyCapabilitiesEndpoints|MapPolicySimulationEndpoints' src/Policy/StellaOps.Policy.Engine/Program.cs
rg -n 'MapPost|RequireStellaOpsScopes|InitialStatus|ActivatePolicyRevisionRequest' src/Policy/StellaOps.Policy.Engine/Endpoints/PolicyPackEndpoints.cs
rg -n 'UpsertRevisionAsync|StoreBundleAsync|RecordActivationAsync|is_published|requires_two_person_approval|ActivationApprovals' src/Policy/StellaOps.Policy.Engine/Services/PostgresPolicyPackRepository.cs
rg -n 'ForceTwoPersonApproval|DefaultRequiresTwoPersonApproval|requested' src/Policy/StellaOps.Policy.Engine/Services/PolicyActivationSettings.cs
rg -n 'signature stub|AttestationRef: null|private static string Sign' src/Policy/StellaOps.Policy.Engine/Services/PolicyBundleService.cs
rg -n 'CompatibilityUnavailable|Status501NotImplemented' src/Policy/StellaOps.Policy.Engine/Endpoints/Gateway/PolicySimulationEndpoints.cs
rg -n 'api/policy/policies/.+versions/.+:activate' src/Cli/StellaOps.Cli/Services/BackendOperationsClient.cs

Run the actual transport/repository regression tests after changing the tenant boundary:

pwsh ./tools/scripts/test-targeted-xunit.ps1 -Project src/Policy/__Tests/StellaOps.Policy.Engine.Tests/StellaOps.Policy.Engine.Tests.csproj -Class '*PolicyPackRouterTenantContextTests' -BuildProjectReferences -Restore

These tests include an owner-role database with RLS bypass, signed Router identity, isolated same-name packs, all six lifecycle routes and missing-context rejection. They are local conformance, not a deployed policy activation receipt.

Read the enclosing methods, not only matching lines. Reconfirm live capabilities, mapped routes, persisted requirements and consumer behavior before operational use.