From 0210c66ef67e53ae0d15415f5382e7750a5a6419 Mon Sep 17 00:00:00 2001 From: master <> Date: Sat, 28 Mar 2026 10:52:53 +0200 Subject: [PATCH] Absorb Release Gates into Deployments Approvals tab MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Product rationale: Users asking "why is my release blocked?" had to check 3 places (Policy > Release Gates, Deployments > Approvals, Release Detail > Gates). Now the Deployments Approvals tab is the single surface for all blocking information with action capability. Approvals tab enriched: - Summary cards: Pending Approvals, Gate Failures, Gates Passed counts - Table columns: Release, Promotion, Gate Type, Status, Reason, Requested, Actions - Gate Type column shows approval/policy/security/freeze/dependency - Reason column explains blocking status - Contextual action: "Policy" link to policy packs when gates are blocking - Approve/Reject buttons for pending human approvals Policy sidebar: - Release Gates removed from Policy group (5 items → 4: Packs, Governance, Simulation, VEX & Exceptions, Audit) - /ops/policy/gates redirects to /releases/deployments?view=approvals - Sub-routes (gates/catalog, gates/simulate, etc.) retained for deep links - Policy shell subtitle updated to remove "release gates" mention No functionality loss — policy authors use Simulation to test rules and Audit to review change history. The gate evaluation impact on releases is now visible in context where operators can act on it. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../releases/releases-activity.component.ts | 47 +++++++++++++++++-- .../app-sidebar/app-sidebar.component.ts | 12 +---- 2 files changed, 44 insertions(+), 15 deletions(-) diff --git a/src/Web/StellaOps.Web/src/app/features/releases/releases-activity.component.ts b/src/Web/StellaOps.Web/src/app/features/releases/releases-activity.component.ts index 3ff6aa66f..db35e30db 100644 --- a/src/Web/StellaOps.Web/src/app/features/releases/releases-activity.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/releases/releases-activity.component.ts @@ -204,14 +204,30 @@ function deriveOutcomeIcon(status: string): string { } @else if (filteredApprovals().length === 0) {
No approvals match the active gate filters.
} @else { + +
+
+ {{ countByStatus('pending') }} + Pending Approvals +
+
+ {{ countByGateResult(false) }} + Gate Failures +
+
+ {{ countByGateResult(true) }} + Gates Passed +
+
+ + - - + @@ -226,9 +242,11 @@ function deriveOutcomeIcon(status: string): string { } + - - + } @empty { - + }
Release PromotionGate Type StatusUrgencyGatesReason Requested Actions
{{ apr.sourceEnvironment }} → {{ apr.targetEnvironment }}{{ deriveGateType(apr) }} {{ apr.status }}{{ apr.urgency }}{{ apr.gatesPassed ? 'Passed' : 'Failed' }} + {{ apr.gatesPassed ? 'All gates passed' : 'Gates blocking — review required' }} + {{ formatDate(apr.requestedAt) }}
by {{ apr.requestedBy }}
@@ -236,12 +254,15 @@ function deriveOutcomeIcon(status: string): string { } + @if (!apr.gatesPassed) { + Policy + }
No approvals match the active gate filters.
No approvals or gate evaluations match the current filters.
@@ -395,6 +416,14 @@ function deriveOutcomeIcon(status: string): string { .apc__status-row{display:flex;gap:.35rem;padding:0 .6rem .45rem;flex-wrap:wrap} + .gate-summary{display:flex;gap:1rem;margin-bottom:1rem} + .gate-summary__card{flex:1;padding:0.75rem 1rem;border:1px solid var(--color-border-primary);border-radius:var(--radius-md);display:flex;align-items:center;gap:0.5rem} + .gate-summary__card strong{font-size:1.25rem} + .gate-summary__card span{font-size:0.75rem;color:var(--color-text-muted);text-transform:uppercase;letter-spacing:0.04em} + .gate-summary__card--pending{border-left:3px solid var(--color-status-warning)} + .gate-summary__card--blocked{border-left:3px solid var(--color-severity-high)} + .gate-summary__card--passed{border-left:3px solid var(--color-status-success)} + .gate-type-chip{font-size:0.6875rem;font-weight:600;text-transform:uppercase;letter-spacing:0.03em;padding:0.125rem 0.5rem;border-radius:var(--radius-sm);background:var(--color-surface-tertiary)} .pending-lane{margin-bottom:1rem;padding:1rem;border:1px solid var(--color-status-warning-border);border-radius:var(--radius-lg);background:var(--color-status-warning-bg);animation:lane-enter 300ms ease-out both;overflow:hidden} .pending-lane--exiting{animation:lane-exit 250ms ease-in forwards} @keyframes lane-enter{from{opacity:0;max-height:0;padding:0;margin:0;border-width:0}to{opacity:1;max-height:500px}} @@ -523,6 +552,14 @@ export class ReleasesActivityComponent implements OnInit, OnDestroy { readonly approvalSearchQuery = signal(''); + countByStatus(status: string): number { + return this.filteredApprovals().filter(a => a.status === status).length; + } + + countByGateResult(passed: boolean): number { + return this.filteredApprovals().filter(a => a.gatesPassed === passed).length; + } + readonly gateToggleState = signal>({ gated: false, policy: false, diff --git a/src/Web/StellaOps.Web/src/app/layout/app-sidebar/app-sidebar.component.ts b/src/Web/StellaOps.Web/src/app/layout/app-sidebar/app-sidebar.component.ts index 7e066904d..5a98b0533 100644 --- a/src/Web/StellaOps.Web/src/app/layout/app-sidebar/app-sidebar.component.ts +++ b/src/Web/StellaOps.Web/src/app/layout/app-sidebar/app-sidebar.component.ts @@ -777,7 +777,7 @@ export class AppSidebarComponent implements AfterViewInit { id: 'ops-environments', label: 'Environments', icon: 'globe', - route: '/environments/regions', + route: '/environments/overview', menuGroupId: 'release-control', menuGroupLabel: 'Release Control', requireAnyScope: [ @@ -821,15 +821,7 @@ export class AppSidebarComponent implements AfterViewInit { menuGroupLabel: 'Policy', requireAnyScope: [StellaOpsScopes.VEX_READ, StellaOpsScopes.EXCEPTION_READ], }, - { - id: 'ops-policy-gates', - label: 'Release Gates', - icon: 'check-square', - route: '/ops/policy/gates', - menuGroupId: 'policy', - menuGroupLabel: 'Policy', - requireAnyScope: [StellaOpsScopes.POLICY_READ], - }, + // Release Gates absorbed into Deployments > Approvals tab { id: 'ops-policy-audit', label: 'Policy Audit',