Policy Decisioning Studio
Status
Shipped on 2026-03-07.
Product Shape
- Canonical mount:
/ops/policy - User-facing title:
Policy Decisioning Studio - Active primary tabs:
Overview,Packs,Governance,VEX & Exceptions,Release Gates,Audit(theSimulationtab was withdrawn, CCP-016) - Supported modes:
global,pack,release-context, plus non-owningapproval,workflow, andevidencecontext chips
This is now the canonical mutable owner for policy packs, governance controls, VEX resolution, exception handling, release-gate review, and policy/VEX audit.
Shipped Route Contract
Canonical routes
/ops/policy/overview/ops/policy/packs/ops/policy/packs/:packId(redirects to/ops/policy/packs/:packId/simulate)/ops/policy/packs/:packId/simulate/ops/policy/governance/.../ops/policy/vex/ops/policy/vex/search/ops/policy/vex/search/detail/:id/ops/policy/vex/create/ops/policy/vex/stats/ops/policy/vex/consensus/ops/policy/vex/explorer/ops/policy/vex/conflicts/ops/policy/vex/exceptions/ops/policy/vex/exceptions/approvals/ops/policy/vex/exceptions/:exceptionId/ops/policy/gates(redirects to the approvals view,/releases/deployments?view=approvals)/ops/policy/audit/policy/ops/policy/audit/vex/ops/policy/audit/log/ops/policy/audit/log/events
Legacy aliases kept live
/policy-studio/*/policy/*/admin/policy/governance*/admin/vex-hub*/security/vex*/security/exceptions*/administration/policy*/administration/policy-governance*
Shipped Merge Boundary
Packs
PolicyPackShellComponent(list header and pack header; a pack has no tab strip, CCP-016)PolicyWorkspaceComponent(pack list,/ops/policy/packs)PolicySimulationComponent(pack Test tab,/ops/policy/packs/:packId/simulate, CCP-015)
Pack list contract (CCP-016)
- The list reads
GET /api/policy/packs(policy:read) on every visit and on Refresh; the session cache is not used for this page. A session withoutpolicy:readmakes no request and states the missing scope. A refused read (401/403) is shown as refused, without retry. An unavailable owner (no response or another failure status) is shown with its status and a Retry. Neither is shown as an empty list. A failed Refresh keeps the previous list and says that it is the previous answer. - A pack card shows the pack name, identifier, creation date and the revision versions Policy reports (
Revision 3 · 3 revisions, orNo revisions). The list answer has no activation status, description or tags, so the card shows none. - Create policy pack (page action, or the form shown when Policy answers with no packs) calls
POST /api/policy/packsand sends the display name only, which is all Policy stores; Policy assigns the identifier. The action is offered topolicy:edit, the scope Policy enforces; without it the action is shown as unavailable with that reason. A refused or failed create keeps its error and reports no pack. - Test opens the pack Test tab and needs
policy:read, as the page itself does (notpolicy:simulate). Bind to environment writes ReleaseOrchestrator’s environment policy binding: on a pack card it opens the bindings form with that pack (/ops/policy/packs?packId=<pack>) and is offered only to a session holdingrelease.policy-bindings.readandrelease.policy-bindings.write, the grants the binding endpoints check (admindoes not imply them). When the list was opened with?bind=<environment>, the card’s action names the environment (Bind to prod for?bind=prod) and carries it too (?bind=<environment>&packId=<pack>), and the card’s Test links carry it to the pack page, whose Bind this pack to prod hands both back to the form. - The page names what the console does not do: rule authoring, revisions, bundles and activation have no console page; Policy serves them through
POST /api/policy/packs/{packId}/revisions,POST /api/policy/packs/{packId}/revisions/{version}/bundleandPOST /api/policy/packs/{packId}/revisions/{version}:activate(Policy Studio API contract). Policy has no operation that deletes a pack.
Withdrawn pack controls (CCP-016)
Each of these called a Policy route that no service maps (the router answers urn:stellaops:router:endpoint-not-found). Their routes and legacy aliases were removed; former paths resolve to the not-found page.
| Former control | Former route | What it showed | Supported path |
|---|---|---|---|
| Run dashboard (pack landing, Rules tab) | /ops/policy/packs/:packId/dashboard | “No run data available” when its read failed; run export | Test tab for an evaluation |
| DSL editor (Edit) | /ops/policy/packs/:packId/edit, /editor | an editor with nothing loaded; a lint request that always failed | Policy revision and bundle API |
| Rule builder (Rules tab) | /ops/policy/packs/:packId/rules | a form with fixed defaults and a JSON preview, never loaded or saved | Policy revision and bundle API |
| YAML editor | /ops/policy/packs/:packId/yaml | YAML of a pack read that never answered | Policy revision and bundle API |
| Approvals (Activate tab, Approvals) | /ops/policy/packs/:packId/approvals | a blank page (review, checklist, schedule and comment forms) | Policy two-person :activate |
| Explain | /ops/policy/packs/:packId/explain/:runId | a page that never left its loading state | Test tab result |
| Delete | (pack card action) | a confirmation; the failure only reached the browser console | none: Policy cannot delete a pack |
Pack Test tab contract
- Evaluates one revision of the routed pack against one release of the caller’s tenant through Policy’s pack evaluation operation,
POST /api/policy/packs/{packId}/revisions/{version}/evaluate, with subjectrelease:{releaseId}and the query parameterpurpose=authoring. This is the operation the release gate calls; the gate names no purpose (gate). - Inputs come only from their owners. Revisions:
GET /api/policy/packs(the routed pack’sversions, newest first). Releases:GET /api/v1/release-orchestrator/releases?page=&pageSize=50with Load more; selection identity is the release id. - Immediately before the evaluation the page re-reads the selected release (
GET /api/v1/release-orchestrator/releases/{id}). A failed re-read (404, 401/403, or an owner that cannot answer) stops the run before Policy is asked. - Changing the routed pack cancels the revision read and any run still in progress for the previous pack, so a late answer cannot fill the form or the result.
- The page needs
policy:read(pack list and evaluation) and the ReleaseOrchestrator release read policy. - A pack missing from the tenant, a pack without revisions, an empty release list, an owner configuration state such as
missing_ownership_claims, and an unavailable or refused read are separate states. No option is substituted when an owner cannot answer. - The result shows Policy’s decision with pack, revision, bundle digest, subject, correlation ID, cache flag, evidence (resolved release, CVE count, deny-list rule count, rule matches, Policy note) and the verdict attestation reference, plus the release status and component count from the re-read. A draft release is marked as such: ReleaseOrchestrator still accepts component changes for a draft, so a later evaluation of the same release can resolve different findings. The page does not show component digests. Policy’s evidence note (for example
no finding.cve_id rules recognised in bundle, or a findings source that was unavailable) is shown next to the decision. A missing bundle (404), a rejected request (400) and an unavailable Policy are shown as failures without a decision. - The evaluation does not activate the revision, and it is an authoring evaluation: Policy computes the same decision as for the gate and records no verdict (PCD-974). The page states this before the run. The result shows the returned reference
not_recordedwith the reasonauthoring_evaluationas “Not recorded”. Evaluations that name no purpose, such as the release gate’s and Replay’s re-evaluations, record a signed verdict for the release when verdict attestation is enabled (the Policy default). The statement holds only against a Policy Engine that carries PCD-974: an older Policy Engine ignores thepurposeparameter and records a verdict, which the result then shows asattested. - SBOM catalog entries and free-text component or advisory lists are not inputs: Policy’s evaluation resolves only release subjects, and the SBOM catalog read model has no in-platform producer.
Governance
- Existing
policy-governance.routes.tssubtree mounted under/ops/policy/governance - Settings, impact-preview, profile, trust-weight, and schema surfaces now point to the canonical shell
- The policy Validator (
/ops/policy/validatorand the Validator section of the governance developer tools) was withdrawn (CCP-016): its only operation, the policy validation route/api/v1/governance/policy/validate, is not mapped by Policy, and a failed validation reached only the browser console. The Playground section validates risk profiles through the servedPOST /api/v1/governance/risk-profiles/validate.
Simulation (withdrawn, CCP-016)
The /ops/policy/simulation subtree and its Simulation tab were withdrawn. Every Policy route it read answers 501 “Policy simulation compatibility backend unavailable”, and Policy’s capability read (GET /policy/capabilities) reports simulation and shadowMode unavailable. The former paths and their policy-studio/simulate, policy/simulation* and admin/policy/simulation* aliases resolve to the not-found page, and no console page links to them.
| Former page | Former route | Policy routes it read (all 501) |
|---|---|---|
| Shadow mode (landing) | /ops/policy/simulation, /shadow | /policy/shadow/config, /enable, /disable, /results |
| Promotion gate | /ops/policy/simulation/promotion | /policy/packs/{packId}/versions/{version}/promotion-gate and its override |
| Test and validate (console, coverage, lint) | /ops/policy/simulation/testing | /policy/simulations, /policy/packs/{packId}/coverage, /policy/packs/{packId}/lint |
| Pre-promotion review (diff, merge, conflicts) | /ops/policy/simulation/review | /policy/packs/{packId}/diff, /policy/merge/preview, /policy/conflicts/* |
| Effective policies | /ops/policy/simulation/effective | /policy/effective |
| History | /ops/policy/simulation/history | /policy/simulations/history, /compare, /{simulationId}/verify |
| Batch evaluation | /ops/policy/simulation/batch | /policy/batch-evaluations |
| Pack diff | /ops/policy/simulation/diff/:policyPackId | /policy/packs/{packId}/diff |
The supported path is the pack Test tab, which evaluates a pack revision against a release (see the Pack Test tab contract above). Shadow mode, batch evaluation and the other simulation operations are not offered until Policy serves them.
VEX and exceptions
- Existing
vex-hubcomponents mounted under/ops/policy/vex - Security VEX and exception aliases now redirect into the canonical VEX subtree
- Mutable VEX actions are no longer owned by a separate Security shell
Gates and audit
/ops/policy/gatesredirects to the approvals view; the recorded per-gate verdicts, including the decision basis and its missing inputs, are on the approval record (/releases/approvals/:id).- The environment, release and approval gate review pages (
/ops/policy/gates/environments/:environment,/ops/policy/gates/releases/:releaseId,/ops/policy/gates/approvals/:approvalId) were withdrawn (CCP-028) with every action that opened them: Open Decisioning Studio on the promotion wizard and the promotion request, and Decisioning and Open Decisioning Studio on the release detail. They read no owner and showed the same fixed gate results for every subject (policy pack and VEX consensus passed, invented reachability witness counts and CVE ids, a stale-feed warning), so a blocked release or a decision with an absent basis read as passed. The paths resolve to the not-found page. - The gate catalog (
/ops/policy/gates/catalog, profile selector, gate simulation, feed freshness and air-gap switch) and the bundle simulator (/ops/policy/gates/simulate/:promotionId) were withdrawn (CCP-016) with theirpolicy/gatesandpolicy/gates/simulate/:promotionIdaliases. They read gate profiles, gate and bundle simulation and feed freshness under/api/v1/policy/gate/, which Policy does not map (the router answersurn:stellaops:router:endpoint-not-found); Policy’s gate API serves evaluation, decisions, health and jobs only. - Canonical policy/VEX audit owner under
/ops/policy/audit*
Release Orchestrator Integration
Shipped entry points
- approvals detail
- release detail (Create Exception, to the exception queue)
- workflow editor
- evidence detail
Shipped context fields
releaseIdapprovalIdenvironmentartifact/bundleDigestworkflowIdevidenceIdreturnTo
Release Orchestrator still owns promotion state and workflow execution. Decisioning Studio owns policy and VEX authoring, mutation, and explanation.
Secondary Entry Points Updated
Security OverviewSecurity ExceptionsVulnerability DetailHome DashboardPolicy Governance SettingsEvidence AuditTimeline Evidence LinksPolicy baseline chip- global search VEX normalization
Retired Or Superseded Writable Owners
- standalone
Policy Studioproduct label - standalone
VEX Hubmutable owner - mutable
policy/*writable paths - mutable
security/vex*owner paths
These names survive only as temporary redirect aliases where needed for bookmark continuity.
Verification Evidence
- feature verification note:
docs/features/checked/web/policy-decisioning-studio-ui.md - targeted Angular tests:
94passing assertions across route, shell, redirect, workflow, evidence, and search coverage - Playwright:
4/4passing scenarios for global mode, pack mode, release-context mode, and security VEX alias redirect - production build: pass, with existing unrelated bundle-budget warnings
