Policy Gateway (Merged into Policy Engine)
Audience: Operators and integrators calling the Stella Ops Policy HTTP API (Console, CLI, and automation). Status: The
StellaOps.Policy.Gatewayservice has been merged intoStellaOps.Policy.Engine. All gateway endpoints (exceptions, deltas, gates, governance, tool-lattice, pack CRUD, activation) are now served directly by the policy-engine process. The separate gateway container, HTTP proxy layer, DPoP token flow, andPolicyEngineClienthave been removed.
This guide documents the HTTP surface and operational behaviour of the policy-engine endpoints that the former gateway exposed. For the full DTO and schema reference, see the Policy Engine API doc.
1 - Responsibilities (now handled by Policy Engine)
- Policy pack CRUD and activation endpoints enforcing scope policies (
policy:read,policy:author,policy:review,policy:operate,policy:activate). - Normalised responses (DTO +
ProblemDetails) so Console, CLI, and automation receive consistent payloads. - Structured logging and metrics for activation actions so approvals are auditable.
- Exception management, delta computation, gate evaluation, governance, and tool-lattice endpoints.
2 - Endpoints
All endpoints below are served by the policy-engine service.
| Route | Method | Description | Required scope(s) |
|---|---|---|---|
/api/policy/packs | GET | List policy packs and revisions for the active tenant. | policy:read |
/api/policy/packs | POST | Create a policy pack shell or upsert display metadata. | policy:author |
/api/policy/packs/{packId}/revisions | POST | Create or update a policy revision (draft/approved). | policy:author |
/api/policy/packs/{packId}/revisions/{version}:activate | POST | Activate a revision, enforcing single/two-person approvals. | policy:operate, policy:activate |
Response shapes
- Successful responses return camel-case DTOs matching
PolicyPackDto,PolicyRevisionDto, orPolicyRevisionActivationDtoas described in the Policy Engine API doc (/docs/api/policy.md). - Errors always return RFC 7807
ProblemDetailswith deterministic fields (title,detail,status).
Dual-control activation
- Config-driven. Set
PolicyEngine.activation.forceTwoPersonApproval=truewhen every activation must collect two distinctpolicy:activateapprovals. When false, operators can opt into dual-control per revision (requiresTwoPersonApproval: true). - Defaults.
PolicyEngine.activation.defaultRequiresTwoPersonApprovalfeeds the default when callers omit the checkbox/flag. - Statuses. First approval on a dual-control revision returns
202 pending_second_approval; duplicate actors get400 duplicate_approval; the second distinct approver receives the usual200 activated. - Audit trail. With
PolicyEngine.activation.emitAuditLogson, Policy Engine emits structuredpolicy.activation.*scopes (pack id, revision, tenant, approver IDs, comments).
Activation configuration wiring
- Compose/Offline Kit. Set
STELLAOPS_POLICY_ENGINE__ACTIVATION__*variables through the supported Compose profile, Offline Kit overlay, or host service configuration. - File-based overrides (optional). The Policy Engine host probes
/config/policy-engine/activation.yaml,../etc/policy-engine.activation.yaml, and ambientpolicy-engine.activation.yamlfiles beside the binary. - Unsupported deployment path. Helm charts, Kubernetes ConfigMaps, and pod
envFromwiring are retired and must not be used as Stella Ops deployment guidance.
3 - Authentication & headers
| Header | Source | Notes |
|---|---|---|
Authorization | Caller token. | Caller tokens must include tenant scope. |
X-StellaOps-TenantId | Caller | Tenant isolation header. |
Note: The previous DPoP proxy layer (gateway client credentials,
PolicyEngineClient,PolicyGatewayDpopHandler) has been removed. Callers authenticate directly with Policy Engine using standard StellaOps resource server authentication.
4 - Metrics & logging
All activation calls emit:
policy_engine_activation_requests_total{outcome,source}– counter labelled withoutcome(activated,pending_second_approval,already_active,bad_request,not_found,unauthorized,forbidden,error) andsource(caller,service).
Structured logs (category StellaOps.Policy.Engine.Activation) include PackId, Version, Outcome, Source, and status code for audit trails.
5 - Release Gate Runtime Integrity
POST /api/v1/policy/gate/evaluate and the scheduler-backed async gate worker are fail-closed for missing baselines. A request that omits baselineRef and has no tenant baseline now returns a denied gate by default with baselineStatus: "missing:block", blockedBy: "missing-baseline", and evidence describing the selected missing-baseline mode.
Callers that need first-build behavior must choose it explicitly:
missingBaselineMode | Result when no baseline exists | Use case |
|---|---|---|
block or omitted | Deny/fail the gate | Protected and production profiles |
warn | Return a warning with evidence | Non-prod migration windows |
bootstrap | Create the initial baseline only when no explicit baselineRef is supplied | Audited first-build enrollment |
Bootstrap is never implicit. Explicit baselineRef values remain strict: a missing referenced snapshot is an error rather than a fabricated baseline.
Score-gate signing also fails closed. Operators must configure PolicyGate:ScoreGate:SigningSecretBase64 or STELLA_GATE_SIGNING_SECRET with at least 32 bytes of non-zero decoded key material. Empty, invalid, or all-zero secrets cause the request path to return 503 problem+json.
Transparency mode is explicit:
PolicyGate:ScoreGate:TransparencyMode | Behavior |
|---|---|
Offline | Sign locally and do not submit to Rekor |
OnlineRekor | Require PolicyGate:ScoreGate:RekorUrl or STELLA_REKOR_URL, and anchor requested verdicts to that endpoint |
There is no public Rekor default. Online Rekor is an opt-in profile and the endpoint must be configured by the deployment.
6 - Sample curl workflows
Assuming you already obtained an access token ($TOKEN) for tenant acme:
curl -sS https://gateway.example.com/api/policy/packs \
-H "Authorization: Bearer $TOKEN" \
-H "X-StellaOps-TenantId: acme"
# Draft a new revision
curl -sS https://gateway.example.com/api/policy/packs/policy.core/revisions \
-H "Authorization: Bearer $TOKEN" \
-H "X-StellaOps-TenantId: acme" \
-H "Content-Type: application/json" \
-d '{"version":5,"requiresTwoPersonApproval":true,"initialStatus":"Draft"}'
# Activate revision 5 (returns 202 when awaiting the second approver)
curl -sS https://gateway.example.com/api/policy/packs/policy.core/revisions/5:activate \
-H "Authorization: Bearer $TOKEN" \
-H "X-StellaOps-TenantId: acme" \
-H "Content-Type: application/json" \
-d '{"comment":"Rollout baseline"}'
7 - Offline Kit guidance
- Include
policy-engine.yaml.sampleand the resolved runtime config in the Offline Kit’sconfig/tree. - When building verification scripts, use the policy-engine endpoints above. The Offline Kit validator expects
policy_engine_activation_requests_totalmetrics in the Prometheus snapshot.
8 - Backwards compatibility
- The
policy-gateway.stella-ops.localhostname is preserved as a network alias on the policy-engine container in both docker-compose and hosts files. Existing configurations referencingpolicy-gateway.stella-ops.localwill continue to work. - The
STELLAOPS_POLICY_GATEWAY_URLenvironment variable has been removed from service defaults. Services that previously used it should useSTELLAOPS_POLICY_ENGINE_URLinstead.
9 - Change log
- 2026-04-25 – Gate integrity fail-closed: Missing baselines deny by default, first-build bootstrap is explicit, score-gate signing rejects missing/zero secrets, and Rekor use requires an explicit transparency mode and endpoint.
- 2026-04-08 – Gateway merge: Gateway merged into Policy Engine. Separate container, HTTP proxy layer, DPoP flow, and
PolicyEngineClientremoved. All endpoints served directly by policy-engine. - 2025-10-27 – Sprint 18.5: Initial gateway bootstrap + activation metrics + DPoP client credentials.
