Normalize live policy simulation tenant routing
This commit is contained in:
@@ -229,7 +229,7 @@
|
||||
"PreserveAuthHeaders": true
|
||||
},
|
||||
{
|
||||
"Type": "Microservice",
|
||||
"Type": "ReverseProxy",
|
||||
"Path": "/policy/shadow",
|
||||
"TranslatesTo": "http://policy-gateway.stella-ops.local/policy/shadow",
|
||||
"PreserveAuthHeaders": true
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Sprint 20260309-019 - FE Policy Simulation Active Tenant Runtime
|
||||
|
||||
## Topic & Scope
|
||||
- Remove the remaining mock-era tenant placeholder behavior from live Policy Simulation runtime calls.
|
||||
- Ensure live policy simulation surfaces use the active shell tenant context when older callers still pass the legacy `'default'` placeholder.
|
||||
- Verify the repaired behavior with focused client tests, a web rebuild, and authenticated Playwright against `https://stella-ops.local`.
|
||||
- Working directory: `src/Web/StellaOps.Web/src/app/core/api`.
|
||||
- Allowed coordination edits: `src/Web/StellaOps.Web/src/app/features/policy-simulation/**`, `docs/modules/ui/**`.
|
||||
- Expected evidence: focused client spec pass, live Playwright policy sweep artifact, rebuilt web bundle.
|
||||
|
||||
## Dependencies & Concurrency
|
||||
- Depends on `SPRINT_20260309_018_Router_policy_simulation_frontdoor_translation.md` so the frontdoor preserves auth/DPoP for policy simulation requests.
|
||||
- Safe parallelism: avoid touching unrelated search and setup slices; keep this sprint scoped to policy simulation tenant resolution.
|
||||
|
||||
## Documentation Prerequisites
|
||||
- `AGENTS.md`
|
||||
- `docs/code-of-conduct/CODE_OF_CONDUCT.md`
|
||||
- `docs/qa/feature-checks/FLOW.md`
|
||||
- `docs/modules/ui/README.md`
|
||||
|
||||
## Delivery Tracker
|
||||
|
||||
### FE-POLICY-SIM-019-001 - Normalize legacy placeholder tenants to the active shell context
|
||||
Status: DOING
|
||||
Dependency: none
|
||||
Owners: Developer, QA
|
||||
Task description:
|
||||
- Repair the live Policy Simulation client seam so runtime requests stop sending `tenant=default` when the shell is actually scoped to a real tenant such as `demo-prod`.
|
||||
- Preserve explicit tenant overrides for legitimate cross-tenant/admin flows while treating the legacy `'default'` value as a placeholder whenever an active context tenant is available.
|
||||
- Cover the behavior with focused tests and live Playwright verification on the shadow results/history flows.
|
||||
|
||||
Completion criteria:
|
||||
- [ ] Policy Simulation history, pin, compare, verify, and shadow-results requests no longer fail with tenant override rejection in live router logs.
|
||||
- [ ] Focused client tests prove placeholder tenant resolution prefers active runtime tenant while explicit custom tenants still win.
|
||||
- [ ] Authenticated Playwright on `/ops/policy/simulation` and `/ops/policy/simulation/history` completes without `403` responses for `/policy/shadow/results` or `/policy/simulations/history`.
|
||||
|
||||
## Execution Log
|
||||
| Date (UTC) | Update | Owner |
|
||||
| --- | --- | --- |
|
||||
| 2026-03-09 | Sprint created after live Playwright and router logs showed Policy Simulation pages were reachable, but background requests still failed with `403` because the feature passed `tenant=default` while the live context resolved to `demo-prod`. | Developer |
|
||||
| 2026-03-10 | Focused `policy-simulation.client.spec.ts` passed with the new placeholder-tenant normalization. Live recheck confirmed `/policy/simulations/history` moved from `403` to `200`, then exposed remaining local gateway drift where `/policy/shadow` was still typed as `Microservice` and returned frontdoor `404`s. | Developer |
|
||||
|
||||
## Decisions & Risks
|
||||
- Decision: normalize the legacy `'default'` tenant at the shared client seam instead of patching only the currently failing components; this protects the whole Policy Simulation feature cluster against the same runtime drift.
|
||||
- Risk: a real tenant literally named `default` would still be ambiguous; preserve it only when no active tenant context exists.
|
||||
|
||||
## Next Checkpoints
|
||||
- 2026-03-09: land the client normalization and focused regression test.
|
||||
- 2026-03-09: rebuild the web bundle and re-run authenticated Playwright on the affected policy routes.
|
||||
@@ -30,7 +30,9 @@ describe('PolicySimulationHttpClient', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
authSessionStoreMock = jasmine.createSpyObj('AuthSessionStore', ['getActiveTenantId']);
|
||||
tenantServiceMock = jasmine.createSpyObj('TenantActivationService', ['getActiveTenant']);
|
||||
tenantServiceMock = {
|
||||
activeTenantId: jasmine.createSpy('activeTenantId').and.returnValue(null),
|
||||
};
|
||||
authSessionStoreMock.getActiveTenantId.and.returnValue('tenant-001');
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
@@ -726,6 +728,7 @@ describe('PolicySimulationHttpClient', () => {
|
||||
|
||||
it('should use provided tenant over active tenant', async () => {
|
||||
const customTenant = 'custom-tenant-001';
|
||||
tenantServiceMock.activeTenantId.and.returnValue('tenant-context-001');
|
||||
|
||||
const promise = firstValueFrom(httpClient.getShadowModeConfig({ tenantId: customTenant }));
|
||||
const req = httpMock.expectOne(`${baseUrl}/policy/shadow/config`);
|
||||
@@ -734,6 +737,23 @@ describe('PolicySimulationHttpClient', () => {
|
||||
|
||||
await promise;
|
||||
});
|
||||
|
||||
it('should map legacy default placeholder tenant to the active tenant context', async () => {
|
||||
tenantServiceMock.activeTenantId.and.returnValue('demo-prod');
|
||||
|
||||
const promise = firstValueFrom(
|
||||
httpClient.getSimulationHistory({
|
||||
tenantId: 'default',
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
}),
|
||||
);
|
||||
const req = httpMock.expectOne((request) => request.url === `${baseUrl}/policy/simulations/history`);
|
||||
expect(req.request.headers.get('X-StellaOps-Tenant')).toBe('demo-prod');
|
||||
req.flush({ items: [], total: 0, hasMore: false });
|
||||
|
||||
await promise;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -110,6 +110,8 @@ export interface PolicySimulationApi {
|
||||
export const POLICY_SIMULATION_API = new InjectionToken<PolicySimulationApi>('POLICY_SIMULATION_API');
|
||||
export const POLICY_SIMULATION_API_BASE_URL = new InjectionToken<string>('POLICY_SIMULATION_API_BASE_URL');
|
||||
|
||||
const LEGACY_PLACEHOLDER_TENANTS = new Set(['default']);
|
||||
|
||||
// ============================================================================
|
||||
// HTTP Implementation
|
||||
// ============================================================================
|
||||
@@ -426,7 +428,15 @@ export class PolicySimulationHttpClient implements PolicySimulationApi {
|
||||
}
|
||||
|
||||
private resolveTenant(tenantId?: string): string {
|
||||
const tenant = (tenantId && tenantId.trim()) || this.authSession.getActiveTenantId();
|
||||
const requestedTenant = tenantId?.trim() || null;
|
||||
const activeTenant =
|
||||
this.tenantService.activeTenantId?.() ??
|
||||
this.authSession.getActiveTenantId();
|
||||
const tenant =
|
||||
!requestedTenant || (activeTenant && LEGACY_PLACEHOLDER_TENANTS.has(requestedTenant.toLowerCase()))
|
||||
? activeTenant ?? requestedTenant
|
||||
: requestedTenant;
|
||||
|
||||
if (!tenant) {
|
||||
throw new Error('PolicySimulationHttpClient requires an active tenant identifier.');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user