Keep trust-signing flows under setup routes
This commit is contained in:
@@ -79,6 +79,9 @@ Completion criteria:
|
|||||||
| 2026-03-06 | Follow-up mission-board QA found a semantic navigation defect: Playwright DOM capture showed both Reachability card actions (`View reachability`, `Deep analysis`) pointing at `/security/findings` even though the working canonical reachability surface is `/security/reachability`. | QA |
|
| 2026-03-06 | Follow-up mission-board QA found a semantic navigation defect: Playwright DOM capture showed both Reachability card actions (`View reachability`, `Deep analysis`) pointing at `/security/findings` even though the working canonical reachability surface is `/security/reachability`. | QA |
|
||||||
| 2026-03-06 | Updated the mission-board Reachability card actions to target `/security/reachability` and added focused Playwright coverage for the canonical reachability route plus the mission-board click-through. | Developer (FE) |
|
| 2026-03-06 | Updated the mission-board Reachability card actions to target `/security/reachability` and added focused Playwright coverage for the canonical reachability route plus the mission-board click-through. | Developer (FE) |
|
||||||
| 2026-03-06 | Verification: targeted Playwright slice passed locally (`npx playwright test tests/e2e/prealpha-canonical-full-sweep.spec.ts --grep \"route works: /security/reachability|mission board reachability card opens Reachability Center\"` -> 2/2 pass). Frontend bundle rebuilt, synced into `compose_console-dist`, and live authenticated Playwright confirmed the mission-board Reachability action now lands on `https://stella-ops.local/security/reachability` with `Reachability Center` visible. | QA |
|
| 2026-03-06 | Verification: targeted Playwright slice passed locally (`npx playwright test tests/e2e/prealpha-canonical-full-sweep.spec.ts --grep \"route works: /security/reachability|mission board reachability card opens Reachability Center\"` -> 2/2 pass). Frontend bundle rebuilt, synced into `compose_console-dist`, and live authenticated Playwright confirmed the mission-board Reachability action now lands on `https://stella-ops.local/security/reachability` with `Reachability Center` visible. | QA |
|
||||||
|
| 2026-03-07 | Setup-surface QA found a canonical drift defect inside Trust & Signing: from `/setup/trust-signing`, Playwright showed in-page tabs rendering `/administration/trust-signing/*` hrefs, and clicking `Signing Keys` navigated to the retired administration tree instead of staying under setup. | QA |
|
||||||
|
| 2026-03-07 | Fixed Trust & Signing to load directly under `setup.routes.ts`, repointed evidence-related setup/trust links to `/setup/trust-signing`, and added Playwright coverage for the canonical setup trust route and tab navigation. | Developer (FE) |
|
||||||
|
| 2026-03-07 | Verification: the new setup trust-signing Playwright slice initially exposed a harness gap in trust API fixtures; added deterministic trust dashboard/key stubs to `prealpha-canonical-full-sweep.spec.ts`, then reran the slice successfully (`npx playwright test tests/e2e/prealpha-canonical-full-sweep.spec.ts --grep \"route works: /setup/trust-signing|setup trust-signing tabs stay under setup routes\"` -> 2/2 pass). Frontend bundle was synced into `compose_console-dist`, and live authenticated Playwright confirmed `Signing Keys` now keeps users on `https://stella-ops.local/setup/trust-signing/keys`. | QA |
|
||||||
|
|
||||||
## Decisions & Risks
|
## Decisions & Risks
|
||||||
- Decision: this sprint stays inside `src/Web/StellaOps.Web` plus required sprint/doc updates only.
|
- Decision: this sprint stays inside `src/Web/StellaOps.Web` plus required sprint/doc updates only.
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ export const routes: Routes = [
|
|||||||
{ path: 'bundles', redirectTo: '/releases/bundles', pathMatch: 'full' },
|
{ path: 'bundles', redirectTo: '/releases/bundles', pathMatch: 'full' },
|
||||||
{ path: 'replay', redirectTo: '/evidence/verify-replay', pathMatch: 'full' },
|
{ path: 'replay', redirectTo: '/evidence/verify-replay', pathMatch: 'full' },
|
||||||
{ path: 'proofs', redirectTo: '/evidence/capsules', pathMatch: 'full' },
|
{ path: 'proofs', redirectTo: '/evidence/capsules', pathMatch: 'full' },
|
||||||
{ path: 'trust-signing', redirectTo: '/administration/trust-signing', pathMatch: 'full' },
|
{ path: 'trust-signing', redirectTo: '/setup/trust-signing', pathMatch: 'full' },
|
||||||
{ path: '**', redirectTo: '/evidence/overview' },
|
{ path: '**', redirectTo: '/evidence/overview' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ type EvidenceHomeMode = 'normal' | 'degraded' | 'empty';
|
|||||||
<a routerLink="/releases/bundles" class="shortcut-link">Evidence Bundles</a>
|
<a routerLink="/releases/bundles" class="shortcut-link">Evidence Bundles</a>
|
||||||
<a routerLink="/evidence/verify-replay" class="shortcut-link">Replay & Verify</a>
|
<a routerLink="/evidence/verify-replay" class="shortcut-link">Replay & Verify</a>
|
||||||
<a routerLink="/evidence/capsules" class="shortcut-link">Proof Chains</a>
|
<a routerLink="/evidence/capsules" class="shortcut-link">Proof Chains</a>
|
||||||
<a routerLink="/administration/trust-signing" class="shortcut-link">Trust & Signing</a>
|
<a routerLink="/setup/trust-signing" class="shortcut-link">Trust & Signing</a>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -158,7 +158,7 @@ type EvidenceHomeMode = 'normal' | 'degraded' | 'empty';
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a routerLink="/administration/trust-signing" class="cross-link">
|
<a routerLink="/setup/trust-signing" class="cross-link">
|
||||||
<span class="cross-link-icon" aria-hidden="true">■</span>
|
<span class="cross-link-icon" aria-hidden="true">■</span>
|
||||||
<div class="cross-link-body">
|
<div class="cross-link-body">
|
||||||
<div class="cross-link-title">Evidence & Audit > Trust & Signing</div>
|
<div class="cross-link-title">Evidence & Audit > Trust & Signing</div>
|
||||||
@@ -194,7 +194,7 @@ type EvidenceHomeMode = 'normal' | 'degraded' | 'empty';
|
|||||||
<line x1="12" y1="16" x2="12.01" y2="16"/>
|
<line x1="12" y1="16" x2="12.01" y2="16"/>
|
||||||
</svg>
|
</svg>
|
||||||
Trust and signing operations are available at
|
Trust and signing operations are available at
|
||||||
<a routerLink="/administration/trust-signing">Evidence > Trust & Signing</a>
|
<a routerLink="/setup/trust-signing">Evidence > Trust & Signing</a>
|
||||||
with permanent aliases from legacy settings/admin paths.
|
with permanent aliases from legacy settings/admin paths.
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -89,6 +89,6 @@ describe('ContextChipsComponent', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(evidenceLink).toBeTruthy();
|
expect(evidenceLink).toBeTruthy();
|
||||||
expect(evidenceLink?.getAttribute('href')).toContain('/administration/trust-signing');
|
expect(evidenceLink?.getAttribute('href')).toContain('/setup/trust-signing');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { AUTH_SERVICE, AuthService, StellaOpsScopes } from '../../core/auth';
|
|||||||
class="chip"
|
class="chip"
|
||||||
[class.chip--on]="isEnabled()"
|
[class.chip--on]="isEnabled()"
|
||||||
[class.chip--off]="!isEnabled()"
|
[class.chip--off]="!isEnabled()"
|
||||||
routerLink="/administration/trust-signing"
|
routerLink="/setup/trust-signing"
|
||||||
[attr.title]="tooltip()"
|
[attr.title]="tooltip()"
|
||||||
>
|
>
|
||||||
<svg class="chip__icon" viewBox="0 0 24 24" width="14" height="14" aria-hidden="true">
|
<svg class="chip__icon" viewBox="0 0 24 24" width="14" height="14" aria-hidden="true">
|
||||||
@@ -98,4 +98,3 @@ export class EvidenceModeChipComponent {
|
|||||||
: 'Evidence signing scopes are not active for this session.'
|
: 'Evidence signing scopes are not active for this session.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,9 @@ export const SETUP_ROUTES: Routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'trust-signing',
|
path: 'trust-signing',
|
||||||
redirectTo: '/administration/trust-signing',
|
title: 'Trust & Signing',
|
||||||
pathMatch: 'full',
|
data: { breadcrumb: 'Trust & Signing' },
|
||||||
|
loadChildren: () =>
|
||||||
|
import('../features/trust-admin/trust-admin.routes').then((m) => m.trustAdminRoutes),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -163,6 +163,7 @@ const canonicalRoutes = [
|
|||||||
'/setup/notifications',
|
'/setup/notifications',
|
||||||
'/setup/usage',
|
'/setup/usage',
|
||||||
'/setup/system',
|
'/setup/system',
|
||||||
|
'/setup/trust-signing',
|
||||||
'/setup/topology',
|
'/setup/topology',
|
||||||
'/setup/topology/overview',
|
'/setup/topology/overview',
|
||||||
'/setup/topology/map',
|
'/setup/topology/map',
|
||||||
@@ -191,6 +192,10 @@ const strictRouteExpectations: Partial<Record<(typeof canonicalRoutes)[number],
|
|||||||
title: /Reachability/i,
|
title: /Reachability/i,
|
||||||
texts: ['Reachability Center'],
|
texts: ['Reachability Center'],
|
||||||
},
|
},
|
||||||
|
'/setup/trust-signing': {
|
||||||
|
title: /Trust/i,
|
||||||
|
texts: ['Trust Management'],
|
||||||
|
},
|
||||||
'/ops/policy': {
|
'/ops/policy': {
|
||||||
title: /Policy/i,
|
title: /Policy/i,
|
||||||
texts: ['Policy Governance', 'Risk Budget Overview'],
|
texts: ['Policy Governance', 'Risk Budget Overview'],
|
||||||
@@ -773,6 +778,119 @@ async function setupHarness(page: Page): Promise<void> {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
await page.route('**/api/v1/trust/dashboard**', (route) =>
|
||||||
|
route.fulfill({
|
||||||
|
status: 200,
|
||||||
|
contentType: 'application/json',
|
||||||
|
body: JSON.stringify({
|
||||||
|
keys: {
|
||||||
|
total: 3,
|
||||||
|
active: 1,
|
||||||
|
expiringSoon: 1,
|
||||||
|
expired: 1,
|
||||||
|
revoked: 0,
|
||||||
|
pendingRotation: 0,
|
||||||
|
},
|
||||||
|
issuers: {
|
||||||
|
total: 3,
|
||||||
|
fullTrust: 2,
|
||||||
|
partialTrust: 1,
|
||||||
|
minimalTrust: 0,
|
||||||
|
untrusted: 0,
|
||||||
|
blocked: 0,
|
||||||
|
averageTrustScore: 87.3,
|
||||||
|
},
|
||||||
|
certificates: {
|
||||||
|
total: 3,
|
||||||
|
valid: 2,
|
||||||
|
expiringSoon: 1,
|
||||||
|
expired: 0,
|
||||||
|
revoked: 0,
|
||||||
|
invalidChains: 0,
|
||||||
|
},
|
||||||
|
recentEvents: [],
|
||||||
|
expiryAlerts: [
|
||||||
|
{
|
||||||
|
keyId: 'key-002',
|
||||||
|
keyName: 'SBOM Signing Key',
|
||||||
|
expiresAt: '2026-04-15T00:00:00.000Z',
|
||||||
|
daysUntilExpiry: 39,
|
||||||
|
severity: 'warning',
|
||||||
|
purpose: 'sbom_signing',
|
||||||
|
suggestedAction: 'Schedule key rotation before expiry.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
await page.route('**/api/v1/trust/keys/expiry-alerts**', (route) =>
|
||||||
|
route.fulfill({
|
||||||
|
status: 200,
|
||||||
|
contentType: 'application/json',
|
||||||
|
body: JSON.stringify([
|
||||||
|
{
|
||||||
|
keyId: 'key-002',
|
||||||
|
keyName: 'SBOM Signing Key',
|
||||||
|
purpose: 'sbom_signing',
|
||||||
|
expiresAt: '2026-04-15T00:00:00.000Z',
|
||||||
|
daysUntilExpiry: 39,
|
||||||
|
severity: 'warning',
|
||||||
|
suggestedAction: 'Schedule key rotation before expiry.',
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
await page.route('**/api/v1/trust/keys**', (route) => {
|
||||||
|
const pathname = new URL(route.request().url()).pathname;
|
||||||
|
if (!pathname.endsWith('/api/v1/trust/keys')) {
|
||||||
|
return route.fallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
return route.fulfill({
|
||||||
|
status: 200,
|
||||||
|
contentType: 'application/json',
|
||||||
|
body: JSON.stringify({
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
keyId: 'key-001',
|
||||||
|
tenantId: adminSession.tenant,
|
||||||
|
name: 'Production Key',
|
||||||
|
description: 'Main signing key',
|
||||||
|
keyType: 'asymmetric',
|
||||||
|
algorithm: 'RS256',
|
||||||
|
keySize: 2048,
|
||||||
|
purpose: 'attestation',
|
||||||
|
status: 'active',
|
||||||
|
publicKeyFingerprint: 'sha256:prod-key',
|
||||||
|
createdAt: '2026-01-01T00:00:00.000Z',
|
||||||
|
expiresAt: '2027-01-01T00:00:00.000Z',
|
||||||
|
lastUsedAt: '2026-03-06T10:00:00.000Z',
|
||||||
|
usageCount: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
keyId: 'key-002',
|
||||||
|
tenantId: adminSession.tenant,
|
||||||
|
name: 'SBOM Signing Key',
|
||||||
|
description: 'Secondary signing key',
|
||||||
|
keyType: 'asymmetric',
|
||||||
|
algorithm: 'RS256',
|
||||||
|
keySize: 2048,
|
||||||
|
purpose: 'sbom_signing',
|
||||||
|
status: 'expiring_soon',
|
||||||
|
publicKeyFingerprint: 'sha256:sbom-key',
|
||||||
|
createdAt: '2026-01-15T00:00:00.000Z',
|
||||||
|
expiresAt: '2026-04-15T00:00:00.000Z',
|
||||||
|
lastUsedAt: '2026-03-05T09:00:00.000Z',
|
||||||
|
usageCount: 50,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
totalCount: 2,
|
||||||
|
pageNumber: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
totalPages: 1,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
await page.route('**/policy/api/risk/profiles**', (route) => {
|
await page.route('**/policy/api/risk/profiles**', (route) => {
|
||||||
if (route.request().method() !== 'GET') {
|
if (route.request().method() !== 'GET') {
|
||||||
return route.fulfill({
|
return route.fulfill({
|
||||||
@@ -1105,6 +1223,7 @@ async function setupHarness(page: Page): Promise<void> {
|
|||||||
requestUrl.includes('/api/v2/context/') ||
|
requestUrl.includes('/api/v2/context/') ||
|
||||||
requestUrl.includes('/api/v2/security/sbom-explorer') ||
|
requestUrl.includes('/api/v2/security/sbom-explorer') ||
|
||||||
requestUrl.includes('/policy/api/') ||
|
requestUrl.includes('/policy/api/') ||
|
||||||
|
requestUrl.includes('/api/v1/trust/') ||
|
||||||
requestUrl.includes('/api/v1/audit/') ||
|
requestUrl.includes('/api/v1/audit/') ||
|
||||||
requestUrl.includes('/api/v1/authority/quotas') ||
|
requestUrl.includes('/api/v1/authority/quotas') ||
|
||||||
requestUrl.includes('/api/v1/gateway/rate-limits') ||
|
requestUrl.includes('/api/v1/gateway/rate-limits') ||
|
||||||
@@ -1294,6 +1413,15 @@ test.describe('Pre-alpha key end-user interactions', () => {
|
|||||||
await expect(page.locator('#main-content')).toContainText('Reachability Center');
|
await expect(page.locator('#main-content')).toContainText('Reachability Center');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('setup trust-signing tabs stay under setup routes', async ({ page }) => {
|
||||||
|
await page.goto('/setup/trust-signing', { waitUntil: 'domcontentloaded' });
|
||||||
|
await expect(page.locator('#main-content')).toContainText('Trust Management');
|
||||||
|
|
||||||
|
await page.locator('#main-content a:has-text("Signing Keys")').first().click();
|
||||||
|
await expect(page).toHaveURL(/\/setup\/trust-signing\/keys$/);
|
||||||
|
await expect(page.locator('#main-content')).toContainText('Trust Management');
|
||||||
|
});
|
||||||
|
|
||||||
test('sidebar root navigation works for all canonical workspaces', async ({ page }) => {
|
test('sidebar root navigation works for all canonical workspaces', async ({ page }) => {
|
||||||
await page.goto('/mission-control/board', { waitUntil: 'domcontentloaded' });
|
await page.goto('/mission-control/board', { waitUntil: 'domcontentloaded' });
|
||||||
await page.locator('aside.sidebar a[href="/releases/overview"]').first().click();
|
await page.locator('aside.sidebar a[href="/releases/overview"]').first().click();
|
||||||
|
|||||||
Reference in New Issue
Block a user