From c797bd9f469fb3af51e7aabdf720beee668ad325 Mon Sep 17 00:00:00 2001 From: master <> Date: Sun, 8 Mar 2026 10:23:34 +0200 Subject: [PATCH] Preserve canonical policy and reachability QA routes --- .../policy-decisioning-shell.component.ts | 23 ++- .../policy-decisioning.routes.ts | 157 +++++++++++++++++- .../policy-decisioning-routes.spec.ts | 17 ++ ...policy-decisioning-shell.component.spec.ts | 19 +++ .../e2e/prealpha-canonical-full-sweep.spec.ts | 23 ++- 5 files changed, 224 insertions(+), 15 deletions(-) diff --git a/src/Web/StellaOps.Web/src/app/features/policy-decisioning/policy-decisioning-shell.component.ts b/src/Web/StellaOps.Web/src/app/features/policy-decisioning/policy-decisioning-shell.component.ts index 74a6c21d3..cc14a415e 100644 --- a/src/Web/StellaOps.Web/src/app/features/policy-decisioning/policy-decisioning-shell.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/policy-decisioning/policy-decisioning-shell.component.ts @@ -401,16 +401,33 @@ function collectRouteParams(snapshot: ActivatedRouteSnapshot | null): Record m.PolicyDecisioningOverviewPageComponent, ), }, + { + path: 'risk-budget', + title: 'Policy Governance', + loadComponent: () => + import('../policy-governance/risk-budget-dashboard.component').then( + (m) => m.RiskBudgetDashboardComponent, + ), + }, + { + path: 'budget', + title: 'Policy Governance', + loadComponent: () => + import('../policy-governance/risk-budget-dashboard.component').then( + (m) => m.RiskBudgetDashboardComponent, + ), + }, + { + path: 'risk-budget/config', + title: 'Policy Governance', + loadComponent: () => + import('../policy-governance/risk-budget-config.component').then( + (m) => m.RiskBudgetConfigComponent, + ), + }, + { + path: 'budget/config', + title: 'Policy Governance', + loadComponent: () => + import('../policy-governance/risk-budget-config.component').then( + (m) => m.RiskBudgetConfigComponent, + ), + }, + { + path: 'trust-weights', + title: 'Policy Governance', + loadComponent: () => + import('../policy-governance/trust-weighting.component').then( + (m) => m.TrustWeightingComponent, + ), + }, + { + path: 'staleness', + title: 'Policy Governance', + loadComponent: () => + import('../policy-governance/staleness-config.component').then( + (m) => m.StalenessConfigComponent, + ), + }, + { + path: 'sealed-mode', + title: 'Policy Governance', + loadComponent: () => + import('../policy-governance/sealed-mode-control.component').then( + (m) => m.SealedModeControlComponent, + ), + }, + { + path: 'sealed-mode/overrides', + title: 'Policy Governance', + loadComponent: () => + import('../policy-governance/sealed-mode-overrides.component').then( + (m) => m.SealedModeOverridesComponent, + ), + }, + { + path: 'profiles', + title: 'Policy Governance', + loadComponent: () => + import('../policy-governance/risk-profile-list.component').then( + (m) => m.RiskProfileListComponent, + ), + }, + { + path: 'profiles/new', + title: 'Policy Governance', + loadComponent: () => + import('../policy-governance/risk-profile-editor.component').then( + (m) => m.RiskProfileEditorComponent, + ), + }, + { + path: 'profiles/:profileId', + title: 'Policy Governance', + loadComponent: () => + import('../policy-governance/risk-profile-editor.component').then( + (m) => m.RiskProfileEditorComponent, + ), + }, + { + path: 'validator', + title: 'Policy Governance', + loadComponent: () => + import('../policy-governance/policy-validator.component').then( + (m) => m.PolicyValidatorComponent, + ), + }, + { + path: 'conflicts', + title: 'Policy Governance', + loadComponent: () => + import('../policy-governance/policy-conflict-dashboard.component').then( + (m) => m.PolicyConflictDashboardComponent, + ), + }, + { + path: 'conflicts/:conflictId/resolve', + title: 'Policy Governance', + loadComponent: () => + import('../policy-governance/conflict-resolution-wizard.component').then( + (m) => m.ConflictResolutionWizardComponent, + ), + }, + { + path: 'impact-preview', + title: 'Policy Governance', + loadComponent: () => + import('../policy-governance/impact-preview.component').then( + (m) => m.ImpactPreviewComponent, + ), + }, + { + path: 'schema-playground', + title: 'Policy Governance', + loadComponent: () => + import('../policy-governance/schema-playground.component').then( + (m) => m.SchemaPlaygroundComponent, + ), + }, + { + path: 'schema-docs', + title: 'Policy Governance', + loadComponent: () => + import('../policy-governance/schema-docs.component').then( + (m) => m.SchemaDocsComponent, + ), + }, { path: 'baselines', - pathMatch: 'full', - redirectTo: 'overview', + title: 'Policy Packs', + loadComponent: () => + import('../policy-studio/workspace/policy-workspace.component').then( + (m) => m.PolicyWorkspaceComponent, + ), }, { path: 'waivers', - pathMatch: 'full', - redirectTo: 'vex/exceptions', + title: 'VEX & Exceptions', + loadComponent: () => + import('../exceptions/exception-dashboard.component').then( + (m) => m.ExceptionDashboardComponent, + ), }, { path: 'exceptions', - pathMatch: 'full', - redirectTo: 'vex/exceptions', + title: 'VEX & Exceptions', + loadComponent: () => + import('../exceptions/exception-dashboard.component').then( + (m) => m.ExceptionDashboardComponent, + ), }, { path: 'packs', diff --git a/src/Web/StellaOps.Web/src/tests/policy_decisioning/policy-decisioning-routes.spec.ts b/src/Web/StellaOps.Web/src/tests/policy_decisioning/policy-decisioning-routes.spec.ts index dfb68439c..4a6b0edfa 100644 --- a/src/Web/StellaOps.Web/src/tests/policy_decisioning/policy-decisioning-routes.spec.ts +++ b/src/Web/StellaOps.Web/src/tests/policy_decisioning/policy-decisioning-routes.spec.ts @@ -19,6 +19,23 @@ describe('policyDecisioningRoutes', () => { ); }); + it('preserves governance aliases as canonical top-level policy routes', () => { + const routeByPath = new Map(children.map((route) => [route.path, route] as const)); + + expect(routeByPath.get('risk-budget')?.loadComponent).toEqual(jasmine.any(Function)); + expect(routeByPath.get('baselines')?.loadComponent).toEqual(jasmine.any(Function)); + expect(routeByPath.get('budget')?.loadComponent).toEqual(jasmine.any(Function)); + expect(routeByPath.get('risk-budget/config')?.loadComponent).toEqual(jasmine.any(Function)); + expect(routeByPath.get('budget/config')?.loadComponent).toEqual(jasmine.any(Function)); + expect(routeByPath.get('trust-weights')?.loadComponent).toEqual(jasmine.any(Function)); + expect(routeByPath.get('staleness')?.loadComponent).toEqual(jasmine.any(Function)); + expect(routeByPath.get('sealed-mode')?.loadComponent).toEqual(jasmine.any(Function)); + expect(routeByPath.get('profiles')?.loadComponent).toEqual(jasmine.any(Function)); + expect(routeByPath.get('validator')?.loadComponent).toEqual(jasmine.any(Function)); + expect(routeByPath.get('waivers')?.loadComponent).toEqual(jasmine.any(Function)); + expect(routeByPath.get('exceptions')?.loadComponent).toEqual(jasmine.any(Function)); + }); + it('keeps pack authoring subviews inside the packs shell', () => { const packsRoute = children.find((route) => route.path === 'packs'); const packPaths = packsRoute?.children?.map((route) => route.path) ?? []; diff --git a/src/Web/StellaOps.Web/src/tests/policy_decisioning/policy-decisioning-shell.component.spec.ts b/src/Web/StellaOps.Web/src/tests/policy_decisioning/policy-decisioning-shell.component.spec.ts index 8a9d4ba79..b9b79cf86 100644 --- a/src/Web/StellaOps.Web/src/tests/policy_decisioning/policy-decisioning-shell.component.spec.ts +++ b/src/Web/StellaOps.Web/src/tests/policy_decisioning/policy-decisioning-shell.component.spec.ts @@ -100,6 +100,25 @@ describe('PolicyDecisioningShellComponent', () => { expect(navigateByUrlSpy).toHaveBeenCalledWith('/releases/rel-42'); }); + + it('treats preserved governance aliases as governance tab routes', () => { + const fixture = createShell('/ops/policy/risk-budget'); + const component = fixture.componentInstance; + + expect(component.shellState().kind).toBe('global'); + expect(component.shellState().activeTab).toBe('governance'); + expect(component.primaryTabs().find((tab) => tab.id === 'governance')?.route).toEqual([ + '/ops/policy/governance', + ]); + }); + + it('treats preserved baseline and waiver aliases as first-class shell tabs', () => { + const packsFixture = createShell('/ops/policy/baselines'); + expect(packsFixture.componentInstance.shellState().activeTab).toBe('packs'); + + const vexFixture = createShell('/ops/policy/waivers'); + expect(vexFixture.componentInstance.shellState().activeTab).toBe('vex'); + }); }); function buildSnapshot( diff --git a/src/Web/StellaOps.Web/tests/e2e/prealpha-canonical-full-sweep.spec.ts b/src/Web/StellaOps.Web/tests/e2e/prealpha-canonical-full-sweep.spec.ts index 9833fbc34..d576d457b 100644 --- a/src/Web/StellaOps.Web/tests/e2e/prealpha-canonical-full-sweep.spec.ts +++ b/src/Web/StellaOps.Web/tests/e2e/prealpha-canonical-full-sweep.spec.ts @@ -193,7 +193,10 @@ const strictRouteExpectations: Partial { await expect(page.locator('#main-content')).toContainText('Attestation Coverage Metrics'); }); - test('mission board reachability card opens Reachability Center', async ({ page }) => { + test('mission board reachability card opens Reachability workspace', async ({ page }) => { await page.goto('/mission-control/board', { waitUntil: 'domcontentloaded' }); await page.locator('#main-content a[href="/security/reachability"]').first().click(); await expect(page).toHaveURL(/\/security\/reachability$/); await expect(page).toHaveTitle(/Reachability/i); - await expect(page.locator('#main-content')).toContainText('Reachability Center'); + await expect(page.locator('#main-content')).toContainText( + 'Coverage, witnesses, proof-of-exposure artifacts, and sensor gaps stay in one investigation shell.' + ); }); test('setup trust-signing tabs stay under setup routes', async ({ page }) => {