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.
Approvedis an explicit metadata upsert usingpolicy:edit, not reviewer/shadow evidence. The database’sis_publishedfield represents that state, not external artifact publication.- Bundle compilation does not approve a Draft. Explicit revision evaluation can evaluate its stored bundle without checking activation status; this is not shadow-mode coverage.
- Activation checks persisted Approved state and the configured distinct-actor threshold, not bundle presence, reviewer evidence, coverage or shadow results.
- Metadata and bundle fields are updateable. Allocate a new revision for changed policy content and preserve the reviewed digest; do not claim storage-enforced version immutability.
Submitted, review, publish/promote andArchivedare not transitions in this Engine API. Separate Registry implementations are not registered as its live workflow.
2 · Authorization
| Engine operation | Required scope |
|---|---|
| Create pack, upsert revision metadata, compile/store bundle | policy:edit |
| List pack summaries, evaluate a specified revision, read capabilities | policy:read |
| Activate an approved revision | policy: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
POST /api/policy/packsacceptspackIdand optionaldisplayName. Retain the returned identity; creating the container does not compile a policy.POST /api/policy/packs/{packId}/revisionsaccepts explicit positiveversion,initialStatus: "Draft"and the approvedrequiresTwoPersonApprovalrequirement. Do not omit status: the DTO defaults toApproved.- 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 bundle route requires a positive revision. Zero or negative values return HTTP 400 with error_code: policy.bundle.invalid_revision before compilation or storage; no Location is returned. A positive revision need not already exist, although creating reviewed Draft metadata first preserves the activation requirement described above. To allocate a revision, call the revision metadata route with version omitted and use the returned positive version in the bundle request. Allocation starts at 1 for empty, zero-only or negative-only revision sets; otherwise it follows the highest stored positive revision. Existing imported values are not rewritten. Its Location identifies the same pack/revision tuple that was stored.
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.
Validation errors use application/problem+json with ProblemDetails. Compilation errors keep the existing application/json response with success: false and diagnostics.
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.
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.
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. The body pack ID must match the route exactly, including case. A name or version mismatch returns 400 before bundle lookup or evaluation; the existing mismatch body is retained. Both 400 and 404 use application/json ProblemDetails. The route returns 404 when the bundle is missing. It evaluates a specified bundle, not a Registry simulation or shadow run.
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.
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.
| Response | Meaning |
|---|---|
202, pending_second_approval | First distinct approval recorded; not active yet. |
200, activated | Threshold met and pack active-version pointer changed. |
200, already_active | Already active; not a new approval or forcing proof. |
| 400, not approved / approval already recorded | Draft or duplicate actor refused. |
| 404 | Pack 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
- Use the canonical Engine API. A response
Locationdoes not establish a mapped single-revision GET; the pack group exposes summary listing and the operations above. - Local
stella policy edit <file>,lint <file>andtest <file>operate on files and coverage fixtures; they do not create durable review records. - CLI activation currently sends
api/policy/policies/{id}/versions/{v}:activate, not the registered pack route. Parser acceptance is not working Engine activation. See the CLI guide. - Registry submit/review/publish commands do not create a backend by existing in client source.
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 retained | Current Engine limit |
|---|---|
| Tenant/scope and Approved activation state | Registered checks; not full lifecycle governance. |
| Reviewer sign-off, separation and unresolved comments | Registry workflow unregistered; Approved metadata is not evidence. |
| Shadow, coverage and twin-run determinism | Required for relevant changes; no registered shadow promotion gate validates them here. |
| Reason/ticket and trusted DSSE for governed publication | Required when publishing; the compile signature stub does not satisfy them. |
| Incident rollback, preservation and consumer acceptance | Separate 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
- [ ] Exact runtime/source, tenant, pack, revision and reviewed digest recorded.
- [ ] Draft metadata precedes bundle; persisted activation requirement verified.
- [ ] Applicable content/evaluation checks recorded without claiming a shadow result.
- [ ] Review, coverage, shadow and signing requirements have attributable evidence; unavailable requirements remain open rather than successful.
- [ ] Explicit Approved metadata and final activation response recorded; pending is not complete.
- [ ] Consumer selection/forcing and rollback posture verified separately.
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.
