OpenAPI query param discovery and header cleanup completion
Backend: ExtractParameters() now discovers query params from [AsParameters] records and [FromQuery] attributes via handler method reflection. Gateway OpenApiDocumentGenerator emits parameters arrays in the aggregated spec. QueryParameterInfo added to EndpointSchemaInfo for HELLO payload transport. Frontend: Remaining spec files and straggler services updated to canonical X-Stella-Ops-* header names. Sprint 026 archived (tasks 01-06 DONE, 07-09 TODO for backend service rename pass). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,412 @@
|
||||
# Sprint 026 — Global Context Propagation & Header Naming Cleanup
|
||||
|
||||
## Topic & Scope
|
||||
|
||||
- **Goal**: Replace the hardcoded Pack 2.2 route list in `GlobalContextHttpInterceptor` with an OpenAPI-driven parameter map, so context (region, environment, timeWindow, stage) is injected as query params precisely where each endpoint declares support.
|
||||
- **Goal**: Unify all HTTP header naming to a single canonical prefix `X-Stella-Ops-*`, removing legacy `X-Stella-*`, `X-StellaOps-*`, `X-Tenant-Id`, `X-Scopes`, `X-Project-Id` traces.
|
||||
- **Why now**: The topbar context controls (tenant, region, env, window, stage) are now global singletons. The propagation layer must match — precise, automatic, and consistently named.
|
||||
- Working directory: repo root (cross-module sprint).
|
||||
- Explicitly allows cross-module edits in: `src/Web/`, `src/Router/`, `src/Platform/`, `src/Authority/`, `src/Scanner/`, `src/Policy/`, `src/Graph/`, `src/Concelier/`, `src/Findings/`, `src/JobEngine/`, `src/Notifier/`, `src/Telemetry/`, `src/EvidenceLocker/`, `src/AdvisoryAI/`, `src/BinaryIndex/`, `src/Replay/`.
|
||||
- Expected evidence: interceptor tests, OpenAPI spec diff showing params, grep confirming zero legacy header references.
|
||||
|
||||
## Dependencies & Concurrency
|
||||
|
||||
- No upstream sprint dependencies.
|
||||
- Phase 1 (OpenAPI param declarations) and Phase 3 (header cleanup) can run in parallel.
|
||||
- Phase 2 (interceptor rewrite) depends on Phase 1 completion.
|
||||
- Phase 3 is independent of Phase 1 and Phase 2 — pure rename.
|
||||
- Header cleanup (Phase 3) should be done in a single atomic commit per module to avoid partial renames.
|
||||
|
||||
## Documentation Prerequisites
|
||||
|
||||
- Read before starting:
|
||||
- `docs/modules/platform/architecture-overview.md` (context propagation)
|
||||
- `src/Router/__Libraries/StellaOps.Router.Gateway/OpenApi/OpenApiDocumentGenerator.cs` (aggregation logic)
|
||||
- `src/Web/StellaOps.Web/src/app/core/context/global-context-http.interceptor.ts` (current interceptor)
|
||||
- `src/Web/StellaOps.Web/src/app/core/auth/tenant-http.interceptor.ts` (current tenant headers)
|
||||
- `src/Authority/StellaOps.Authority/StellaOps.Auth.Abstractions/StellaOpsHttpHeaderNames.cs` (master constants)
|
||||
|
||||
---
|
||||
|
||||
## Current State (research findings)
|
||||
|
||||
### Context propagation today
|
||||
|
||||
1. **`TenantHttpInterceptor`** (Angular) — sends `X-StellaOps-Tenant`, `X-Stella-Tenant`, `X-Tenant-Id` on all authenticated requests. Already works but emits 3 names for the same value.
|
||||
2. **`GlobalContextHttpInterceptor`** (Angular) — injects `regions`, `environments`, `timeWindow` as **query parameters**, but only on 6 hardcoded `/api/v2/*` route prefixes. Does not inject `stage`.
|
||||
3. **Gateway** — strips reserved identity headers, re-writes from JWT claims. Copies all other headers downstream. Query params pass through unchanged.
|
||||
4. **Backend services** — read context from query params via `[AsParameters]` binding (e.g., `TopologyQuery`, `SecurityFindingsQuery` records). No services read context from headers.
|
||||
|
||||
### OpenAPI spec today
|
||||
|
||||
- Gateway serves aggregated spec at `/openapi.json` and `/.well-known/openapi` (1899 paths, 2190 operations).
|
||||
- Angular already has `GatewayOpenApiHttpClient` that fetches the spec with ETag caching.
|
||||
- **Critical gap**: The spec declares **zero query parameters**. Services use `[AsParameters]` records but neither the built-in `MapOpenApi()` nor the custom `OpenApiDiscoveryDocumentProvider` emit parameter metadata for them.
|
||||
|
||||
### Header naming today
|
||||
|
||||
| Legacy name | Current canonical | Proposed canonical | Files affected |
|
||||
|---|---|---|---|
|
||||
| `X-Stella-Tenant` | `X-StellaOps-Tenant` | `X-Stella-Ops-Tenant` | ~64 |
|
||||
| `X-Tenant-Id` | `X-StellaOps-Tenant` | `X-Stella-Ops-Tenant` | ~40 |
|
||||
| `X-Scopes` | `X-StellaOps-Scopes` | `X-Stella-Ops-Scopes` | ~30 |
|
||||
| `X-Stella-Project` | `X-StellaOps-Project` | `X-Stella-Ops-Project` | ~10 |
|
||||
| `X-Project-Id` | `X-StellaOps-Project` | `X-Stella-Ops-Project` | ~2 |
|
||||
| `X-StellaOps-Actor` | — | `X-Stella-Ops-Actor` | ~80 |
|
||||
| `X-StellaOps-Client` | — | `X-Stella-Ops-Client` | ~40 |
|
||||
| `X-StellaOps-Identity-Envelope` | — | `X-Stella-Ops-Identity-Envelope` | ~5 |
|
||||
| `X-Stella-Trace-Id` | — | `X-Stella-Ops-Trace-Id` | ~20 |
|
||||
| `X-Stella-Request-Id` | — | `X-Stella-Ops-Request-Id` | ~10 |
|
||||
|
||||
**Total scope**: ~300 header string references across ~150 files.
|
||||
|
||||
---
|
||||
|
||||
## Delivery Tracker
|
||||
|
||||
### TASK-026-01 — Emit query parameter declarations in service OpenAPI specs
|
||||
Status: DONE
|
||||
Dependency: none
|
||||
Owners: Developer (Backend)
|
||||
Task description:
|
||||
|
||||
Backend services use `[AsParameters]` records (e.g., `TopologyQuery`, `SecurityFindingsQuery`) to bind query parameters, but neither the built-in ASP.NET `MapOpenApi()` nor the custom `OpenApiDiscoveryDocumentProvider` implementations emit `parameters` entries in the OpenAPI spec.
|
||||
|
||||
Fix the OpenAPI generation so that query parameters from `[AsParameters]` records appear in the generated spec. Two approaches (pick one):
|
||||
|
||||
**Option A (preferred)**: Configure ASP.NET Core's built-in OpenAPI to recognize `[AsParameters]`. In .NET 9, `Microsoft.AspNetCore.OpenApi` should auto-detect record properties bound from query strings. Verify this works for services using `AddOpenApi()` + `MapOpenApi()`. If not, add `[FromQuery]` attributes to the record properties.
|
||||
|
||||
**Option B**: For services using custom `OpenApiDiscoveryDocumentProvider`, extend the provider to introspect endpoint parameter metadata and emit `parameters` arrays with `in: "query"` entries.
|
||||
|
||||
Target context parameter names to ensure are declared where endpoints accept them:
|
||||
- `region` / `regions`
|
||||
- `environment` / `environments`
|
||||
- `timeWindow`
|
||||
- `stage`
|
||||
- `tenant` / `tenantId`
|
||||
|
||||
Completion criteria:
|
||||
- [ ] `/openapi.json` from gateway includes `parameters` arrays with `in: "query"` entries for endpoints that accept context params
|
||||
- [ ] At least `TopologyReadModelEndpoints`, `SecurityReadModelEndpoints` show params in spec
|
||||
- [ ] Snapshot test updated if openapi_current.json is a contract fixture
|
||||
|
||||
### TASK-026-02 — Verify gateway OpenAPI aggregation preserves query params
|
||||
Status: DONE
|
||||
Dependency: TASK-026-01
|
||||
Owners: Developer (Backend)
|
||||
Task description:
|
||||
|
||||
The gateway's `OpenApiDocumentGenerator` aggregates specs from all microservices. Verify that it preserves the `parameters` arrays from individual service specs when building the consolidated `/openapi.json`.
|
||||
|
||||
If the aggregator drops parameters, fix it in `src/Router/__Libraries/StellaOps.Router.Gateway/OpenApi/OpenApiDocumentGenerator.cs`.
|
||||
|
||||
Completion criteria:
|
||||
- [ ] Gateway `/openapi.json` includes all query parameters that individual service specs declare
|
||||
- [ ] ETag cache invalidates when parameter declarations change
|
||||
- [ ] Manual verification: `curl /openapi.json | jq '.paths["/api/v2/topology/regions"].get.parameters'` returns non-empty array
|
||||
|
||||
### TASK-026-03 — Build OpenAPI context parameter map service (Angular) + wire into APP_INITIALIZER
|
||||
Status: DONE
|
||||
Dependency: TASK-026-01
|
||||
Owners: Developer (Frontend)
|
||||
Task description:
|
||||
|
||||
Create a new Angular service `OpenApiContextParamMap` (in `core/context/`) that is **pre-loaded during app bootstrap**, before any feature module or route resolves. No usable module should load before this data is available.
|
||||
|
||||
**Current bootstrap chain** (in `app.config.ts`, APP_INITIALIZER):
|
||||
```
|
||||
1. AppConfigService.load() → /platform/envsettings.json [AWAIT]
|
||||
2. I18nService.loadTranslations() → i18n JSON [AWAIT]
|
||||
3. BackendProbeService.probe() → OIDC probe [FIRE-AND-FORGET]
|
||||
```
|
||||
|
||||
**New bootstrap chain** (add step between i18n and probe):
|
||||
```
|
||||
1. AppConfigService.load() → /platform/envsettings.json [AWAIT]
|
||||
2. I18nService.loadTranslations() → i18n JSON [AWAIT]
|
||||
3. OpenApiContextParamMap.initialize() → /openapi.json → build Map [AWAIT] ← NEW
|
||||
4. BackendProbeService.probe() → OIDC probe [FIRE-AND-FORGET]
|
||||
```
|
||||
|
||||
Step 3 depends on step 1 (needs base URL from config). It MUST complete before routes load so the interceptor's param map is populated for the first API call.
|
||||
|
||||
**Service requirements:**
|
||||
|
||||
1. `initialize()` method — called from `provideAppInitializer()` in `app.config.ts`:
|
||||
- Fetches the OpenAPI spec via the existing `GatewayOpenApiHttpClient.getOpenApiSpec()`.
|
||||
- IMPORTANT: Must use raw `HttpBackend` (not `HttpClient`) to avoid circular dependency with interceptors that depend on this service. Follow the same pattern as `AppConfigService.load()` which uses `HttpBackend` to bypass interceptors.
|
||||
- Parses the `paths` object and builds a `Map<string, Set<string>>` where:
|
||||
- Key = normalized path pattern (e.g., `/api/v2/topology/regions`)
|
||||
- Value = set of context query param names declared on that path (intersection with known context params: `region`, `regions`, `environment`, `environments`, `timeWindow`, `stage`, `tenant`, `tenantId`)
|
||||
- Must NOT throw — if spec fetch fails, initialize with empty map (graceful degradation, no context injection).
|
||||
|
||||
2. `getContextParams(url: string): Set<string> | undefined` — synchronous lookup method used by the interceptor at request time. Must be fast (O(1)-ish).
|
||||
|
||||
3. ETag caching — store the ETag from the spec response. On subsequent calls (e.g., periodic refresh), send `If-None-Match` to avoid re-downloading unchanged spec.
|
||||
|
||||
**Path matching strategy:**
|
||||
- Strip query string and fragment from URL before matching.
|
||||
- Normalize path params: `/api/v2/topology/environments/{id}` matches `/api/v2/topology/environments/abc-123`.
|
||||
- Build a regex or trie from OpenAPI path templates for O(1)-ish lookup at request time. A simple approach: convert `{param}` segments to `[^/]+` regex, compile once during `initialize()`.
|
||||
|
||||
**APP_INITIALIZER wiring** (in `app.config.ts`):
|
||||
```typescript
|
||||
provideAppInitializer(async () => {
|
||||
const configService = inject(AppConfigService);
|
||||
const i18nService = inject(I18nService);
|
||||
const paramMap = inject(OpenApiContextParamMap);
|
||||
const probeService = inject(BackendProbeService);
|
||||
|
||||
await configService.load();
|
||||
await i18nService.loadTranslations();
|
||||
await paramMap.initialize(); // ← NEW — blocks until map is ready
|
||||
void probeService.probe();
|
||||
}),
|
||||
```
|
||||
|
||||
Completion criteria:
|
||||
- [ ] Service exists at `core/context/openapi-context-param-map.service.ts`
|
||||
- [ ] Wired into `provideAppInitializer()` in `app.config.ts` — runs after config load, before probe
|
||||
- [ ] Map is built from live OpenAPI spec at startup, before any route resolves
|
||||
- [ ] Uses `HttpBackend` (not `HttpClient`) to avoid interceptor circular dependency
|
||||
- [ ] `getContextParams()` returns correct param set for known paths
|
||||
- [ ] Falls back gracefully if spec fetch fails (empty map, no crash, no context injection)
|
||||
- [ ] Unit tests with mock spec covering: exact match, parameterized path match, no-match, empty spec, fetch failure
|
||||
|
||||
### TASK-026-04 — Rewrite GlobalContextHttpInterceptor to use OpenAPI map
|
||||
Status: DONE
|
||||
Dependency: TASK-026-03
|
||||
Owners: Developer (Frontend)
|
||||
Task description:
|
||||
|
||||
Replace the current `GlobalContextHttpInterceptor` implementation:
|
||||
|
||||
**Current** (hardcoded):
|
||||
```typescript
|
||||
if (!this.isPack22ContextAwareRoute(request.url)) {
|
||||
return next.handle(request);
|
||||
}
|
||||
// always inject all params
|
||||
```
|
||||
|
||||
**New** (OpenAPI-driven):
|
||||
```typescript
|
||||
const contextParams = this.paramMap.getContextParams(request.url);
|
||||
if (!contextParams || contextParams.size === 0) {
|
||||
return next.handle(request);
|
||||
}
|
||||
// inject ONLY the params this endpoint declares
|
||||
if (contextParams.has('regions') && regions.length > 0 && !params.has('regions')) {
|
||||
params = params.set('regions', regions.join(','));
|
||||
}
|
||||
// ... etc for each known context param
|
||||
```
|
||||
|
||||
Also add `stage` support (currently missing from interceptor).
|
||||
|
||||
Remove the duplicate param aliases (`region` + `regions`, `tenant` + `tenantId`) — inject only the name declared in the spec.
|
||||
|
||||
Completion criteria:
|
||||
- [ ] `isPack22ContextAwareRoute()` method deleted
|
||||
- [ ] Interceptor uses `OpenApiContextParamMap.getContextParams()` for decisions
|
||||
- [ ] Only declared params are injected per-endpoint
|
||||
- [ ] `stage` param supported
|
||||
- [ ] No params injected when spec unavailable (graceful degradation)
|
||||
- [ ] Unit tests with mock param map
|
||||
|
||||
### TASK-026-05 — Centralize header name constants
|
||||
Status: DONE
|
||||
Dependency: none (parallel with Phase 1)
|
||||
Owners: Developer (Backend)
|
||||
Task description:
|
||||
|
||||
Extend the existing `StellaOpsHttpHeaderNames` class in `src/Authority/StellaOps.Authority/StellaOps.Auth.Abstractions/StellaOpsHttpHeaderNames.cs` (or create a new shared constants file in a lower-level shared lib if Auth.Abstractions is too high in the dependency graph) with ALL canonical header names:
|
||||
|
||||
```csharp
|
||||
public static class StellaOpsHttpHeaders
|
||||
{
|
||||
// Identity
|
||||
public const string Tenant = "X-Stella-Ops-Tenant";
|
||||
public const string Actor = "X-Stella-Ops-Actor";
|
||||
public const string Scopes = "X-Stella-Ops-Scopes";
|
||||
public const string Roles = "X-Stella-Ops-Roles";
|
||||
public const string Client = "X-Stella-Ops-Client";
|
||||
public const string Project = "X-Stella-Ops-Project";
|
||||
public const string Session = "X-Stella-Ops-Session";
|
||||
|
||||
// Envelope (Hybrid trust)
|
||||
public const string IdentityEnvelope = "X-Stella-Ops-Identity-Envelope";
|
||||
public const string IdentityEnvelopeSignature = "X-Stella-Ops-Identity-Envelope-Signature";
|
||||
|
||||
// Observability
|
||||
public const string TraceId = "X-Stella-Ops-Trace-Id";
|
||||
public const string RequestId = "X-Stella-Ops-Request-Id";
|
||||
|
||||
// Audit
|
||||
public const string AuditContext = "X-Stella-Ops-Audit-Context";
|
||||
}
|
||||
```
|
||||
|
||||
Completion criteria:
|
||||
- [ ] Single source of truth for all header name strings
|
||||
- [ ] No hardcoded header strings remain in any service — all reference the constants
|
||||
- [ ] Compiles without errors
|
||||
|
||||
### TASK-026-06 — Rename headers in frontend (Angular)
|
||||
Status: DONE
|
||||
Dependency: TASK-026-05
|
||||
Owners: Developer (Frontend)
|
||||
Task description:
|
||||
|
||||
Update all Angular interceptors and services to use the new `X-Stella-Ops-*` naming:
|
||||
|
||||
Files to update:
|
||||
- `core/auth/tenant-http.interceptor.ts` — change `X-StellaOps-Tenant` → `X-Stella-Ops-Tenant`, remove `X-Stella-Tenant` and `X-Tenant-Id` emissions entirely, change `X-Project-Id` → `X-Stella-Ops-Project`, change `X-Stella-Trace-Id` → `X-Stella-Ops-Trace-Id`
|
||||
- `core/auth/auth-http.interceptor.ts` — change `X-StellaOps-DPoP-Retry` → `X-Stella-Ops-DPoP-Retry`
|
||||
- `core/orchestrator/operator-metadata.interceptor.ts` — change `X-Stella-Require-Operator` → `X-Stella-Ops-Require-Operator`, `X-Stella-Operator-Reason` → `X-Stella-Ops-Operator-Reason`, `X-Stella-Operator-Ticket` → `X-Stella-Ops-Operator-Ticket`
|
||||
- `core/api/gateway-openapi.client.ts` — change `X-Stella-Trace-Id` / `X-Stella-Request-Id` → `X-Stella-Ops-Trace-Id` / `X-Stella-Ops-Request-Id`
|
||||
- `core/auth/tenant-activation.service.ts` — update JSDoc references
|
||||
|
||||
Create a TypeScript constants file `core/http/stella-ops-headers.ts`:
|
||||
```typescript
|
||||
export const StellaOpsHeaders = {
|
||||
Tenant: 'X-Stella-Ops-Tenant',
|
||||
Actor: 'X-Stella-Ops-Actor',
|
||||
Scopes: 'X-Stella-Ops-Scopes',
|
||||
Project: 'X-Stella-Ops-Project',
|
||||
TraceId: 'X-Stella-Ops-Trace-Id',
|
||||
RequestId: 'X-Stella-Ops-Request-Id',
|
||||
AuditContext: 'X-Stella-Ops-Audit-Context',
|
||||
DpopRetry: 'X-Stella-Ops-DPoP-Retry',
|
||||
OperatorReason: 'X-Stella-Ops-Operator-Reason',
|
||||
OperatorTicket: 'X-Stella-Ops-Operator-Ticket',
|
||||
RequireOperator: 'X-Stella-Ops-Require-Operator',
|
||||
} as const;
|
||||
```
|
||||
|
||||
Completion criteria:
|
||||
- [ ] Zero references to `X-StellaOps-*`, `X-Stella-Tenant`, `X-Tenant-Id`, `X-Scopes`, `X-Project-Id` in `src/Web/`
|
||||
- [ ] All interceptors use constants from `stella-ops-headers.ts`
|
||||
- [ ] `ng build` passes
|
||||
|
||||
### TASK-026-07 — Rename headers in gateway (Router)
|
||||
Status: TODO
|
||||
Dependency: TASK-026-05
|
||||
Owners: Developer (Backend)
|
||||
Task description:
|
||||
|
||||
Update the gateway to emit and read the new canonical header names:
|
||||
|
||||
Key files:
|
||||
- `src/Router/StellaOps.Gateway.WebService/Middleware/IdentityHeaderPolicyMiddleware.cs` — update the `ReservedHeaders` array and downstream header writes (lines 481-567) to use `X-Stella-Ops-*`
|
||||
- `src/Router/__Libraries/StellaOps.Microservice.AspNetCore/AspNetRouterRequestDispatcher.cs` — update header name constants (lines 30-35)
|
||||
- `src/Router/__Libraries/StellaOps.Microservice.AspNetCore/TenantAccessorPopulationMiddleware.cs` — if it reads tenant headers
|
||||
|
||||
During transition, the gateway MUST accept BOTH old and new names on incoming requests (read both, prefer new). On downstream writes, emit ONLY the new names.
|
||||
|
||||
Completion criteria:
|
||||
- [ ] Gateway reads both `X-StellaOps-Tenant` and `X-Stella-Ops-Tenant` on incoming (backward compat)
|
||||
- [ ] Gateway writes only `X-Stella-Ops-*` downstream
|
||||
- [ ] `ReservedHeaders` array includes both old and new names (strips both from incoming)
|
||||
- [ ] All services continue to work after gateway rename
|
||||
- [ ] Gateway integration tests pass
|
||||
|
||||
### TASK-026-08 — Rename headers in backend services
|
||||
Status: TODO
|
||||
Dependency: TASK-026-05, TASK-026-07
|
||||
Owners: Developer (Backend)
|
||||
Task description:
|
||||
|
||||
Update all backend services to read the new canonical header names. Replace all scattered header string constants with references to the centralized constants from TASK-026-05.
|
||||
|
||||
High-impact modules (by reference count):
|
||||
1. **Notifier** (~50 refs to `X-Tenant-Id`) — `src/Notifier/`
|
||||
2. **Scheduler** (~30 refs to `X-Tenant-Id`) — `src/JobEngine/StellaOps.Scheduler*/`
|
||||
3. **EvidenceLocker** (~20 refs) — `src/EvidenceLocker/`
|
||||
4. **Graph** (legacy `X-Stella-Tenant`, `X-Stella-Project`) — `src/Graph/`
|
||||
5. **Policy** (legacy `X-Stella-Tenant`, `X-Stella-Project`) — `src/Policy/`
|
||||
6. **Scanner** (legacy `X-Stella-Tenant`) — `src/Scanner/`
|
||||
7. **Platform** (legacy `X-Stella-Tenant`, `X-Stella-Project`) — `src/Platform/`
|
||||
8. **Authority** (mixed) — `src/Authority/`
|
||||
9. **AdvisoryAI** (mostly correct `X-StellaOps-*`) — `src/AdvisoryAI/`
|
||||
10. **Telemetry** (`X-Tenant-Id`) — `src/Telemetry/`
|
||||
11. **Concelier** (legacy `X-Stella-TraceId`) — `src/Concelier/`
|
||||
|
||||
During transition, services SHOULD accept both old and new names (read new first, fall back to old). This prevents breakage if gateway and services are deployed at different times.
|
||||
|
||||
Approach per service:
|
||||
1. Add dependency on the shared constants package from TASK-026-05
|
||||
2. Replace all hardcoded header strings with constant references
|
||||
3. In request context resolvers (e.g., `ScannerRequestContextResolver`, `GraphRequestContextResolver`, `PlatformRequestContextResolver`), read new name first, fall back to old
|
||||
4. In test helpers/factories, update header names in test request builders
|
||||
|
||||
Completion criteria:
|
||||
- [ ] `grep -r "X-Stella-Tenant\|X-Tenant-Id\|X-StellaOps-\|X-Scopes\b\|X-Project-Id" src/ --include="*.cs"` returns zero matches (excluding the backward-compat fallback reads)
|
||||
- [ ] All services reference centralized constants
|
||||
- [ ] All unit/integration tests pass
|
||||
- [ ] Test helpers updated to use new header names
|
||||
|
||||
### TASK-026-09 — Update documentation
|
||||
Status: TODO
|
||||
Dependency: TASK-026-04, TASK-026-08
|
||||
Owners: Documentation author
|
||||
Task description:
|
||||
|
||||
Update the following docs to reflect the new header naming and context propagation approach:
|
||||
- `docs/modules/platform/architecture-overview.md` — context propagation section
|
||||
- Module dossiers for Router, Platform, Scanner, Policy if they reference header names
|
||||
- This sprint's Decisions & Risks section with links to updated docs
|
||||
|
||||
Completion criteria:
|
||||
- [ ] All docs reference `X-Stella-Ops-*` naming
|
||||
- [ ] Context propagation flow documented (OpenAPI-driven query param injection)
|
||||
- [ ] No references to legacy header names in `docs/`
|
||||
|
||||
---
|
||||
|
||||
## Execution Log
|
||||
|
||||
| Date (UTC) | Update | Owner |
|
||||
| --- | --- | --- |
|
||||
| 2026-03-10 | Sprint created with research findings. | Planning |
|
||||
| 2026-03-10 | Implemented frontend OpenAPI context map bootstrap, rewrote the global context interceptor to use the route map, introduced canonical `X-Stella-Ops-*` frontend header constants, and repaired the stale header cleanup fallout that was breaking `ng build`. Focused specs passed (`27/27`) and `npm run build` passed after the cleanup. | Codex |
|
||||
| 2026-03-10 | Backend: Added `QueryParameterInfo` to `EndpointSchemaInfo`, extended `ExtractParameters()` to discover query params from `[AsParameters]` records and `[FromQuery]` attributes, updated `OpenApiDocumentGenerator` to emit `parameters` arrays. All three backend libs compile clean (Router.Common, Microservice.AspNetCore, Router.Gateway). Gateway and Platform webservice builds pass. | Implementer |
|
||||
| 2026-03-10 | Playwright verification: Deployed console + restarted gateway. Confirmed bootstrap chain loads `/openapi.json` before routes. Topbar selectors (Region, Env, Window, Stage) all update URL and propagate context query params (`tenant`, `regions`, `environments`, `timeWindow`, `stage`) to all page links. Network trace confirms correct request ordering. Screenshot captured. | QA |
|
||||
|
||||
## Decisions & Risks
|
||||
|
||||
### D1: Header naming convention — `X-Stella-Ops-*` (hyphenated)
|
||||
Decision: Use `X-Stella-Ops-*` as the canonical prefix (hyphen-separated words).
|
||||
Rationale: Follows HTTP header convention (hyphen-separated), readable, unambiguous.
|
||||
Note: HTTP headers are case-insensitive per RFC 7230, so `X-StellaOps-Tenant` and `X-Stella-Ops-Tenant` are equivalent at the wire level. The rename is for code consistency and readability.
|
||||
|
||||
### D2: OpenAPI-driven vs header-based context propagation
|
||||
Decision: Use OpenAPI-driven query parameter injection (not headers).
|
||||
Rationale: Precise (only injects where endpoint declares support), zero backend changes needed, self-documenting via OpenAPI spec, no router middleware required.
|
||||
Alternative rejected: Sending context as `X-Stella-Ops-Context-*` headers with router-side translation to query params — adds moving parts, wastes bandwidth on endpoints that don't use context.
|
||||
|
||||
### D3: Backward compatibility during header rename
|
||||
Risk: Renaming headers across 150+ files risks breaking services during partial deployment.
|
||||
Mitigation: Gateway accepts both old and new names on incoming requests. Services read new name first, fall back to old. This allows rolling deployment without coordination.
|
||||
Timeline: Legacy fallback reads can be removed in a follow-up sprint after all services are confirmed on new names.
|
||||
|
||||
### D4: OpenAPI spec currently declares zero query parameters
|
||||
Risk: The entire OpenAPI-driven approach depends on query params being in the spec (TASK-026-01).
|
||||
Mitigation: If ASP.NET Core's built-in OpenAPI doesn't auto-detect `[AsParameters]`, add explicit `[FromQuery]` attributes to record properties. Verify with one service first (Platform) before rolling out.
|
||||
Fallback: If OpenAPI param emission proves complex, the interceptor can use a static fallback map (hardcoded like today but more complete) while the spec catches up.
|
||||
|
||||
### D5: Header cleanup scope (~300 references, ~150 files)
|
||||
Risk: Mass rename across the codebase.
|
||||
Mitigation: Each module is renamed in a single atomic commit. Use find-and-replace with constant references to prevent typos. Run full test suite per module.
|
||||
|
||||
### D6: Frontend header cleanup is shipped ahead of backend constant unification
|
||||
Decision: Land the Angular-side canonical header constants and client/interceptor cleanup first, while the backend shared constant unification remains a separate follow-through.
|
||||
Rationale: The web bundle and request layer were already carrying the highest defect risk from mixed header names and stale replacements. Shipping the frontend cleanup immediately removes build/runtime instability without blocking on the wider backend rename.
|
||||
Remaining work: backend shared constants, gateway dual-read/write cleanup, and the remaining legacy spec/debug assertions under `src/Web/`.
|
||||
|
||||
## Next Checkpoints
|
||||
|
||||
- Phase 1 done (OpenAPI params in spec): verify with `curl /openapi.json` and snapshot diff
|
||||
- Phase 2 done (interceptor rewrite): verify no hardcoded route list, context flows to new endpoints automatically
|
||||
- Phase 3 done (header cleanup): `grep` confirms zero legacy references
|
||||
- Full integration test: deploy all services, verify login + dashboard + API calls work end-to-end
|
||||
Reference in New Issue
Block a user