Preserve mission control scope through context hydration
This commit is contained in:
@@ -21,7 +21,7 @@
|
||||
## Delivery Tracker
|
||||
|
||||
### FE-MISSION-001 - Reproduce scope loss from mission action links
|
||||
Status: DOING
|
||||
Status: DONE
|
||||
Dependency: none
|
||||
Owners: QA
|
||||
Task description:
|
||||
@@ -29,11 +29,11 @@ Task description:
|
||||
- Capture which links drop `tenant`, `regions`, and `environments` when leaving mission-control surfaces.
|
||||
|
||||
Completion criteria:
|
||||
- [ ] Live Playwright evidence shows the exact mission actions that lose active scope.
|
||||
- [ ] The defect is reduced to a specific link-contract pattern instead of a generic downstream page issue.
|
||||
- [x] Live Playwright evidence shows the exact mission actions that lose active scope.
|
||||
- [x] The defect is reduced to a specific link-contract pattern instead of a generic downstream page issue.
|
||||
|
||||
### FE-MISSION-002 - Make mission action links explicitly preserve active scope
|
||||
Status: TODO
|
||||
Status: DONE
|
||||
Dependency: FE-MISSION-001
|
||||
Owners: Developer
|
||||
Task description:
|
||||
@@ -41,11 +41,11 @@ Task description:
|
||||
- Keep the fix scoped to the action layer so downstream pages receive the already-selected global context without route-specific patches.
|
||||
|
||||
Completion criteria:
|
||||
- [ ] Mission-board summary, alert, activity, and cross-domain action links preserve active scope during navigation.
|
||||
- [ ] Mission-control alerts and activity action links preserve active scope during navigation.
|
||||
- [x] Mission-board summary, alert, activity, and cross-domain action links preserve active scope during navigation.
|
||||
- [x] Mission-control alerts and activity action links preserve active scope during navigation.
|
||||
|
||||
### FE-MISSION-003 - Add focused Angular coverage for scope-preserving links
|
||||
Status: TODO
|
||||
Status: DONE
|
||||
Dependency: FE-MISSION-002
|
||||
Owners: Test Automation
|
||||
Task description:
|
||||
@@ -53,11 +53,11 @@ Task description:
|
||||
- Keep the coverage under `src/app/core/testing` so it remains in the focused Angular test include set.
|
||||
|
||||
Completion criteria:
|
||||
- [ ] Focused Angular tests assert the relevant mission links use `queryParamsHandling="merge"`.
|
||||
- [ ] The tests fail before the fix and pass after it.
|
||||
- [x] Focused Angular tests assert the relevant mission links use `queryParamsHandling="merge"`.
|
||||
- [x] The tests fail before the fix and pass after it.
|
||||
|
||||
### FE-MISSION-004 - Replay mission actions live after the patch
|
||||
Status: TODO
|
||||
Status: DONE
|
||||
Dependency: FE-MISSION-003
|
||||
Owners: QA
|
||||
Task description:
|
||||
@@ -65,17 +65,23 @@ Task description:
|
||||
- Confirm the downstream pages keep the original tenant/region/environment scope after navigation.
|
||||
|
||||
Completion criteria:
|
||||
- [ ] Live Playwright confirms approvals, disposition, data-integrity, and release-runs actions retain the scoped query string.
|
||||
- [ ] Live Playwright confirms the fixed links no longer depend on destination-route behavior to keep context.
|
||||
- [x] Live Playwright confirms approvals, disposition, data-integrity, and release-runs actions retain the scoped query string.
|
||||
- [x] Live Playwright confirms the fixed links no longer depend on destination-route behavior to keep context.
|
||||
|
||||
## Execution Log
|
||||
| Date (UTC) | Update | Owner |
|
||||
| --- | --- | --- |
|
||||
| 2026-03-07 | Sprint created after live Playwright from `/mission-control/alerts` and `/mission-control/activity` showed several actions dropping `tenant=demo-prod®ions=us-east&environments=stage` on navigation, while others kept it only by route-specific accident. | QA |
|
||||
| 2026-03-07 | Updated mission-board plus dedicated mission alerts/activity links to use explicit `queryParamsHandling=\"merge\"` and added focused coverage in `src/app/core/testing/mission-scope-links.component.spec.ts`; focused Angular run passed for `dashboard-v3`, mission-scope-links, and `platform-context.store`. | Developer |
|
||||
| 2026-03-07 | Live Playwright on the rebuilt stack confirmed the original broken mission links now keep `tenant`, `regions`, and `environments`, but a deeper race remained: fresh authenticated navigations could still collapse scoped URLs back to tenant-only during global context initialization. | QA |
|
||||
| 2026-03-07 | Fixed the context hydration race in `PlatformContextStore` by retaining the latest pending scope query until initialization completes, then added a focused store regression covering late-arriving route scope during bootstrap. | Developer |
|
||||
| 2026-03-07 | Rebuilt the Web bundle, synced `dist/stellaops-web/browser` into the live `compose_console-dist` volume, and re-ran live Playwright on `/mission-control/board`, `/mission-control/alerts`, and `/mission-control/activity` with `timeWindow=7d`. Source URLs and representative actions now retain `tenant=demo-prod®ions=us-east&environments=stage&timeWindow=7d` through downstream routes such as `/releases/approvals`, `/security/disposition`, and `/releases/runs`. | QA |
|
||||
|
||||
## Decisions & Risks
|
||||
- Decision: fix scope preservation at the mission-link layer with explicit query-param merge semantics rather than modifying every downstream route that currently receives an empty or partial query string.
|
||||
- Risk: the main dashboard uses the same static mission links, so the fix must cover both the dedicated mission-control pages and the mission-board surface in the same iteration.
|
||||
- Decision: treat the later tenant-only URL collapse as a context-initialization defect in `PlatformContextStore`, not another mission-page routing issue, because the scoped route arrived before context initialization completed and the store only remembered the first query override.
|
||||
- Decision: keep the default `24h` time window omission from `scopeQueryPatch()` unchanged; live verification used a non-default `7d` value to prove that explicit time-window scope now survives initialization and navigation.
|
||||
|
||||
## Next Checkpoints
|
||||
- 2026-03-07: land the scope-preserving mission action patch and focused Angular coverage.
|
||||
|
||||
@@ -118,4 +118,63 @@ describe('PlatformContextStore', () => {
|
||||
expect(store.loading()).toBe(false);
|
||||
expect(store.error()).toBeNull();
|
||||
});
|
||||
|
||||
it('keeps the latest route scope when query params arrive before initialization finishes', () => {
|
||||
store.initialize();
|
||||
|
||||
const regionsReq = httpMock.expectOne('/api/v2/context/regions');
|
||||
regionsReq.flush([
|
||||
{
|
||||
regionId: 'us-east',
|
||||
displayName: 'US East',
|
||||
sortOrder: 10,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
regionId: 'eu-west',
|
||||
displayName: 'EU West',
|
||||
sortOrder: 20,
|
||||
enabled: true,
|
||||
},
|
||||
]);
|
||||
|
||||
store.applyScopeQueryParams({
|
||||
tenant: 'demo-prod',
|
||||
regions: 'us-east',
|
||||
environments: 'stage',
|
||||
timeWindow: '7d',
|
||||
});
|
||||
|
||||
const preferencesReq = httpMock.expectOne('/api/v2/context/preferences');
|
||||
preferencesReq.flush({
|
||||
tenantId: null,
|
||||
actorId: 'context-tests',
|
||||
regions: [],
|
||||
environments: [],
|
||||
timeWindow: '24h',
|
||||
updatedAt: '2026-03-07T00:00:00Z',
|
||||
updatedBy: 'context-tests',
|
||||
});
|
||||
|
||||
const environmentsReq = httpMock.expectOne((request) =>
|
||||
request.url === '/api/v2/context/environments'
|
||||
&& request.params.get('regions') === 'us-east'
|
||||
);
|
||||
environmentsReq.flush([
|
||||
{
|
||||
environmentId: 'stage',
|
||||
regionId: 'us-east',
|
||||
environmentType: 'staging',
|
||||
displayName: 'Staging',
|
||||
sortOrder: 10,
|
||||
enabled: true,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(store.tenantId()).toBe('demo-prod');
|
||||
expect(store.selectedRegions()).toEqual(['us-east']);
|
||||
expect(store.selectedEnvironments()).toEqual(['stage']);
|
||||
expect(store.timeWindow()).toBe('7d');
|
||||
expect(store.error()).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -59,7 +59,7 @@ export class PlatformContextStore {
|
||||
private readonly authSession = inject(AuthSessionStore);
|
||||
private persistPaused = false;
|
||||
private readonly apiDisabled = this.shouldDisableApiCalls();
|
||||
private readonly initialQueryOverride = this.readScopeQueryFromLocation();
|
||||
private pendingQueryOverride: PlatformContextQueryState | null = this.readScopeQueryFromLocation();
|
||||
|
||||
readonly regions = signal<PlatformContextRegion[]>([]);
|
||||
readonly environments = signal<PlatformContextEnvironment[]>([]);
|
||||
@@ -116,7 +116,7 @@ export class PlatformContextStore {
|
||||
}
|
||||
|
||||
if (this.apiDisabled) {
|
||||
this.tenantId.set(this.initialQueryOverride?.tenantId ?? null);
|
||||
this.tenantId.set(this.pendingQueryOverride?.tenantId ?? null);
|
||||
this.loading.set(false);
|
||||
this.error.set(null);
|
||||
this.initialized.set(true);
|
||||
@@ -227,15 +227,17 @@ export class PlatformContextStore {
|
||||
}
|
||||
|
||||
applyScopeQueryParams(queryParams: Record<string, unknown>): void {
|
||||
const queryState = this.parseScopeQueryState(queryParams);
|
||||
if (!this.initialized()) {
|
||||
this.pendingQueryOverride = queryState;
|
||||
return;
|
||||
}
|
||||
|
||||
const queryState = this.parseScopeQueryState(queryParams);
|
||||
if (!queryState) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.pendingQueryOverride = queryState;
|
||||
const nextTenantId = this.normalizeTenantId(queryState.tenantId);
|
||||
const allowedRegions = this.regions().map((item) => item.regionId);
|
||||
const nextRegions = this.normalizeIds(queryState.regions, allowedRegions);
|
||||
@@ -300,7 +302,7 @@ export class PlatformContextStore {
|
||||
timeWindow: (prefs?.timeWindow ?? DEFAULT_TIME_WINDOW).trim() || DEFAULT_TIME_WINDOW,
|
||||
stage: (prefs?.stage ?? DEFAULT_STAGE).trim().toLowerCase() || DEFAULT_STAGE,
|
||||
};
|
||||
const hydrated = this.mergeWithInitialQueryOverride(preferenceState);
|
||||
const hydrated = this.mergeWithPendingQueryOverride(preferenceState);
|
||||
const preferredRegions = this.normalizeIds(
|
||||
hydrated.regions,
|
||||
this.regions().map((item) => item.regionId),
|
||||
@@ -313,7 +315,7 @@ export class PlatformContextStore {
|
||||
},
|
||||
error: () => {
|
||||
// Preferences are optional; continue with default empty context.
|
||||
const fallbackState = this.mergeWithInitialQueryOverride({
|
||||
const fallbackState = this.mergeWithPendingQueryOverride({
|
||||
tenantId: null,
|
||||
regions: [],
|
||||
environments: [],
|
||||
@@ -419,8 +421,8 @@ export class PlatformContextStore {
|
||||
this.persistPaused = false;
|
||||
}
|
||||
|
||||
private mergeWithInitialQueryOverride(baseState: PlatformContextQueryState): PlatformContextQueryState {
|
||||
const override = this.initialQueryOverride;
|
||||
private mergeWithPendingQueryOverride(baseState: PlatformContextQueryState): PlatformContextQueryState {
|
||||
const override = this.pendingQueryOverride;
|
||||
if (!override) {
|
||||
return baseState;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { RouterLink, provideRouter } from '@angular/router';
|
||||
import { By } from '@angular/platform-browser';
|
||||
|
||||
import { PlatformContextStore } from '../context/platform-context.store';
|
||||
import { DashboardV3Component } from '../../features/dashboard-v3/dashboard-v3.component';
|
||||
import { MissionAlertsPageComponent } from '../../features/mission-control/mission-alerts-page.component';
|
||||
import { MissionActivityPageComponent } from '../../features/mission-control/mission-activity-page.component';
|
||||
|
||||
function routerLinksFor<T>(component: T): RouterLink[] {
|
||||
const fixture = TestBed.createComponent(component as never);
|
||||
fixture.detectChanges();
|
||||
return fixture.debugElement.queryAll(By.directive(RouterLink)).map((debugElement) => debugElement.injector.get(RouterLink));
|
||||
}
|
||||
|
||||
describe('Mission scope-preserving links', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [DashboardV3Component, MissionAlertsPageComponent, MissionActivityPageComponent],
|
||||
providers: [
|
||||
provideRouter([]),
|
||||
{
|
||||
provide: PlatformContextStore,
|
||||
useValue: {
|
||||
initialize: () => undefined,
|
||||
regions: () => [
|
||||
{ regionId: 'eu-west', displayName: 'EU West' },
|
||||
{ regionId: 'us-east', displayName: 'US East' },
|
||||
],
|
||||
environments: () => [
|
||||
{
|
||||
environmentId: 'dev',
|
||||
regionId: 'eu-west',
|
||||
environmentType: 'development',
|
||||
displayName: 'Development EU West',
|
||||
},
|
||||
{
|
||||
environmentId: 'stage',
|
||||
regionId: 'us-east',
|
||||
environmentType: 'staging',
|
||||
displayName: 'Staging US East',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('marks every mission alerts link to merge the active query scope', () => {
|
||||
const links = routerLinksFor(MissionAlertsPageComponent);
|
||||
|
||||
expect(links.length).toBe(3);
|
||||
expect(links.every((link) => link.queryParamsHandling === 'merge')).toBeTrue();
|
||||
});
|
||||
|
||||
it('marks every mission activity link to merge the active query scope', () => {
|
||||
const links = routerLinksFor(MissionActivityPageComponent);
|
||||
|
||||
expect(links.length).toBe(3);
|
||||
expect(links.every((link) => link.queryParamsHandling === 'merge')).toBeTrue();
|
||||
});
|
||||
|
||||
it('marks dashboard mission navigation links to merge the active query scope', () => {
|
||||
const links = routerLinksFor(DashboardV3Component);
|
||||
|
||||
expect(links.length).toBeGreaterThan(0);
|
||||
expect(links.every((link) => link.queryParamsHandling === 'merge')).toBeTrue();
|
||||
});
|
||||
});
|
||||
@@ -101,19 +101,19 @@ interface MissionSummary {
|
||||
<div class="summary-card" [class.warning]="summary().blockedPromotions > 0">
|
||||
<div class="summary-value">{{ summary().activePromotions }}</div>
|
||||
<div class="summary-label">Active Promotions</div>
|
||||
<a routerLink="/releases/runs" class="summary-link">View all</a>
|
||||
<a routerLink="/releases/runs" queryParamsHandling="merge" class="summary-link">View all</a>
|
||||
</div>
|
||||
|
||||
<div class="summary-card" [class.critical]="summary().blockedPromotions > 0">
|
||||
<div class="summary-value">{{ summary().blockedPromotions }}</div>
|
||||
<div class="summary-label">Blocked Promotions</div>
|
||||
<a routerLink="/releases/approvals" class="summary-link">Review</a>
|
||||
<a routerLink="/releases/approvals" queryParamsHandling="merge" class="summary-link">Review</a>
|
||||
</div>
|
||||
|
||||
<div class="summary-card">
|
||||
<div class="summary-value env-name">{{ summary().highestRiskEnv }}</div>
|
||||
<div class="summary-label">Highest Risk Environment</div>
|
||||
<a routerLink="/security" class="summary-link">Risk detail</a>
|
||||
<a routerLink="/security" queryParamsHandling="merge" class="summary-link">Risk detail</a>
|
||||
</div>
|
||||
|
||||
<div class="summary-card" [class.warning]="summary().dataIntegrityStatus === 'degraded'"
|
||||
@@ -123,7 +123,7 @@ interface MissionSummary {
|
||||
{{ summary().dataIntegrityStatus | titlecase }}
|
||||
</div>
|
||||
<div class="summary-label">Data Integrity</div>
|
||||
<a routerLink="/ops/operations/data-integrity" class="summary-link">Ops detail</a>
|
||||
<a routerLink="/ops/operations/data-integrity" queryParamsHandling="merge" class="summary-link">Ops detail</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -131,7 +131,7 @@ interface MissionSummary {
|
||||
<section class="pipeline-board" aria-label="Regional pipeline board">
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">Regional Pipeline</h2>
|
||||
<a routerLink="/setup/topology/environments" class="section-link">All environments</a>
|
||||
<a routerLink="/setup/topology/environments" queryParamsHandling="merge" class="section-link">All environments</a>
|
||||
</div>
|
||||
|
||||
<div class="env-grid">
|
||||
@@ -213,7 +213,7 @@ interface MissionSummary {
|
||||
<section class="risk-table" aria-label="Environments at risk">
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">Environments at Risk</h2>
|
||||
<a routerLink="/setup/topology/environments" class="section-link">Open environments</a>
|
||||
<a routerLink="/setup/topology/environments" queryParamsHandling="merge" class="section-link">Open environments</a>
|
||||
</div>
|
||||
|
||||
@if (riskEnvironments().length === 0) {
|
||||
@@ -266,7 +266,7 @@ interface MissionSummary {
|
||||
<section class="domain-card" aria-label="SBOM snapshot">
|
||||
<div class="card-header">
|
||||
<h2 class="card-title">SBOM Findings Snapshot</h2>
|
||||
<a routerLink="/security/sbom-lake" class="card-link">View SBOM</a>
|
||||
<a routerLink="/security/sbom-lake" queryParamsHandling="merge" class="card-link">View SBOM</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="snapshot-stat">
|
||||
@@ -288,8 +288,8 @@ interface MissionSummary {
|
||||
}
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<a routerLink="/security/findings" [queryParams]="{ reachability: 'critical' }" class="card-action">Open Findings</a>
|
||||
<a routerLink="/releases/runs" class="card-action">Release Runs</a>
|
||||
<a routerLink="/security/findings" [queryParams]="{ reachability: 'critical' }" queryParamsHandling="merge" class="card-action">Open Findings</a>
|
||||
<a routerLink="/releases/runs" queryParamsHandling="merge" class="card-action">Release Runs</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -297,7 +297,7 @@ interface MissionSummary {
|
||||
<section class="domain-card" aria-label="Reachability summary">
|
||||
<div class="card-header">
|
||||
<h2 class="card-title">Reachability</h2>
|
||||
<a routerLink="/security/reachability" class="card-link">View reachability</a>
|
||||
<a routerLink="/security/reachability" queryParamsHandling="merge" class="card-link">View reachability</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="bir-matrix">
|
||||
@@ -328,7 +328,7 @@ interface MissionSummary {
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<a routerLink="/security/reachability" class="card-action">Deep analysis</a>
|
||||
<a routerLink="/security/reachability" queryParamsHandling="merge" class="card-action">Deep analysis</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -336,7 +336,7 @@ interface MissionSummary {
|
||||
<section class="domain-card" aria-label="Nightly ops signals">
|
||||
<div class="card-header">
|
||||
<h2 class="card-title">Nightly Ops Signals</h2>
|
||||
<a routerLink="/ops/operations/data-integrity" class="card-link">Open Data Integrity</a>
|
||||
<a routerLink="/ops/operations/data-integrity" queryParamsHandling="merge" class="card-link">Open Data Integrity</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@for (signal of nightlyOpsSignals(); track signal.id) {
|
||||
@@ -350,7 +350,7 @@ interface MissionSummary {
|
||||
}
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<a routerLink="/ops/operations/data-integrity" class="card-action">Open Data Integrity</a>
|
||||
<a routerLink="/ops/operations/data-integrity" queryParamsHandling="merge" class="card-action">Open Data Integrity</a>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@@ -362,9 +362,9 @@ interface MissionSummary {
|
||||
</div>
|
||||
<div class="alerts-card">
|
||||
<ul class="alerts-list">
|
||||
<li><a routerLink="/releases/approvals">3 approvals blocked by policy gate evidence freshness</a></li>
|
||||
<li><a routerLink="/security/disposition">2 waivers expiring within 24h</a></li>
|
||||
<li><a routerLink="/ops/operations/data-integrity">Feed freshness degraded for advisory ingest</a></li>
|
||||
<li><a routerLink="/releases/approvals" queryParamsHandling="merge">3 approvals blocked by policy gate evidence freshness</a></li>
|
||||
<li><a routerLink="/security/disposition" queryParamsHandling="merge">2 waivers expiring within 24h</a></li>
|
||||
<li><a routerLink="/ops/operations/data-integrity" queryParamsHandling="merge">Feed freshness degraded for advisory ingest</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
@@ -378,40 +378,40 @@ interface MissionSummary {
|
||||
<article class="activity-card">
|
||||
<h3 class="activity-card-title">Release Runs</h3>
|
||||
<p class="activity-card-desc">Latest standard and hotfix promotions with gate checkpoints.</p>
|
||||
<a routerLink="/releases/runs" class="activity-card-link">Open Runs</a>
|
||||
<a routerLink="/releases/runs" queryParamsHandling="merge" class="activity-card-link">Open Runs</a>
|
||||
</article>
|
||||
<article class="activity-card">
|
||||
<h3 class="activity-card-title">Evidence</h3>
|
||||
<p class="activity-card-desc">Newest decision capsules and replay verification outcomes.</p>
|
||||
<a routerLink="/evidence/capsules" class="activity-card-link">Open Capsules</a>
|
||||
<a routerLink="/evidence/capsules" queryParamsHandling="merge" class="activity-card-link">Open Capsules</a>
|
||||
</article>
|
||||
<article class="activity-card">
|
||||
<h3 class="activity-card-title">Audit</h3>
|
||||
<p class="activity-card-desc">Unified activity trail by actor, resource, and correlation key.</p>
|
||||
<a routerLink="/evidence/audit-log" class="activity-card-link">Open Audit Log</a>
|
||||
<a routerLink="/evidence/audit-log" queryParamsHandling="merge" class="activity-card-link">Open Audit Log</a>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Cross-domain navigation links -->
|
||||
<nav class="domain-nav" aria-label="Domain navigation">
|
||||
<a routerLink="/releases/runs" class="domain-nav-item">
|
||||
<a routerLink="/releases/runs" queryParamsHandling="merge" class="domain-nav-item">
|
||||
<span class="domain-icon">▶</span>
|
||||
Release Runs
|
||||
</a>
|
||||
<a routerLink="/security" class="domain-nav-item">
|
||||
<a routerLink="/security" queryParamsHandling="merge" class="domain-nav-item">
|
||||
<span class="domain-icon">■</span>
|
||||
Security & Risk
|
||||
</a>
|
||||
<a routerLink="/ops/operations" class="domain-nav-item">
|
||||
<a routerLink="/ops/operations" queryParamsHandling="merge" class="domain-nav-item">
|
||||
<span class="domain-icon">◆</span>
|
||||
Platform
|
||||
</a>
|
||||
<a routerLink="/evidence" class="domain-nav-item">
|
||||
<a routerLink="/evidence" queryParamsHandling="merge" class="domain-nav-item">
|
||||
<span class="domain-icon">●</span>
|
||||
Evidence (Decision Capsules)
|
||||
</a>
|
||||
<a routerLink="/ops/platform-setup" class="domain-nav-item">
|
||||
<a routerLink="/ops/platform-setup" queryParamsHandling="merge" class="domain-nav-item">
|
||||
<span class="domain-icon">⚙</span>
|
||||
Platform Setup
|
||||
</a>
|
||||
|
||||
@@ -17,17 +17,17 @@ import { RouterLink } from '@angular/router';
|
||||
<article>
|
||||
<h2>Release Runs</h2>
|
||||
<p>Latest standard and hotfix promotions with gate checkpoints.</p>
|
||||
<a routerLink="/releases/runs">Open Runs</a>
|
||||
<a routerLink="/releases/runs" queryParamsHandling="merge">Open Runs</a>
|
||||
</article>
|
||||
<article>
|
||||
<h2>Evidence</h2>
|
||||
<p>Newest decision capsules and replay verification outcomes.</p>
|
||||
<a routerLink="/evidence/capsules">Open Capsules</a>
|
||||
<a routerLink="/evidence/capsules" queryParamsHandling="merge">Open Capsules</a>
|
||||
</article>
|
||||
<article>
|
||||
<h2>Audit</h2>
|
||||
<p>Unified activity trail by actor, resource, and correlation key.</p>
|
||||
<a routerLink="/evidence/audit-log">Open Audit Log</a>
|
||||
<a routerLink="/evidence/audit-log" queryParamsHandling="merge">Open Audit Log</a>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -14,9 +14,9 @@ import { RouterLink } from '@angular/router';
|
||||
</header>
|
||||
|
||||
<ul>
|
||||
<li><a routerLink="/releases/approvals">3 approvals blocked by policy gate evidence freshness</a></li>
|
||||
<li><a routerLink="/security/disposition">2 waivers expiring within 24h</a></li>
|
||||
<li><a routerLink="/ops/operations/data-integrity">Feed freshness degraded for advisory ingest</a></li>
|
||||
<li><a routerLink="/releases/approvals" queryParamsHandling="merge">3 approvals blocked by policy gate evidence freshness</a></li>
|
||||
<li><a routerLink="/security/disposition" queryParamsHandling="merge">2 waivers expiring within 24h</a></li>
|
||||
<li><a routerLink="/ops/operations/data-integrity" queryParamsHandling="merge">Feed freshness degraded for advisory ingest</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
`,
|
||||
|
||||
Reference in New Issue
Block a user