wip: doctor/cli/docs/api to vector db consolidation; api hardening for descriptions, tenant, and scopes; migrations and conversions of all DALs to EF v10

This commit is contained in:
master
2026-02-23 15:30:50 +02:00
parent bd8fee6ed8
commit e746577380
1424 changed files with 81225 additions and 25251 deletions

View File

@@ -0,0 +1,56 @@
# Multi-Tenant Same-Key Acceptance Matrix
Date: 2026-02-22
Source sprint: `SPRINT_20260222_053_DOCS_multi_tenant_same_api_key_contract_baseline.md`
Used by sprint: `SPRINT_20260222_060_FE_playwright_multi_tenant_end_to_end_matrix.md`
## Scope
- Validate tenant selection and tenant isolation behavior for:
- Platform + Topology APIs
- Scanner APIs (scans, triage, webhooks, unknowns)
- Graph APIs
- Web primary pages with global tenant selector
## Status Matrix (API)
| Area | Representative route(s) | Valid tenant | Missing tenant | Cross-tenant attempt | Required evidence |
| --- | --- | --- | --- | --- | --- |
| Platform context | `/api/v1/platform/context/preferences` | `200` tenant-scoped preferences | deterministic auth/context rejection | `403/404` (tenant mismatch/forbidden) | Command output + payload snippets + test assertion output |
| Platform topology | `/api/v1/platform/topology/*` | `200` tenant-scoped topology | deterministic auth/context rejection | `403/404` | Integration test output with overlapping IDs across two tenants |
| Scanner scans | `/api/v1/scans/*` | `200/202` for owned scans | deterministic auth/context rejection | `403/404` on non-owned scan id | Test output for scan ownership + replay/read paths |
| Scanner triage | `/api/v1/triage/*` | `200` for tenant-owned findings | deterministic auth/context rejection | `404` on non-owned finding id | Test output for triage query/status/isolation cases |
| Scanner webhooks | `/api/v1/webhooks/{provider}/{sourceName}` | `2xx` only for tenant-scoped source mapping | `400 tenant_missing` (where required) | deterministic reject/no cross-dispatch | Test output showing same `sourceName` across tenants does not collide |
| Scanner unknowns | `/api/v1/unknowns/*` | `200` tenant-scoped list/detail | deterministic auth/context rejection | `404` cross-tenant detail/evidence/history | Test output for unknown detail isolation |
| Graph query/search/export | `/api/v1/graph/*` | `200` for authorized tenant + scopes | deterministic auth/context rejection | `403/404` mismatch + ownership denial | Graph API test output with auth + tenant negative paths |
## Status Matrix (UI Pages)
| Page group | Routes | Expected tenant indicator behavior | Expected backend call behavior | Negative assertion |
| --- | --- | --- | --- | --- |
| Mission Control | `/mission-control/*` | Header selector shows selected tenant name and persists after navigation | Requests carry canonical tenant context | No stale content from previous tenant after switch |
| Releases | `/releases/*` | Tenant selector remains available; selected tenant stable | Tenant-scoped API calls after switch | No cross-tenant release data visible |
| Security | `/security/*` | Selected tenant remains active across subroutes | Scanner/Graph-related requests reflect selected tenant | No findings/advisories leak from previous tenant |
| Evidence | `/evidence/*` | Selected tenant persists through refresh | Tenant-scoped evidence requests | No evidence thread from previous tenant persists post-switch |
| Ops | `/ops/*` | Tenant context remains globally applied | Platform/ops requests include selected tenant context | No mixed-tenant cards/widgets |
| Setup | `/setup/*` | Selector remains visible and stable | Topology/setup reads align with selected tenant where tenant-scoped | No topology entities from previous tenant |
| Admin | `/administration/*` (or equivalent admin routes) | Selector persists and selected tenant is clear | Authority admin reads operate in selected tenant scope | No client/user entries leaked from other tenant |
## Required Artifacts
- Tier 2a:
- Raw command outputs for Platform/Scanner/Graph targeted verification.
- Response/status assertions for valid, missing, and cross-tenant requests.
- Tier 2c:
- Playwright command output.
- Trace zip and screenshots for tenant switch and post-switch navigation checks.
- Desktop and mobile viewport results.
- Cross-cutting:
- Test counts from targeted runs (not suite totals only).
- List of new tests written and bugs fixed (if any).
- Final go/no-go decision + residual risks.
## Pass/Fail Gate
- Pass:
- All matrix rows have deterministic positive and negative-path evidence.
- No unresolved cross-tenant leakage failures.
- Fail:
- Any cross-tenant leakage, nondeterministic auth behavior, or missing Tier 2 evidence blocks rollout.

View File

@@ -0,0 +1,7 @@
COMMAND: dotnet run --project src/Authority/StellaOps.Authority/StellaOps.Authority.Tests/StellaOps.Authority.Tests.csproj -- -class StellaOps.Authority.Tests.Console.ConsoleAdminEndpointsTests -maxThreads 1 -noLogo
Discovering: StellaOps.Authority.Tests
Discovered: StellaOps.Authority.Tests
Starting: StellaOps.Authority.Tests
Finished: StellaOps.Authority.Tests (ID = '4c6bea7c77eb0f33a99f8646aaddc0e8f692ee0e0fd1a5d9200ec1247d34977b')
=== TEST EXECUTION SUMMARY ===
StellaOps.Authority.Tests Total: 8, Errors: 0, Failed: 0, Skipped: 0, Not Run: 0, Time: 1.190s

View File

@@ -0,0 +1,7 @@
COMMAND: dotnet run --project src/Authority/StellaOps.Authority/StellaOps.Authority.Tests/StellaOps.Authority.Tests.csproj -- -method StellaOps.Authority.Tests.Console.ConsoleEndpointsTests.Tenants_ReturnsAllowedTenantAssignments_WithSelectedMarker -maxThreads 1 -noLogo
Discovering: StellaOps.Authority.Tests
Discovered: StellaOps.Authority.Tests
Starting: StellaOps.Authority.Tests
Finished: StellaOps.Authority.Tests (ID = '4c6bea7c77eb0f33a99f8646aaddc0e8f692ee0e0fd1a5d9200ec1247d34977b')
=== TEST EXECUTION SUMMARY ===
StellaOps.Authority.Tests Total: 1, Errors: 0, Failed: 0, Skipped: 0, Not Run: 0, Time: 0.315s

View File

@@ -0,0 +1,7 @@
COMMAND: dotnet run --no-build --project src/Authority/StellaOps.Authority/StellaOps.Authority.Tests/StellaOps.Authority.Tests.csproj -- -method StellaOps.Authority.Tests.OpenIddict.ClientCredentialsHandlersTests.ValidateClientCredentials_SelectsRequestedTenant_WhenAssigned -method StellaOps.Authority.Tests.OpenIddict.ClientCredentialsHandlersTests.ValidateClientCredentials_Rejects_WhenRequestedTenantNotAssigned -method StellaOps.Authority.Tests.OpenIddict.ClientCredentialsHandlersTests.ValidateClientCredentials_Rejects_WhenTenantSelectionIsAmbiguous -method StellaOps.Authority.Tests.OpenIddict.PasswordGrantHandlersTests.ValidatePasswordGrant_SelectsRequestedTenant_WhenAssigned -method StellaOps.Authority.Tests.OpenIddict.PasswordGrantHandlersTests.ValidatePasswordGrant_Rejects_WhenTenantSelectionIsAmbiguous -method StellaOps.Authority.Tests.OpenIddict.TokenValidationHandlersTests.ValidateAccessTokenHandler_AddsTenantClaim_FromTokenDocument -method StellaOps.Authority.Tests.OpenIddict.TokenValidationHandlersTests.ValidateAccessTokenHandler_Rejects_WhenTenantDiffersFromToken -method StellaOps.Authority.Tests.OpenIddict.TokenValidationHandlersTests.ValidateAccessTokenHandler_Rejects_WhenTenantOutsideMultiTenantAssignments -maxThreads 1 -noLogo
Discovering: StellaOps.Authority.Tests
Discovered: StellaOps.Authority.Tests
Starting: StellaOps.Authority.Tests
Finished: StellaOps.Authority.Tests (ID = '4c6bea7c77eb0f33a99f8646aaddc0e8f692ee0e0fd1a5d9200ec1247d34977b')
=== TEST EXECUTION SUMMARY ===
StellaOps.Authority.Tests Total: 8, Errors: 0, Failed: 0, Skipped: 0, Not Run: 0, Time: 0.191s

View File

@@ -0,0 +1,73 @@
[
{
"name": "authority-admin-tenant-assignments",
"command": "dotnet run --project src/Authority/StellaOps.Authority/StellaOps.Authority.Tests/StellaOps.Authority.Tests.csproj -- -class StellaOps.Authority.Tests.Console.ConsoleAdminEndpointsTests -maxThreads 1 -noLogo",
"exitCode": 0,
"testsRun": 8,
"testsPassed": 8,
"evidenceFile": "evidence/authority-console-admin-tenant-assignments.txt"
},
{
"name": "authority-console-tenants-selected-marker",
"command": "dotnet run --project src/Authority/StellaOps.Authority/StellaOps.Authority.Tests/StellaOps.Authority.Tests.csproj -- -method StellaOps.Authority.Tests.Console.ConsoleEndpointsTests.Tenants_ReturnsAllowedTenantAssignments_WithSelectedMarker -maxThreads 1 -noLogo",
"exitCode": 0,
"testsRun": 1,
"testsPassed": 1,
"evidenceFile": "evidence/authority-console-tenants-selected-marker.txt"
},
{
"name": "authority-token-tenant-selection-targeted",
"command": "dotnet run --no-build --project src/Authority/StellaOps.Authority/StellaOps.Authority.Tests/StellaOps.Authority.Tests.csproj -- -method StellaOps.Authority.Tests.OpenIddict.ClientCredentialsHandlersTests.ValidateClientCredentials_SelectsRequestedTenant_WhenAssigned -method StellaOps.Authority.Tests.OpenIddict.ClientCredentialsHandlersTests.ValidateClientCredentials_Rejects_WhenRequestedTenantNotAssigned -method StellaOps.Authority.Tests.OpenIddict.ClientCredentialsHandlersTests.ValidateClientCredentials_Rejects_WhenTenantSelectionIsAmbiguous -method StellaOps.Authority.Tests.OpenIddict.PasswordGrantHandlersTests.ValidatePasswordGrant_SelectsRequestedTenant_WhenAssigned -method StellaOps.Authority.Tests.OpenIddict.PasswordGrantHandlersTests.ValidatePasswordGrant_Rejects_WhenTenantSelectionIsAmbiguous -method StellaOps.Authority.Tests.OpenIddict.TokenValidationHandlersTests.ValidateAccessTokenHandler_AddsTenantClaim_FromTokenDocument -method StellaOps.Authority.Tests.OpenIddict.TokenValidationHandlersTests.ValidateAccessTokenHandler_Rejects_WhenTenantDiffersFromToken -method StellaOps.Authority.Tests.OpenIddict.TokenValidationHandlersTests.ValidateAccessTokenHandler_Rejects_WhenTenantOutsideMultiTenantAssignments -maxThreads 1 -noLogo",
"exitCode": 0,
"testsRun": 8,
"testsPassed": 8,
"evidenceFile": "evidence/authority-token-tenant-selection-targeted.txt"
},
{
"name": "platform-targeted-build-attempt",
"command": "dotnet run --project src/Platform/__Tests/StellaOps.Platform.WebService.Tests/StellaOps.Platform.WebService.Tests.csproj -- -class StellaOps.Platform.WebService.Tests.PlatformRequestContextResolverTests -class StellaOps.Platform.WebService.Tests.TopologyReadModelEndpointsTests -class StellaOps.Platform.WebService.Tests.ContextEndpointsTests -maxThreads 1 -noLogo",
"exitCode": 1,
"note": "Failed due to pre-existing unrelated compile errors in src/Policy/StellaOps.Policy.Engine/Endpoints/RiskProfileAirGapEndpoints.cs",
"evidenceFile": "evidence/platform-tenant-isolation-targeted.txt"
},
{
"name": "platform-targeted-no-build",
"command": "dotnet run --no-build --project src/Platform/__Tests/StellaOps.Platform.WebService.Tests/StellaOps.Platform.WebService.Tests.csproj -- -class StellaOps.Platform.WebService.Tests.PlatformRequestContextResolverTests -class StellaOps.Platform.WebService.Tests.TopologyReadModelEndpointsTests -class StellaOps.Platform.WebService.Tests.ContextEndpointsTests -maxThreads 1 -noLogo",
"exitCode": 0,
"testsRun": 14,
"testsPassed": 14,
"evidenceFile": "evidence/platform-tenant-isolation-targeted.txt"
},
{
"name": "scanner-targeted-no-build",
"command": "dotnet run --no-build --project src/Scanner/__Tests/StellaOps.Scanner.WebService.Tests/StellaOps.Scanner.WebService.Tests.csproj -- -class StellaOps.Scanner.WebService.Tests.ScannerRequestContextResolverTests -class StellaOps.Scanner.WebService.Tests.ScannerTenantIsolationAndEndpointRegistrationTests -class StellaOps.Scanner.WebService.Tests.TriageTenantIsolationEndpointsTests -class StellaOps.Scanner.WebService.Tests.UnknownsTenantIsolationEndpointsTests -class StellaOps.Scanner.WebService.Tests.WebhookEndpointsTenantLookupTests -maxThreads 1 -noLogo",
"exitCode": 0,
"testsRun": 13,
"testsPassed": 13,
"evidenceFile": "evidence/scanner-tenant-isolation-targeted.txt"
},
{
"name": "graph-targeted-no-build",
"command": "dotnet run --no-build --project src/Graph/__Tests/StellaOps.Graph.Api.Tests/StellaOps.Graph.Api.Tests.csproj -- -class StellaOps.Graph.Api.Tests.GraphRequestContextResolverTests -class StellaOps.Graph.Api.Tests.GraphTenantAuthorizationAlignmentTests -maxThreads 1 -noLogo",
"exitCode": 0,
"testsRun": 7,
"testsPassed": 7,
"evidenceFile": "evidence/graph-tenant-alignment-targeted.txt"
},
{
"name": "web-targeted-unit-suite",
"command": "npm run test -- --watch=false --include=src/app/core/auth/tenant-http.interceptor.spec.ts --include=src/app/core/console/console-session.service.spec.ts --include=src/app/core/console/console-session.store.spec.ts --include=src/app/layout/app-topbar/app-topbar.component.spec.ts --include=src/tests/context/platform-context-url-sync.service.spec.ts",
"exitCode": 0,
"testsRun": 18,
"testsPassed": 18,
"evidenceFile": "evidence/web-tenant-unit-tests.txt"
},
{
"name": "web-playwright-tenant-matrix",
"command": "npm run test:e2e -- tests/e2e/tenant-switch-matrix.spec.ts --workers=1 --trace on --reporter=line",
"exitCode": 0,
"testsRun": 2,
"testsPassed": 2,
"evidenceFile": "evidence/web-tenant-playwright-matrix.txt"
}
]

View File

@@ -0,0 +1,71 @@
COMMAND: dotnet run --no-build --project src/Graph/__Tests/StellaOps.Graph.Api.Tests/StellaOps.Graph.Api.Tests.csproj -- -class StellaOps.Graph.Api.Tests.GraphRequestContextResolverTests -class StellaOps.Graph.Api.Tests.GraphTenantAuthorizationAlignmentTests -maxThreads 1 -noLogo
Discovering: StellaOps.Graph.Api.Tests
Discovered: StellaOps.Graph.Api.Tests
Starting: StellaOps.Graph.Api.Tests
info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[63]
User profile is available. Using 'C:\Users\VladimirMoushkov\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest.
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: C:\dev\New folder\git.stella-ops.org\src\Graph\StellaOps.Graph.Api
info: StellaOps.LocalHostname[0]
Also accessible at https://graph.stella-ops.local and http://graph.stella-ops.local
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
Request starting HTTP/1.1 POST http://localhost/graph/query - application/json;+charset=utf-8 92
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
Executing endpoint 'HTTP: POST /graph/query'
[AUDIT] 2026-02-22T23:14:43.0954247+00:00 tenant=acme route=/graph/query status=200 scopes=graph:query duration_ms=21
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
Executed endpoint 'HTTP: POST /graph/query'
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
Request finished HTTP/1.1 POST http://localhost/graph/query - 200 - application/x-ndjson 64.4216ms
info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[63]
User profile is available. Using 'C:\Users\VladimirMoushkov\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest.
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: C:\dev\New folder\git.stella-ops.org\src\Graph\StellaOps.Graph.Api
info: StellaOps.LocalHostname[0]
Also accessible at https://graph.stella-ops.local and http://graph.stella-ops.local
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
Request starting HTTP/1.1 POST http://localhost/graph/query - application/json;+charset=utf-8 33
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
Executing endpoint 'HTTP: POST /graph/query'
info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]
Authorization failed. These requirements were not met:
Handler assertion should evaluate to true.
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
Executed endpoint 'HTTP: POST /graph/query'
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
Request finished HTTP/1.1 POST http://localhost/graph/query - 403 - application/x-ndjson 13.4513ms
info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[63]
User profile is available. Using 'C:\Users\VladimirMoushkov\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest.
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: C:\dev\New folder\git.stella-ops.org\src\Graph\StellaOps.Graph.Api
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
Request starting HTTP/1.1 POST http://localhost/graph/query - application/json;+charset=utf-8 33
info: StellaOps.LocalHostname[0]
Also accessible at https://graph.stella-ops.local and http://graph.stella-ops.local
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
Executing endpoint 'HTTP: POST /graph/query'
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
Executed endpoint 'HTTP: POST /graph/query'
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
Request finished HTTP/1.1 POST http://localhost/graph/query - 400 - application/x-ndjson 9.2996ms
info: Microsoft.Hosting.Lifetime[0]
Application is shutting down...
info: Microsoft.Hosting.Lifetime[0]
Application is shutting down...
info: Microsoft.Hosting.Lifetime[0]
Application is shutting down...
Finished: StellaOps.Graph.Api.Tests (ID = 'a3090b92c822cca46279e2c21530c98d77d6cba006f34ec1c65ccafba9002432')
=== TEST EXECUTION SUMMARY ===
StellaOps.Graph.Api.Tests Total: 7, Errors: 0, Failed: 0, Skipped: 0, Not Run: 0, Time: 0.431s

View File

@@ -0,0 +1,7 @@
COMMAND: dotnet run --no-build --project src/Platform/__Tests/StellaOps.Platform.WebService.Tests/StellaOps.Platform.WebService.Tests.csproj -- -class StellaOps.Platform.WebService.Tests.PlatformRequestContextResolverTests -class StellaOps.Platform.WebService.Tests.TopologyReadModelEndpointsTests -class StellaOps.Platform.WebService.Tests.ContextEndpointsTests -maxThreads 1 -noLogo
Discovering: StellaOps.Platform.WebService.Tests
Discovered: StellaOps.Platform.WebService.Tests
Starting: StellaOps.Platform.WebService.Tests
Finished: StellaOps.Platform.WebService.Tests (ID = '2db881d139343cf28494dcd2d091345dc3546ebd0d3a0f7691cccac253e9eadb')
=== TEST EXECUTION SUMMARY ===
StellaOps.Platform.WebService.Tests Total: 14, Errors: 0, Failed: 0, Skipped: 0, Not Run: 0, Time: 1.290s

View File

@@ -0,0 +1,77 @@
COMMAND: dotnet run --no-build --project src/Scanner/__Tests/StellaOps.Scanner.WebService.Tests/StellaOps.Scanner.WebService.Tests.csproj -- -class StellaOps.Scanner.WebService.Tests.ScannerRequestContextResolverTests -class StellaOps.Scanner.WebService.Tests.ScannerTenantIsolationAndEndpointRegistrationTests -class StellaOps.Scanner.WebService.Tests.TriageTenantIsolationEndpointsTests -class StellaOps.Scanner.WebService.Tests.UnknownsTenantIsolationEndpointsTests -class StellaOps.Scanner.WebService.Tests.WebhookEndpointsTenantLookupTests -maxThreads 1 -noLogo
Discovering: StellaOps.Scanner.WebService.Tests
Discovered: StellaOps.Scanner.WebService.Tests
Starting: StellaOps.Scanner.WebService.Tests
[01:14:33 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=cas-access, Name=default, Success=False, ElapsedMs=0.9423, Provider=unknown, Error=NotFound
[01:14:33 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=registry, Name=default, Success=False, ElapsedMs=0.0794, Provider=unknown, Error=NotFound
[01:14:33 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=attestation, Name=default, Success=False, ElapsedMs=0.0576, Provider=unknown, Error=NotFound
[01:14:33 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=cas-access, Name=default, Success=False, ElapsedMs=0.0989, Provider=unknown, Error=NotFound
[01:14:33 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=registry, Name=default, Success=False, ElapsedMs=0.0406, Provider=unknown, Error=NotFound
[01:14:33 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=attestation, Name=default, Success=False, ElapsedMs=0.0236, Provider=unknown, Error=NotFound
[01:14:33 INF] Application started. Press Ctrl+C to shut down.
[01:14:33 INF] Hosting environment: Testing
[01:14:33 INF] Content root path: C:\dev\New folder\git.stella-ops.org\src\Scanner\StellaOps.Scanner.WebService
[01:14:33 INF] Also accessible at https://scanner.stella-ops.local and http://scanner.stella-ops.local
[01:14:34 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=cas-access, Name=default, Success=False, ElapsedMs=0.0731, Provider=unknown, Error=NotFound
[01:14:34 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=registry, Name=default, Success=False, ElapsedMs=0.0396, Provider=unknown, Error=NotFound
[01:14:34 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=attestation, Name=default, Success=False, ElapsedMs=0.0261, Provider=unknown, Error=NotFound
[01:14:34 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=cas-access, Name=default, Success=False, ElapsedMs=0.0853, Provider=unknown, Error=NotFound
[01:14:34 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=registry, Name=default, Success=False, ElapsedMs=0.039, Provider=unknown, Error=NotFound
[01:14:34 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=attestation, Name=default, Success=False, ElapsedMs=0.0271, Provider=unknown, Error=NotFound
[01:14:34 INF] Application started. Press Ctrl+C to shut down.
[01:14:34 INF] Hosting environment: Testing
[01:14:34 INF] Content root path: C:\dev\New folder\git.stella-ops.org\src\Scanner\StellaOps.Scanner.WebService
[01:14:34 INF] Also accessible at https://scanner.stella-ops.local and http://scanner.stella-ops.local
[01:14:34 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=cas-access, Name=default, Success=False, ElapsedMs=0.0652, Provider=unknown, Error=NotFound
[01:14:34 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=registry, Name=default, Success=False, ElapsedMs=0.0364, Provider=unknown, Error=NotFound
[01:14:34 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=attestation, Name=default, Success=False, ElapsedMs=0.023, Provider=unknown, Error=NotFound
[01:14:34 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=cas-access, Name=default, Success=False, ElapsedMs=0.098, Provider=unknown, Error=NotFound
[01:14:34 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=registry, Name=default, Success=False, ElapsedMs=0.0402, Provider=unknown, Error=NotFound
[01:14:34 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=attestation, Name=default, Success=False, ElapsedMs=0.0257, Provider=unknown, Error=NotFound
[01:14:34 INF] Application started. Press Ctrl+C to shut down.
[01:14:34 INF] Hosting environment: Testing
[01:14:34 INF] Content root path: C:\dev\New folder\git.stella-ops.org\src\Scanner\StellaOps.Scanner.WebService
[01:14:34 INF] Also accessible at https://scanner.stella-ops.local and http://scanner.stella-ops.local
[01:14:34 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=cas-access, Name=default, Success=False, ElapsedMs=0.059, Provider=unknown, Error=NotFound
[01:14:34 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=registry, Name=default, Success=False, ElapsedMs=0.0342, Provider=unknown, Error=NotFound
[01:14:34 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=attestation, Name=default, Success=False, ElapsedMs=0.0205, Provider=unknown, Error=NotFound
[01:14:34 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=cas-access, Name=default, Success=False, ElapsedMs=0.0908, Provider=unknown, Error=NotFound
[01:14:34 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=registry, Name=default, Success=False, ElapsedMs=0.041, Provider=unknown, Error=NotFound
[01:14:34 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=attestation, Name=default, Success=False, ElapsedMs=0.0239, Provider=unknown, Error=NotFound
[01:14:35 INF] Application started. Press Ctrl+C to shut down.
[01:14:35 INF] Hosting environment: Testing
[01:14:35 INF] Content root path: C:\dev\New folder\git.stella-ops.org\src\Scanner\StellaOps.Scanner.WebService
[01:14:35 INF] Also accessible at https://scanner.stella-ops.local and http://scanner.stella-ops.local
[01:14:35 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=cas-access, Name=default, Success=False, ElapsedMs=0.0633, Provider=unknown, Error=NotFound
[01:14:35 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=registry, Name=default, Success=False, ElapsedMs=0.0328, Provider=unknown, Error=NotFound
[01:14:35 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=attestation, Name=default, Success=False, ElapsedMs=0.02, Provider=unknown, Error=NotFound
[01:14:35 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=cas-access, Name=default, Success=False, ElapsedMs=0.0977, Provider=unknown, Error=NotFound
[01:14:35 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=registry, Name=default, Success=False, ElapsedMs=0.0401, Provider=unknown, Error=NotFound
[01:14:35 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=attestation, Name=default, Success=False, ElapsedMs=0.0226, Provider=unknown, Error=NotFound
[01:14:35 INF] Application started. Press Ctrl+C to shut down.
[01:14:35 INF] Hosting environment: Testing
[01:14:35 INF] Content root path: C:\dev\New folder\git.stella-ops.org\src\Scanner\StellaOps.Scanner.WebService
[01:14:35 INF] Also accessible at https://scanner.stella-ops.local and http://scanner.stella-ops.local
[01:14:35 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=cas-access, Name=default, Success=False, ElapsedMs=0.0554, Provider=unknown, Error=NotFound
[01:14:35 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=registry, Name=default, Success=False, ElapsedMs=0.0304, Provider=unknown, Error=NotFound
[01:14:35 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=attestation, Name=default, Success=False, ElapsedMs=0.0224, Provider=unknown, Error=NotFound
[01:14:35 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=cas-access, Name=default, Success=False, ElapsedMs=0.0834, Provider=unknown, Error=NotFound
[01:14:35 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=registry, Name=default, Success=False, ElapsedMs=0.0344, Provider=unknown, Error=NotFound
[01:14:35 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=attestation, Name=default, Success=False, ElapsedMs=0.0329, Provider=unknown, Error=NotFound
[01:14:35 INF] Application started. Press Ctrl+C to shut down.
[01:14:35 INF] Hosting environment: Testing
[01:14:35 INF] Content root path: C:\dev\New folder\git.stella-ops.org\src\Scanner\StellaOps.Scanner.WebService
[01:14:35 INF] Also accessible at https://scanner.stella-ops.local and http://scanner.stella-ops.local
[01:14:35 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=cas-access, Name=default, Success=False, ElapsedMs=0.0767, Provider=unknown, Error=NotFound
[01:14:35 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=registry, Name=default, Success=False, ElapsedMs=0.0523, Provider=unknown, Error=NotFound
[01:14:35 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=attestation, Name=default, Success=False, ElapsedMs=0.0313, Provider=unknown, Error=NotFound
[01:14:36 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=cas-access, Name=default, Success=False, ElapsedMs=0.0995, Provider=unknown, Error=NotFound
[01:14:36 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=registry, Name=default, Success=False, ElapsedMs=0.0499, Provider=unknown, Error=NotFound
[01:14:36 WRN] Surface secret access: Component=Scanner.Worker, Tenant=tenant-a, RequestComponent=Scanner.WebService, SecretType=attestation, Name=default, Success=False, ElapsedMs=0.0383, Provider=unknown, Error=NotFound
[01:14:36 INF] Application started. Press Ctrl+C to shut down.
[01:14:36 INF] Hosting environment: Testing
[01:14:36 INF] Content root path: C:\dev\New folder\git.stella-ops.org\src\Scanner\StellaOps.Scanner.WebService
[01:14:36 INF] Also accessible at https://scanner.stella-ops.local and http://scanner.stella-ops.local
Finished: StellaOps.Scanner.WebService.Tests (ID = 'f93c0ca5bc3341a3dfa74a04e79e09dc2be5a4321b76509e45c0be50c80444ab')
=== TEST EXECUTION SUMMARY ===
StellaOps.Scanner.WebService.Tests Total: 13, Errors: 0, Failed: 0, Skipped: 0, Not Run: 0, Time: 3.398s

View File

@@ -0,0 +1,25 @@
COMMAND: npm run test:e2e -- tests/e2e/tenant-switch-matrix.spec.ts --workers=1 --trace on --reporter=line
> stellaops-web@0.0.0 test:e2e
> playwright test tests/e2e/tenant-switch-matrix.spec.ts --workers=1 --trace on --reporter=line
Running 2 tests using 1 worker
node.exe : (node:61160) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
At line:1 char:1
+ & "C:\Program Files\nodejs/node.exe" "C:\Users\VladimirMoushkov\AppDa ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: ((node:6... env being set.:String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
(Use `node --trace-warnings ...` to show where the warning was created)
(node:61160) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
[1/2] tests\e2e\tenant-switch-matrix.spec.ts:9:7 Multi-tenant switch matrix switches tenant from header and persists across primary sections (desktop)
[2/2] tests\e2e\tenant-switch-matrix.spec.ts:41:7 Multi-tenant switch matrix keeps tenant selector usable and persistent on mobile viewport
 2 passed (29.9s)

View File

@@ -0,0 +1,67 @@
COMMAND: npm run test -- --watch=false --include=src/app/core/auth/tenant-http.interceptor.spec.ts --include=src/app/core/console/console-session.service.spec.ts --include=src/app/core/console/console-session.store.spec.ts --include=src/app/layout/app-topbar/app-topbar.component.spec.ts --include=src/tests/context/platform-context-url-sync.service.spec.ts
> stellaops-web@0.0.0 test
> ng test --watch=false --watch=false --include=src/app/core/auth/tenant-http.interceptor.spec.ts --include=src/app/core/console/console-session.service.spec.ts --include=src/app/core/console/console-session.store.spec.ts --include=src/app/layout/app-topbar/app-topbar.component.spec.ts --include=src/tests/context/platform-context-url-sync.service.spec.ts
Building...
✔ Building...
Initial chunk files | Names | Raw size
spec-app-layout-app-topbar-app-topbar.component.js | spec-app-layout-app-topbar-app-topbar.component | 373.21 kB |
styles.css | styles | 208.77 kB |
chunk-TT2M2B6M.js | - | 23.09 kB |
chunk-4HW7O5MC.js | - | 19.31 kB |
chunk-BABPRG6R.js | - | 11.55 kB |
spec-tests-context-platform-context-url-sync.service.js | spec-tests-context-platform-context-url-sync.service | 11.15 kB |
chunk-PIZBSUX3.js | - | 9.12 kB |
spec-app-core-console-console-session.service.js | spec-app-core-console-console-session.service | 8.04 kB |
chunk-XNDA3IA4.js | - | 5.67 kB |
chunk-XPWKAQFU.js | - | 5.20 kB |
spec-app-core-auth-tenant-http.interceptor.js | spec-app-core-auth-tenant-http.interceptor | 4.86 kB |
spec-app-core-console-console-session.store.js | spec-app-core-console-console-session.store | 3.75 kB |
init-testbed.js | init-testbed | 1.27 kB |
polyfills.js | polyfills | 121 bytes |
| Initial total | 685.10 kB
Application bundle generation complete. [33.235 seconds] - 2026-02-22T23:16:31.084Z
node.exe : ▲ [WARNING] NG8113: GateSimulationResultsComponent is not used within the template of BundleSimulatorComponent [plugin angular-compiler]
At line:1 char:1
+ & "C:\Program Files\nodejs/node.exe" "C:\Users\VladimirMoushkov\AppDa ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (▲ [WARNING] NG8...gular-compiler]:String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
src/app/features/policy-gates/components/bundle-simulator/bundle-simulator.component.ts:26:38:
26 │ ...ports: [ProfileSelectorComponent, GateSimulationResultsComponent],
╵ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
RUN v4.0.18 C:/dev/New folder/git.stella-ops.org/src/Web/StellaOps.Web
✓ |stellaops-web| src/app/core/console/console-session.store.spec.ts (4 tests) 6ms
stderr | src/app/core/console/console-session.service.spec.ts > ConsoleSessionService > rolls back tenant switch when tenant context load fails
Failed to load console context Error: tenant tenant-default is not available
at C:/dev/New folder/git.stella-ops.org/src/Web/StellaOps.Web/src/app/core/console/console-session.service.spec.ts:47:31
at Observable.init [as _subscribe] (C:\dev\New folder\git.stella-ops.org\src\Web\StellaOps.Web\node_modules\rxjs\dist\cjs\internal\observable\throwError.js:8:64)
at Observable._trySubscribe (C:\dev\New folder\git.stella-ops.org\src\Web\StellaOps.Web\node_modules\rxjs\dist\cjs\internal\Observable.js:41:25)
at C:\dev\New folder\git.stella-ops.org\src\Web\StellaOps.Web\node_modules\rxjs\dist\cjs\internal\Observable.js:35:31
at Object.errorContext (C:\dev\New folder\git.stella-ops.org\src\Web\StellaOps.Web\node_modules\rxjs\dist\cjs\internal\util\errorContext.js:22:9)
at Observable.subscribe (C:\dev\New folder\git.stella-ops.org\src\Web\StellaOps.Web\node_modules\rxjs\dist\cjs\internal\Observable.js:26:24)
at C:\dev\New folder\git.stella-ops.org\src\Web\StellaOps.Web\node_modules\rxjs\dist\cjs\internal\firstValueFrom.js:24:16
at new ZoneAwarePromise (C:\dev\New folder\git.stella-ops.org\src\Web\StellaOps.Web\node_modules\zone.js\fesm2015\zone.js:2701:25)
at firstValueFrom (C:\dev\New folder\git.stella-ops.org\src\Web\StellaOps.Web\node_modules\rxjs\dist\cjs\internal\firstValueFrom.js:8:12)
at _ConsoleSessionService.<anonymous> (C:/dev/New folder/git.stella-ops.org/src/Web/StellaOps.Web/src/app/core/console/console-session.service.ts:51:36)
✓ |stellaops-web| src/app/core/console/console-session.service.spec.ts (4 tests) 31ms
✓ |stellaops-web| src/app/core/auth/tenant-http.interceptor.spec.ts (4 tests) 31ms
✓ |stellaops-web| src/tests/context/platform-context-url-sync.service.spec.ts (3 tests) 112ms
✓ |stellaops-web| src/app/layout/app-topbar/app-topbar.component.spec.ts (3 tests) 198ms
Test Files 5 passed (5)
Tests 18 passed (18)
Start at 01:16:31
Duration 1.92s (transform 1.25s, setup 2.19s, import 1.28s, tests 378ms, environment 3.94s)

View File

@@ -0,0 +1,15 @@
{
"feature": "multi-tenant-same-api-key-selection",
"runId": "run-001",
"capturedAtUtc": "2026-02-22T23:18:33Z",
"decision": "go",
"rationale": [
"Tier 2a targeted API verification passed for Authority, Platform, Scanner, and Graph (51/51).",
"Tier 2c Playwright tenant-switch matrix passed for desktop and mobile (2/2).",
"Frontend targeted tenant unit/component tests passed (18/18)."
],
"residualRisks": [
"An unrelated pre-existing compile break exists in Policy (RiskProfileAirGapEndpoints RequireStellaOpsScopes missing); it does not affect tenant test slices run with --no-build but should be fixed before broad solution builds.",
"Playwright matrix currently covers first-level page groups; deeper page-level permutations remain outside this sprint evidence scope."
]
}

View File

@@ -0,0 +1,98 @@
{
"type": "api",
"module": "multi-tenant",
"feature": "same-api-key-selection",
"runId": "run-001",
"capturedAtUtc": "2026-02-22T23:18:33Z",
"transport": "xUnit v3 in-process API integration via dotnet run",
"checks": [
{
"service": "authority",
"scope": "admin client assignment CRUD",
"testsRun": 8,
"testsPassed": 8,
"evidenceFile": "evidence/authority-console-admin-tenant-assignments.txt",
"behaviorVerified": [
"Create and update admin client endpoints persist multi-tenant assignments.",
"Duplicate, missing, and invalid tenant assignment payloads are rejected deterministically.",
"Audit event properties include before/after tenant assignment fields."
],
"result": "pass"
},
{
"service": "authority",
"scope": "console tenants selected marker",
"testsRun": 1,
"testsPassed": 1,
"evidenceFile": "evidence/authority-console-tenants-selected-marker.txt",
"behaviorVerified": [
"/console/tenants returns assigned tenant set and selectedTenant marker for immediate UI hydration."
],
"result": "pass"
},
{
"service": "authority",
"scope": "token issuance and validation tenant selection",
"testsRun": 8,
"testsPassed": 8,
"evidenceFile": "evidence/authority-token-tenant-selection-targeted.txt",
"behaviorVerified": [
"Client credentials and password grants select requested tenant when assigned.",
"Ambiguous or unassigned tenant selections are rejected.",
"Access token validation rejects tenant mismatch and out-of-assignment tenant claims."
],
"result": "pass"
},
{
"service": "platform",
"scope": "resolver + topology + context endpoints",
"testsRun": 14,
"testsPassed": 14,
"evidenceFile": "evidence/platform-tenant-isolation-targeted.txt",
"behaviorVerified": [
"Resolver returns tenant_missing and tenant_conflict errors deterministically.",
"Topology endpoints return bad request when tenant header is missing.",
"Topology and context data are isolated across tenants with overlapping identifiers."
],
"result": "pass"
},
{
"service": "scanner",
"scope": "resolver + scans + triage + unknowns + webhook lookup",
"testsRun": 13,
"testsPassed": 13,
"evidenceFile": "evidence/scanner-tenant-isolation-targeted.txt",
"behaviorVerified": [
"Resolver returns deterministic missing/conflict outcomes with canonical and legacy headers.",
"Cross-tenant scan status, triage evidence, and callgraph submission attempts are rejected.",
"Webhook source resolution is tenant-scoped and same sourceName collisions do not cross-dispatch."
],
"result": "pass"
},
{
"service": "graph",
"scope": "tenant resolver + authorization alignment",
"testsRun": 7,
"testsPassed": 7,
"evidenceFile": "evidence/graph-tenant-alignment-targeted.txt",
"observedHttpStatuses": [
"200",
"400",
"403"
],
"behaviorVerified": [
"Canonical tenant header + scope yields success for query path.",
"Conflicting tenant headers produce tenant_conflict/bad request behavior.",
"Missing required scope returns forbidden deterministically."
],
"result": "pass"
}
],
"summary": {
"testsRun": 51,
"testsPassed": 51,
"testsFailed": 0,
"crossTenantAttemptsDenied": true
},
"verdict": "pass"
}

View File

@@ -0,0 +1,38 @@
{
"tier": 2,
"check": "ui-verification",
"feature": "multi-tenant-same-api-key-selection",
"runId": "run-001",
"timestamp": "2026-02-22T23:18:33Z",
"result": "pass",
"specFile": "tests/e2e/tenant-switch-matrix.spec.ts",
"testCount": 2,
"passCount": 2,
"failCount": 0,
"pageMatrix": [
"mission-control",
"releases",
"security",
"evidence",
"ops",
"setup",
"administration"
],
"viewports": [
"desktop",
"mobile"
],
"assertions": [
"Tenant selector switches tenant and keeps selected tenant visible in header.",
"Tenant selection persists across primary section navigation and page reload.",
"Network requests captured by fixture include selected tenant header context after switch."
],
"artifacts": {
"evidenceFile": "evidence/web-tenant-playwright-matrix.txt",
"traceFiles": [
"artifacts/playwright-traces/tenant-switch-matrix-Multi-8557c-s-primary-sections-desktop-.trace.zip",
"artifacts/playwright-traces/tenant-switch-matrix-Multi-a4393-rsistent-on-mobile-viewport.trace.zip"
]
},
"notes": "Desktop and mobile tenant switch matrix passed with deterministic fixtures for tenant-alpha and tenant-bravo."
}

View File

@@ -0,0 +1,10 @@
[
{
"name": "web-playwright-tenant-matrix-expanded",
"command": "npx playwright test tests/e2e/tenant-switch-matrix.spec.ts --reporter=list",
"exitCode": 0,
"testsRun": 10,
"testsPassed": 10,
"evidenceFile": "evidence/web-tenant-playwright-matrix-expanded.txt"
}
]

View File

@@ -0,0 +1,35 @@
{
"tier": 2,
"check": "ui-verification",
"feature": "multi-tenant-same-api-key-selection",
"runId": "run-002",
"timestamp": "2026-02-23T05:45:43Z",
"result": "pass",
"specFile": "tests/e2e/tenant-switch-matrix.spec.ts",
"testCount": 10,
"passCount": 10,
"failCount": 0,
"pageMatrix": [
"mission-control",
"releases",
"security",
"evidence",
"ops",
"setup",
"administration"
],
"viewports": [
"desktop",
"mobile"
],
"assertions": [
"Tenant selector switches to selected tenant and remains visible across section navigation.",
"Each primary page route preserves the selected tenant and renders expected section markers.",
"Captured tenant-scoped API calls never leak to a different tenant during switch and reverse-switch flows."
],
"artifacts": {
"evidenceFile": "evidence/web-tenant-playwright-matrix-expanded.txt",
"traceFiles": []
},
"notes": "Expanded matrix now covers 10 Playwright scenarios including per-section route assertions and bidirectional tenant switching."
}