# Policy Gateway > **Delivery scope:** `StellaOps.Policy.Gateway` minimal API service fronting Policy Engine pack CRUD + activation endpoints for UI/CLI clients. Sender-constrained with DPoP and tenant headers, suitable for online and Offline Kit deployments. ## 1 · Responsibilities - Proxy policy pack CRUD and activation requests to Policy Engine while enforcing scope policies (`policy:read`, `policy:author`, `policy:review`, `policy:operate`, `policy:activate`). - Normalise responses (DTO + `ProblemDetails`) so Console, CLI, and automation receive consistent payloads. - Guard activation actions with structured logging and metrics so approvals are auditable. - Support dual auth modes: - Forwarded caller tokens (Console/CLI) with DPoP proofs + `X-Stella-Tenant` header. - Gateway client credentials (DPoP) for service automation or Offline Kit flows when no caller token is present. ## 2 · Endpoints | 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`, or `PolicyRevisionActivationDto` as described in the Policy Engine API doc (`/docs/api/policy.md`). - Errors always return RFC 7807 `ProblemDetails` with deterministic fields (`title`, `detail`, `status`). Missing caller credentials now surface `401` with `"Upstream authorization missing"` detail. ## 3 · Authentication & headers | Header | Source | Notes | |--------|--------|-------| | `Authorization` | Forwarded caller token *or* gateway client credentials. | Caller tokens must include tenant scope; gateway tokens default to `DPoP` scheme. | | `DPoP` | Caller or gateway. | Required when Authority mandates proof-of-possession (default). Generated per request; gateway keeps ES256/ES384 key material under `etc/policy-gateway-dpop.pem`. | | `X-Stella-Tenant` | Caller | Tenant isolation header. Forwarded unchanged; gateway automation omits it. | Gateway client credentials are configured in `policy-gateway.yaml`: ```yaml policyEngine: baseAddress: "https://policy-engine.internal" audience: "api://policy-engine" clientCredentials: enabled: true clientId: "policy-gateway" clientSecret: "" scopes: - policy:read - policy:author - policy:review - policy:operate - policy:activate dpop: enabled: true keyPath: "../etc/policy-gateway-dpop.pem" algorithm: "ES256" ``` > 🔐 **DPoP key** – store the private key alongside Offline Kit secrets; rotate it whenever the gateway identity or Authority configuration changes. ## 4 · Metrics & logging All activation calls emit: - `policy_gateway_activation_requests_total{outcome,source}` – counter labelled with `outcome` (`activated`, `pending_second_approval`, `already_active`, `bad_request`, `not_found`, `unauthorized`, `forbidden`, `error`) and `source` (`caller`, `service`). - `policy_gateway_activation_latency_ms{outcome,source}` – histogram measuring proxy latency. Structured logs (category `StellaOps.Policy.Gateway.Activation`) include `PackId`, `Version`, `Outcome`, `Source`, and upstream status code for audit trails. ## 5 · Sample `curl` workflows Assuming you already obtained a DPoP-bound access token (`$TOKEN`) for tenant `acme`: ```bash # Generate a DPoP proof for GET via the CLI helper DPoP_PROOF=$(stella auth dpop proof \ --htu https://gateway.example.com/api/policy/packs \ --htm GET \ --token "$TOKEN") curl -sS https://gateway.example.com/api/policy/packs \ -H "Authorization: DPoP $TOKEN" \ -H "DPoP: $DPoP_PROOF" \ -H "X-Stella-Tenant: acme" # Draft a new revision DPoP_PROOF=$(stella auth dpop proof \ --htu https://gateway.example.com/api/policy/packs/policy.core/revisions \ --htm POST \ --token "$TOKEN") curl -sS https://gateway.example.com/api/policy/packs/policy.core/revisions \ -H "Authorization: DPoP $TOKEN" \ -H "DPoP: $DPoP_PROOF" \ -H "X-Stella-Tenant: acme" \ -H "Content-Type: application/json" \ -d '{"version":5,"requiresTwoPersonApproval":true,"initialStatus":"Draft"}' # Activate revision 5 (returns 202 when awaiting the second approver) DPoP_PROOF=$(stella auth dpop proof \ --htu https://gateway.example.com/api/policy/packs/policy.core/revisions/5:activate \ --htm POST \ --token "$TOKEN") curl -sS https://gateway.example.com/api/policy/packs/policy.core/revisions/5:activate \ -H "Authorization: DPoP $TOKEN" \ -H "DPoP: $DPoP_PROOF" \ -H "X-Stella-Tenant: acme" \ -H "Content-Type: application/json" \ -d '{"comment":"Rollout baseline"}' ``` For air-gapped environments, bundle `policy-gateway.yaml` and the DPoP key in the Offline Kit (see `/docs/24_OFFLINE_KIT.md` §5.7). > **DPoP proof helper:** Use `stella auth dpop proof` to mint sender-constrained proofs locally. The command accepts `--htu`, `--htm`, and `--token` arguments and emits a ready-to-use header value. Teams maintaining alternate tooling (for example, `scripts/make-dpop.sh`) can substitute it as long as the inputs and output match the CLI behaviour. ## 6 · Offline Kit guidance - Include `policy-gateway.yaml.sample` and the resolved runtime config in the Offline Kit’s `config/` tree. - Place the DPoP private key under `secrets/policy-gateway-dpop.pem` with restricted permissions; document rotation steps in the manifest. - When building verification scripts, use the gateway endpoints above instead of hitting Policy Engine directly. The Offline Kit validator now expects `policy_gateway_activation_requests_total` metrics in the Prometheus snapshot. ## 7 · Change log - **2025-10-27 – Sprint 18.5**: Initial gateway bootstrap + activation metrics + DPoP client credentials.