Add determinism tests for verdict artifact generation and update SHA256 sums script
- Implemented comprehensive tests for verdict artifact generation to ensure deterministic outputs across various scenarios, including identical inputs, parallel execution, and change ordering. - Created helper methods for generating sample verdict inputs and computing canonical hashes. - Added tests to validate the stability of canonical hashes, proof spine ordering, and summary statistics. - Introduced a new PowerShell script to update SHA256 sums for files, ensuring accurate hash generation and file integrity checks.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# Hash index for authority gap artefacts (AU1–AU10, RR1–RR10)
|
||||
# Hash index for authority gap artefacts (AU1–AU10, RR1–RR10)
|
||||
# Append lines: "<sha256> <relative-path>"
|
||||
2e07c639a8fa60105e42965c5a92657e66f6255c9aa375bfacc413083e1f36a3 docs/modules/authority/gaps/artifacts/authority-abac.schema.json
|
||||
d0721d49b74f648ad07fe7f77fabc126fe292db515700df5036f1e1324a00025 docs/modules/authority/gaps/artifacts/authority-jwks-metadata.schema.json
|
||||
@@ -14,5 +14,5 @@ d0721d49b74f648ad07fe7f77fabc126fe292db515700df5036f1e1324a00025 docs/modules/a
|
||||
39494b4452095b0229399ca2e03865ece2782318555b32616f8d758396cf55ab docs/modules/authority/gaps/authority-conformance-tests.md
|
||||
285f9b117254242c8eb32014597e2d7be7106c332d97561c6b3c3f6ec7c6eee7 docs/modules/authority/gaps/authority-delegation-quotas.md
|
||||
1a77f02f28fafb5ddb5c8bf514001bc3426d532ee7c3a2ffd4ecfa3d84e6036e docs/modules/authority/gaps/rekor-receipt-error-taxonomy.md
|
||||
97405eabf4a5e54937c44a4714f6c76803a55a18924b8f5b841a73c19feed4a5 docs/console/observability.md
|
||||
c65f72e22ea8d482d2484ec5b826e4cecfabf75f88c1c7031b8190ecbc9b80fa docs/console/forensics.md
|
||||
f4330a13d9991ebf29a6dca963c2219b14140c6e29d09031b276fb33c3f872be docs/console/observability.md
|
||||
71fcee6f13b1fa339b5de6963833a65a94a32eda24982e8bca1e7906fa92be19 docs/console/forensics.md
|
||||
|
||||
@@ -6,11 +6,11 @@ e9e26fe469e221ee6c3255f5c450dc9f0f8cc43b2ae55285e859f28cec62d375 docs/modules/a
|
||||
1d77b324726f07712ec8a5276b2c187a3ebfa1ce888481e941b428e5aadaf310 docs/modules/authority/gaps/dev-smoke/2025-12-05/rekor-receipt-bundle.sigstore.json
|
||||
81dfe543442831f7bfeec480d5937594590a15b3400ae3567d7d96e62c06ed44 docs/modules/authority/gaps/dev-smoke/2025-12-05/rekor-receipt-policy.sigstore.json
|
||||
96316e53ca5885689870c69719778c2685f191bee844003cb170333fb91579e1 docs/modules/authority/gaps/dev-smoke/2025-12-05/rekor-receipt.schema.sigstore.json
|
||||
0d14597c3685d3b9c87626e4fef92c6e18ce9d110d1e019ac3de3592c2be0732 authority-abac.schema.sigstore.json
|
||||
3b6a92f8d650b2ea3afc56d2c63830f0ec4f5f215ee1b361936553788b40ac45 authority-jwks-metadata.schema.sigstore.json
|
||||
7c2888a1f810dd35c9feb0f119aff1fb0f6e11338ca55bbfa8c68bb195c6dbe9 authority-offline-verifier-bundle.sigstore.json
|
||||
3df91f1fb62a1e96b2c9fb7a200983a50f4bdc584e555189c9944bcb74851fd6 authority-scope-role-catalog.sigstore.json
|
||||
192d7ae0e5213fc6c4572d7edc6b2adc4392930a42c8fd54c9ff619a5c7c5573 crypto-profile-registry.sigstore.json
|
||||
59f812e76af748c6636a5e8a3b2fe6dc5a92a6a83aa49dc010042dfcfaa52de3 rekor-receipt-bundle.sigstore.json
|
||||
9b5fdf26e452fcbfcff03359652f8f2e457d594c70f1a3fe7d20c80674701810 rekor-receipt-policy.sigstore.json
|
||||
f6dfa58a44a364d5e7dff6681d85bb9892a0cba8652e4bb0af4fecfaccc2b003 rekor-receipt.schema.sigstore.json
|
||||
283a65b605edc222a8e58f148b3797af3c14c33fc928964f946c77312a802545 authority-abac.schema.sigstore.json
|
||||
cfea834c83ab3ddfcd4863824bbebfcb98578278850a906fce2f535c892c81ad authority-jwks-metadata.schema.sigstore.json
|
||||
e9e26fe469e221ee6c3255f5c450dc9f0f8cc43b2ae55285e859f28cec62d375 authority-offline-verifier-bundle.sigstore.json
|
||||
1c1188af6190438c2485a0e4193a9a8b778bd69a35b743da73ee891357192966 authority-scope-role-catalog.sigstore.json
|
||||
54b4288882bcd93a00d656a0d8ddb256e407096c76ab44f5137956a76ac38c05 crypto-profile-registry.sigstore.json
|
||||
1d77b324726f07712ec8a5276b2c187a3ebfa1ce888481e941b428e5aadaf310 rekor-receipt-bundle.sigstore.json
|
||||
81dfe543442831f7bfeec480d5937594590a15b3400ae3567d7d96e62c06ed44 rekor-receipt-policy.sigstore.json
|
||||
96316e53ca5885689870c69719778c2685f191bee844003cb170333fb91579e1 rekor-receipt.schema.sigstore.json
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# Hash index for CLI guides
|
||||
# <sha256> <relative-path>
|
||||
9967d66765f90a31e16d354e43dd6952566d3a359e3250f4f5f9d4b206ba1686 docs/modules/cli/guides/exceptions.md
|
||||
@@ -418,7 +418,7 @@ Additional notes:
|
||||
|
||||
- [Aggregation-Only Contract reference](../../../ingestion/aggregation-only-contract.md)
|
||||
- [Architecture overview](../../platform/architecture-overview.md)
|
||||
- [Console AOC dashboard](../../../ui/console.md)
|
||||
- [Console operator guide](../../../15_UI_GUIDE.md)
|
||||
- [Authority scopes](../../authority/architecture.md)
|
||||
- [Task Pack CLI profiles](./packs-profiles.md)
|
||||
|
||||
|
||||
@@ -1,11 +1,93 @@
|
||||
# CLI Exceptions Guide (stub)
|
||||
# CLI Exceptions Guide
|
||||
|
||||
> Status: BLOCKED — depends on exception API contract and CLI command shapes (DOCS-EXC-25-006). Outline fixed to reduce future churn.
|
||||
The `stella exceptions` command group manages exception governance objects (list/show/create/promote/revoke/import/export). Exceptions are tenant-scoped and intended to be time-bound and auditable.
|
||||
|
||||
## Outline
|
||||
- Imposed rule banner
|
||||
- Commands: list, get, create, approve, reject (actual names TBD)
|
||||
- Flags/exit codes (to be filled when CLI contract arrives)
|
||||
- Examples with deterministic outputs (hash in `docs/modules/cli/guides/SHA256SUMS` when available)
|
||||
- Offline/air-gap usage notes
|
||||
- Troubleshooting and known errors
|
||||
## Common Options
|
||||
|
||||
- `--tenant`, `-t` — tenant scope for the operation
|
||||
- `--json` — output structured JSON (where supported)
|
||||
- `--verbose` — print additional diagnostic context
|
||||
|
||||
## Commands
|
||||
|
||||
### List
|
||||
|
||||
`stella exceptions list`
|
||||
|
||||
Filters:
|
||||
|
||||
- `--vuln <id>` — CVE or alias
|
||||
- `--scope-type <purl|image|component|tenant>`
|
||||
- `--scope-value <value>` — purl string, image ref, component key, etc.
|
||||
- `--status`, `-s <draft|staged|active|expired|revoked>` (repeatable)
|
||||
- `--owner <string>`
|
||||
- `--effect <suppress|defer|downgrade|requireControl>`
|
||||
- `--expiring-within-days <n>`
|
||||
- `--include-expired`
|
||||
- `--page-size <n>` (default: 50)
|
||||
- `--page-token <token>`
|
||||
- `--csv` — output CSV (implies structured output)
|
||||
|
||||
### Show
|
||||
|
||||
`stella exceptions show <exception-id>`
|
||||
|
||||
### Create
|
||||
|
||||
`stella exceptions create --vuln <id> --scope-type <type> --scope-value <value> --effect <effect> --justification <text> --owner <owner>`
|
||||
|
||||
Options:
|
||||
|
||||
- `--expiration <iso8601|+30d|+90d>` — expiration date/time or relative duration
|
||||
- `--evidence <type:uri>` (repeatable) — evidence references
|
||||
- `--policy <policy-id-or-version>` — bind exception to a policy profile/version
|
||||
- `--stage` — create directly as staged (skip draft)
|
||||
|
||||
### Promote
|
||||
|
||||
`stella exceptions promote <exception-id>`
|
||||
|
||||
Options:
|
||||
|
||||
- `--target <staged|active>` — target status (default: next stage)
|
||||
- `--comment <text>` — audit log comment
|
||||
|
||||
### Revoke
|
||||
|
||||
`stella exceptions revoke <exception-id>`
|
||||
|
||||
Options:
|
||||
|
||||
- `--reason <text>` — audit log reason
|
||||
|
||||
### Import
|
||||
|
||||
`stella exceptions import <file>`
|
||||
|
||||
Imports exceptions from an NDJSON file.
|
||||
|
||||
Options:
|
||||
|
||||
- `--stage` (default: `true`) — import as staged
|
||||
- `--source <label>` — source label stored with imported records
|
||||
|
||||
### Export
|
||||
|
||||
`stella exceptions export --output <path>`
|
||||
|
||||
Options:
|
||||
|
||||
- `--status`, `-s <...>` (repeatable) — filter by status
|
||||
- `--format <ndjson|json>` (default: `ndjson`)
|
||||
- `--signed` — request a signed export (DSSE) when Attestor is enabled
|
||||
|
||||
## Offline / Air-Gap Usage
|
||||
|
||||
- `import` and `export` are the primary offline workflows for moving exception sets between environments.
|
||||
- Prefer NDJSON for deterministic diffs and review workflows.
|
||||
- Keep exception data tenant-scoped; cross-tenant bundles should be treated as an explicit, audited workflow.
|
||||
|
||||
## Related Docs
|
||||
|
||||
- Exceptions API entry point: `docs/api/exceptions.md`
|
||||
- Exception governance migration guide: `docs/migration/exception-governance.md`
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
# Excititor OpenAPI Freeze Checklist (chunk API) — unblocker for EXCITITOR-DOCS-0001
|
||||
|
||||
Status: awaiting CI validation + contract freeze. This checklist defines the gate to flip EXCITITOR-DOCS-0001 to DOING/DONE.
|
||||
|
||||
## Freeze criteria
|
||||
- Chunk API CI green on main (tests + contract lint).
|
||||
- OpenAPI spec version pinned (e.g., `api/chunk-api.yaml` with semver tag) and referenced from README/architecture.
|
||||
- Breaking-change log reviewed (added/removed fields, status codes) with ack from Console Guild.
|
||||
- Deterministic samples produced (request/response NDJSON) and hash-listed.
|
||||
- Observability fields (trace IDs, request IDs) documented.
|
||||
|
||||
## Required artefacts
|
||||
- `docs/modules/excititor/api/chunk-api.yaml` (pinned version)
|
||||
- `docs/modules/excititor/api/SHA256SUMS` with hashes for OpenAPI + samples (file stub present)
|
||||
- `docs/modules/excititor/api/samples/*.json` (deterministic examples; directory stub present)
|
||||
|
||||
## Actions when criteria met
|
||||
1) Set EXCITITOR-DOCS-0001 to DOING in sprint and TASKS; pull pinned OpenAPI + samples.
|
||||
2) Update module README/architecture to reference the pinned spec + samples.
|
||||
3) Add hash entries to `docs/modules/excititor/api/SHA256SUMS`.
|
||||
4) Flip EXCITITOR-DOCS-0001 to DONE and unblock ENG/OPS tasks.
|
||||
@@ -3,7 +3,7 @@
|
||||
Excititor converts heterogeneous VEX feeds into raw observations and linksets that honour the Aggregation-Only Contract.
|
||||
|
||||
## Latest updates (2025-12-05)
|
||||
- OpenAPI freeze gate added at `OPENAPI_FREEZE_CHECKLIST.md`; EXCITITOR-DOCS-0001 remains BLOCKED until chunk API CI passes and pinned spec + hashed samples are delivered.
|
||||
- Chunk API documentation remains blocked until CI is green and a pinned OpenAPI spec + deterministic samples are available.
|
||||
- Sprint tracker `docs/implplan/SPRINT_0333_0001_0001_docs_modules_excititor.md` and module `TASKS.md` mirror status.
|
||||
- Observability/runbook assets remain in `operations/observability.md` and `observability/` (timeline, locker manifests); dashboards stay offline-import friendly.
|
||||
- Prior updates (2025-11-05): Link-Not-Merge readiness and consensus beta note (`../../updates/2025-11-05-excitor-consensus-beta.md`), observability guide additions, DSSE packaging guidance, and Policy/CLI follow-ups tracked in SPRINT_200.
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
| EXCITOR-DOCS-0001 | DONE (2025-11-07) | Docs Guild | README aligned to consensus beta release notes. |
|
||||
| EXCITOR-OPS-0001 | DONE (2025-11-07) | Ops Guild | Runbooks/observability checklist added (`mirrors.md`). |
|
||||
| EXCITOR-ENG-0001 | DONE (2025-11-07) | Module Team | Implementation plan alignment with SPRINT_200 updates. |
|
||||
| EXCITITOR-DOCS-0001 | BLOCKED (2025-11-19) | Docs Guild | Await chunk API CI validation + OpenAPI freeze before finalizing docs; gate defined in `OPENAPI_FREEZE_CHECKLIST.md`. |
|
||||
| EXCITITOR-API-STUBS | DONE (2025-12-05) | Docs Guild | Prepared `docs/modules/excititor/api/` stub with `SHA256SUMS` and `samples/` for chunk API freeze; ready to record hashes when spec lands. |
|
||||
| EXCITITOR-DOCS-0001 | BLOCKED (2025-11-19) | Docs Guild | Await chunk API CI validation + pinned OpenAPI + deterministic samples before finalizing docs. |
|
||||
| EXCITITOR-ENG-0001 | BLOCKED (2025-12-03) | Module Team | Blocked by EXCITITOR-DOCS-0001 (chunk API CI/OpenAPI freeze). |
|
||||
| EXCITITOR-OPS-0001 | BLOCKED (2025-12-03) | Ops Guild | Blocked by EXCITITOR-DOCS-0001; update runbooks once OpenAPI freezes. |
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
# Hash index for Excititor chunk API spec/samples (fill once OpenAPI freezes)
|
||||
@@ -452,7 +452,10 @@ spec:
|
||||
## 14) References
|
||||
|
||||
- Router Architecture: `docs/modules/router/architecture.md`
|
||||
- Gateway Identity Header Policy: `docs/modules/gateway/identity-header-policy.md`
|
||||
- OpenAPI Aggregation: `docs/modules/gateway/openapi.md`
|
||||
- Router ASP.NET Endpoint Bridge: `docs/modules/router/aspnet-endpoint-bridge.md`
|
||||
- Router Messaging (Valkey) Transport: `docs/modules/router/messaging-valkey-transport.md`
|
||||
- Authority Integration: `docs/modules/authority/architecture.md`
|
||||
- Reference Architecture: `docs/product-advisories/archived/2025-12-21-reference-architecture/`
|
||||
|
||||
|
||||
103
docs/modules/gateway/identity-header-policy.md
Normal file
103
docs/modules/gateway/identity-header-policy.md
Normal file
@@ -0,0 +1,103 @@
|
||||
# Gateway · Identity Header Policy for Router Dispatch (Draft v0.1)
|
||||
|
||||
## Status
|
||||
- Draft; intended for implementation via a dedicated sprint.
|
||||
- Last updated: 2025-12-23 (UTC).
|
||||
|
||||
## Why This Exists
|
||||
The Gateway is the single HTTP ingress point and routes requests to internal microservices over Router transports. Many services (and legacy components) still rely on **header-based identity context** (tenant/scopes/actor) rather than (or in addition to) `HttpContext.User` claims.
|
||||
|
||||
This creates a hard security requirement:
|
||||
- **Clients must never be able to inject/override “roles/scopes” headers** that the downstream service trusts.
|
||||
- The Gateway must derive the effective identity from the validated JWT/JWK token (or explicit anonymous identity) and **overwrite** downstream identity headers accordingly.
|
||||
|
||||
## Current State (Repo Reality)
|
||||
The Gateway WebService currently contains middleware that propagates claims into request headers:
|
||||
- `src/Gateway/StellaOps.Gateway.WebService/Middleware/ClaimsPropagationMiddleware.cs`
|
||||
- `src/Gateway/StellaOps.Gateway.WebService/Middleware/TenantMiddleware.cs`
|
||||
|
||||
### Known gaps vs intended behavior
|
||||
1) **Spoofing risk:** claim propagation uses “set if missing”, so client-supplied headers can pass through unmodified.
|
||||
2) **Claim type mismatch:** current middleware uses `tid`, but canonical tenant claim type is `stellaops:tenant` (`StellaOpsClaimTypes.Tenant`).
|
||||
3) **Scope claim mismatch:** tokens may use `scp` (individual scope items) and/or `scope` (space-separated). Current middleware reads only `scope`.
|
||||
4) **Docs drift:** `docs/api/gateway/tenant-auth.md` describes `X-Stella-*` headers and scope override behavior that is not implemented in the Gateway WebService.
|
||||
|
||||
## Policy Goals
|
||||
- **No client-controlled identity headers:** the Gateway rejects or strips identity headers coming from external clients.
|
||||
- **Gateway-controlled propagation:** the Gateway sets downstream identity headers based on validated token claims or a defined anonymous identity.
|
||||
- **Compatibility bridge:** support both `X-Stella-*` and `X-StellaOps-*` header naming during migration.
|
||||
- **Determinism:** header values are canonicalized (whitespace, ordering) and do not vary across equivalent requests.
|
||||
|
||||
## Reserved Headers (Draft)
|
||||
The following headers are considered **reserved identity context** and must not be trusted from external clients:
|
||||
- Tenant / project:
|
||||
- `X-StellaOps-Tenant`, `X-Stella-Tenant`
|
||||
- `X-StellaOps-Project`, `X-Stella-Project`
|
||||
- Scopes / roles:
|
||||
- `X-StellaOps-Scopes`, `X-Stella-Scopes`
|
||||
- Actor / subject (if used for auditing):
|
||||
- `X-StellaOps-Actor`, `X-Stella-Actor`
|
||||
- Token proof / confirmation (if propagated):
|
||||
- `cnf`, `cnf.jkt`
|
||||
|
||||
**Internal/legacy pass-through keys to also treat as reserved:**
|
||||
- `sub`, `scope`, `scp`, `tid` (legacy), `stellaops:tenant` (if ever used as a header key)
|
||||
|
||||
## Overwrite Rules (Draft)
|
||||
For non-system paths (i.e., requests that will be routed to microservices):
|
||||
1) **Strip** all reserved identity headers from the incoming request.
|
||||
2) **Compute** effective identity from the authenticated principal:
|
||||
- `sub` from JWT `sub` (`StellaOpsClaimTypes.Subject`)
|
||||
- `tenant` from `stellaops:tenant` (`StellaOpsClaimTypes.Tenant`)
|
||||
- `project` from `stellaops:project` (`StellaOpsClaimTypes.Project`) when present
|
||||
- `scopes` from:
|
||||
- `scp` claims (`StellaOpsClaimTypes.ScopeItem`) if present, else
|
||||
- split `scope` (`StellaOpsClaimTypes.Scope`) by spaces
|
||||
3) **Write** downstream headers (compat mode):
|
||||
- Tenant:
|
||||
- `X-StellaOps-Tenant: <tenant>`
|
||||
- `X-Stella-Tenant: <tenant>` (optional during migration)
|
||||
- Project (optional):
|
||||
- `X-StellaOps-Project: <project>`
|
||||
- `X-Stella-Project: <project>` (optional during migration)
|
||||
- Scopes:
|
||||
- `X-StellaOps-Scopes: <space-delimited scopes>`
|
||||
- `X-Stella-Scopes: <space-delimited scopes>` (optional during migration)
|
||||
- Actor:
|
||||
- `X-StellaOps-Actor: <sub>` (unless another canonical actor claim is defined)
|
||||
|
||||
### Anonymous mode
|
||||
If `Gateway:Auth:AllowAnonymous=true` and the request is unauthenticated:
|
||||
- Set an explicit anonymous identity so downstream services never interpret “missing header” as privileged:
|
||||
- `X-StellaOps-Actor: anonymous`
|
||||
- `X-StellaOps-Scopes: ` (empty) or `anonymous` (choose one and document)
|
||||
- Tenant behavior must be explicitly defined:
|
||||
- either reject routed requests without tenant context, or
|
||||
- require a tenant header even in anonymous mode and treat it as *untrusted input* that only selects a tenancy partition (not privileges).
|
||||
|
||||
## Scope Override Header (Offline/Pre-prod)
|
||||
Some legacy flows allow setting scopes via headers (for offline kits or pre-prod bundles).
|
||||
|
||||
Draft enforcement:
|
||||
- Default: **forbid** client-provided scope headers (`X-StellaOps-Scopes`, `X-Stella-Scopes`) and return 403 (deterministic error code).
|
||||
- Optional controlled override: allow only when `Gateway:Auth:AllowScopeHeader=true`, and only for explicitly allowed environments (offline/pre-prod).
|
||||
- Even when allowed, the override must not silently escalate a request beyond what the token allows unless the request is explicitly unauthenticated and the environment is configured for offline operation.
|
||||
|
||||
## Implementation Notes (Draft)
|
||||
### Suggested refactor
|
||||
Replace the current “set-if-missing” propagation with a single middleware:
|
||||
- `IdentityHeaderPolicyMiddleware`
|
||||
- strips reserved headers
|
||||
- overwrites headers from claims
|
||||
- stores normalized values in `HttpContext.Items` (tenant, actor, scope set) for use by rate limits, routing decisions, and audit logs
|
||||
|
||||
### Tests
|
||||
- Unit tests: spoofed identity headers are removed and overwritten.
|
||||
- Unit tests: claim type mapping uses `StellaOpsClaimTypes.*` correctly.
|
||||
- Integration tests: routed request arrives at microservice with correct identity headers.
|
||||
|
||||
## Related Documents
|
||||
- Gateway architecture: `docs/modules/gateway/architecture.md`
|
||||
- Tenant auth contract (Web V): `docs/api/gateway/tenant-auth.md` (note: currently drifts from implementation)
|
||||
- Router ASP.NET bridge: `docs/modules/router/aspnet-endpoint-bridge.md`
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
# Hash index for graph observability exports
|
||||
@@ -1,12 +0,0 @@
|
||||
# Graph Ops Demo Placeholder (2025-12-05)
|
||||
|
||||
Waiting on next demo outputs to move GRAPH-OPS-0001 from TODO. This file reserves path and determinism checklist.
|
||||
|
||||
## Pending inputs
|
||||
- Latest demo run metrics and dashboards (JSON export)
|
||||
- Runbook updates (alerts, SLOs) based on demo
|
||||
- Hashes of exported dashboards/runbook files
|
||||
|
||||
## Determinism checklist
|
||||
- Store dashboard export JSON under `docs/modules/graph/observability/` with SHA256 in `docs/modules/graph/observability/SHA256SUMS`.
|
||||
- Document any new runbook steps with UTC timestamps and stable ordering.
|
||||
@@ -56,6 +56,7 @@ The Router replaces the Serdica HTTP-to-RabbitMQ pattern with a simpler, generic
|
||||
| TLS Transport | `StellaOps.Router.Transport.Tls` | Encrypted TCP transport |
|
||||
| UDP Transport | `StellaOps.Router.Transport.Udp` | Small payload transport |
|
||||
| RabbitMQ Transport | `StellaOps.Router.Transport.RabbitMQ` | Message queue transport |
|
||||
| Messaging Transport | `StellaOps.Router.Transport.Messaging` | Messaging/RPC transport (Valkey-backed via `StellaOps.Messaging.Transport.Valkey`) |
|
||||
|
||||
## Solution Structure
|
||||
|
||||
@@ -86,6 +87,8 @@ StellaOps.Router.slnx
|
||||
| [openapi-aggregation.md](openapi-aggregation.md) | OpenAPI document generation |
|
||||
| [migration-guide.md](migration-guide.md) | WebService to Microservice migration |
|
||||
| [rate-limiting.md](rate-limiting.md) | Centralized router rate limiting |
|
||||
| [aspnet-endpoint-bridge.md](aspnet-endpoint-bridge.md) | Using ASP.NET endpoint registration as Router endpoint registration |
|
||||
| [messaging-valkey-transport.md](messaging-valkey-transport.md) | Messaging transport over Valkey |
|
||||
|
||||
## Quick Start
|
||||
|
||||
|
||||
857
docs/modules/router/aspnet-endpoint-bridge.md
Normal file
857
docs/modules/router/aspnet-endpoint-bridge.md
Normal file
@@ -0,0 +1,857 @@
|
||||
# Router · ASP.NET Endpoint Bridge
|
||||
|
||||
## Status
|
||||
|
||||
- **Version:** 0.2 (revised)
|
||||
- **Last updated:** 2025-12-24 (UTC)
|
||||
- **Implementation sprint:** `SPRINT_8100_0011_0001`
|
||||
|
||||
---
|
||||
|
||||
## Problem Statement
|
||||
|
||||
StellaOps Router routes external HTTP requests (via Gateway) to internal microservices over binary transports (TCP/TLS/Messaging). Most StellaOps microservices are ASP.NET Core WebServices that register routes via standard ASP.NET endpoint registration (controllers or minimal APIs).
|
||||
|
||||
**Current behavior:** `StellaOps.Microservice` discovers and registers only Router-native endpoints implemented via `[StellaEndpoint]` handlers. If a service has only ASP.NET endpoints, it either:
|
||||
- Registers **zero** Router endpoints (Gateway cannot route to it), or
|
||||
- Duplicates routes as `[StellaEndpoint]` handlers (route drift + duplicated security metadata).
|
||||
|
||||
**Desired behavior:** Treat ASP.NET endpoint registration as the **single source of truth** and automatically bridge it to Router endpoint registration.
|
||||
|
||||
---
|
||||
|
||||
## Design Goals
|
||||
|
||||
| Goal | Description |
|
||||
|------|-------------|
|
||||
| **Single source of truth** | ASP.NET endpoint registration defines what the service exposes |
|
||||
| **Full ASP.NET fidelity** | Authorization, filters, model binding, and all ASP.NET features work correctly |
|
||||
| **Determinism** | Endpoint discovery produces stable ordering and normalized paths |
|
||||
| **Security alignment** | Authorization metadata flows to Router `RequiringClaims` |
|
||||
| **Opt-in integration** | Services explicitly enable the bridge via `Program.cs` |
|
||||
| **Offline-first** | No runtime network dependency for discovery/dispatch |
|
||||
|
||||
---
|
||||
|
||||
## Non-Goals (v0.1)
|
||||
|
||||
| Feature | Reason |
|
||||
|---------|--------|
|
||||
| SignalR/WebSocket support | Different protocol semantics |
|
||||
| gRPC endpoint bridging | Different protocol |
|
||||
| Streaming request bodies | Router SDK buffering limitation |
|
||||
| Custom route constraints | Complexity; document as limitation |
|
||||
| API versioning (header/query) | Complexity; use path-based versioning |
|
||||
| Automatic schema generation | Depends on source generators; future enhancement |
|
||||
|
||||
---
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ ASP.NET WebService │
|
||||
│ ┌───────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Program.cs │ │
|
||||
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
|
||||
│ │ │ builder.Services.AddStellaRouterBridge(options => { │ │ │
|
||||
│ │ │ options.ServiceName = "scanner"; │ │ │
|
||||
│ │ │ options.Version = "1.0.0"; │ │ │
|
||||
│ │ │ }); │ │ │
|
||||
│ │ │ app.UseStellaRouterBridge(); │ │ │
|
||||
│ │ └─────────────────────────────────────────────────────────┘ │ │
|
||||
│ └───────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌───────────────────────────────────────────────────────────────┐ │
|
||||
│ │ StellaOps.Microservice.AspNetCore │ │
|
||||
│ │ ┌─────────────────────┐ ┌─────────────────────────────────┐ │ │
|
||||
│ │ │ Discovery Provider │ │ Request Dispatcher │ │ │
|
||||
│ │ │ ─────────────────── │ │ ─────────────────────────────── │ │ │
|
||||
│ │ │ • EndpointDataSource│ │ • RequestFrame → HttpContext │ │ │
|
||||
│ │ │ • Metadata extraction│ │ • ASP.NET pipeline execution │ │ │
|
||||
│ │ │ • Route normalization│ │ • ResponseFrame capture │ │ │
|
||||
│ │ │ • Auth claim mapping │ │ • DI scope management │ │ │
|
||||
│ │ └──────────┬──────────┘ └──────────────┬──────────────────┘ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ ▼ ▼ │ │
|
||||
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
|
||||
│ │ │ Router SDK (StellaOps.Microservice) │ │ │
|
||||
│ │ │ • HELLO registration with discovered endpoints │ │ │
|
||||
│ │ │ • Request routing to dispatcher │ │ │
|
||||
│ │ └─────────────────────────────────────────────────────────┘ │ │
|
||||
│ └───────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌───────────────────────────────────────────────────────────────┐ │
|
||||
│ │ ASP.NET Endpoints (unchanged) │ │
|
||||
│ │ • Minimal APIs: app.MapGet("/api/...", handler) │ │
|
||||
│ │ • Controllers: [ApiController] with [HttpGet], etc. │ │
|
||||
│ │ • Route groups: app.MapGroup("/api").MapScannerEndpoints() │ │
|
||||
│ └───────────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
│ Router Transport (TCP/TLS/Messaging)
|
||||
▼
|
||||
┌───────────────────────────────┐
|
||||
│ StellaOps.Gateway.WebService │
|
||||
│ • HELLO processing │
|
||||
│ • Endpoint routing │
|
||||
│ • OpenAPI aggregation │
|
||||
└───────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Component Design
|
||||
|
||||
### 1. StellaRouterBridgeOptions
|
||||
|
||||
Configuration for the bridge, specified in `Program.cs`:
|
||||
|
||||
```csharp
|
||||
public sealed class StellaRouterBridgeOptions
|
||||
{
|
||||
// === Required: Service Identity ===
|
||||
public required string ServiceName { get; set; }
|
||||
public required string Version { get; set; }
|
||||
public required string Region { get; set; }
|
||||
public string? InstanceId { get; set; } // Auto-generated if null
|
||||
|
||||
// === Authorization Mapping ===
|
||||
public AuthorizationMappingStrategy AuthorizationMapping { get; set; }
|
||||
= AuthorizationMappingStrategy.Hybrid;
|
||||
public MissingAuthorizationBehavior OnMissingAuthorization { get; set; }
|
||||
= MissingAuthorizationBehavior.RequireExplicit;
|
||||
|
||||
// === YAML Overrides ===
|
||||
public string? YamlConfigPath { get; set; }
|
||||
|
||||
// === Metadata Extraction ===
|
||||
public bool ExtractSchemas { get; set; } = true;
|
||||
public bool ExtractOpenApiMetadata { get; set; } = true;
|
||||
|
||||
// === Route Handling ===
|
||||
public UnsupportedConstraintBehavior OnUnsupportedConstraint { get; set; }
|
||||
= UnsupportedConstraintBehavior.WarnAndStrip;
|
||||
public Func<RouteEndpoint, bool>? EndpointFilter { get; set; }
|
||||
|
||||
// === Defaults ===
|
||||
public TimeSpan DefaultTimeout { get; set; } = TimeSpan.FromSeconds(30);
|
||||
}
|
||||
|
||||
public enum AuthorizationMappingStrategy
|
||||
{
|
||||
YamlOnly, // Only use YAML overrides
|
||||
AspNetMetadataOnly, // Only use ASP.NET metadata
|
||||
Hybrid // ASP.NET + YAML (YAML wins on conflict)
|
||||
}
|
||||
|
||||
public enum MissingAuthorizationBehavior
|
||||
{
|
||||
RequireExplicit, // Fail if no auth metadata
|
||||
AllowAuthenticated, // Allow with empty claims (authenticated required)
|
||||
WarnAndAllow // Log warning, allow with empty claims
|
||||
}
|
||||
|
||||
public enum UnsupportedConstraintBehavior
|
||||
{
|
||||
Fail, // Fail discovery on unsupported constraint
|
||||
WarnAndStrip, // Log warning, strip constraint
|
||||
SilentStrip // Strip without warning
|
||||
}
|
||||
```
|
||||
|
||||
### 2. AspNetEndpointDescriptor
|
||||
|
||||
Extended endpoint descriptor with full ASP.NET metadata:
|
||||
|
||||
```csharp
|
||||
public sealed record AspNetEndpointDescriptor
|
||||
{
|
||||
// === Core Identity ===
|
||||
public required string ServiceName { get; init; }
|
||||
public required string Version { get; init; }
|
||||
public required string Method { get; init; }
|
||||
public required string Path { get; init; }
|
||||
public TimeSpan DefaultTimeout { get; init; } = TimeSpan.FromSeconds(30);
|
||||
public bool SupportsStreaming { get; init; }
|
||||
|
||||
// === Authorization ===
|
||||
public IReadOnlyList<ClaimRequirement> RequiringClaims { get; init; } = [];
|
||||
public IReadOnlyList<string> AuthorizationPolicies { get; init; } = [];
|
||||
public IReadOnlyList<string> Roles { get; init; } = [];
|
||||
public bool AllowAnonymous { get; init; }
|
||||
public AuthorizationSource AuthorizationSource { get; init; }
|
||||
|
||||
// === Parameters ===
|
||||
public IReadOnlyList<ParameterDescriptor> Parameters { get; init; } = [];
|
||||
|
||||
// === Responses ===
|
||||
public IReadOnlyList<ResponseDescriptor> Responses { get; init; } = [];
|
||||
|
||||
// === OpenAPI ===
|
||||
public string? OperationId { get; init; }
|
||||
public string? Summary { get; init; }
|
||||
public string? Description { get; init; }
|
||||
public IReadOnlyList<string> Tags { get; init; } = [];
|
||||
|
||||
// === Schema ===
|
||||
public EndpointSchemaInfo? SchemaInfo { get; init; }
|
||||
|
||||
// === Internal ===
|
||||
internal RouteEndpoint? OriginalEndpoint { get; init; }
|
||||
internal string? OriginalRoutePattern { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Convert to standard EndpointDescriptor for HELLO payload.
|
||||
/// </summary>
|
||||
public EndpointDescriptor ToEndpointDescriptor() => new()
|
||||
{
|
||||
ServiceName = ServiceName,
|
||||
Version = Version,
|
||||
Method = Method,
|
||||
Path = Path,
|
||||
DefaultTimeout = DefaultTimeout,
|
||||
SupportsStreaming = SupportsStreaming,
|
||||
RequiringClaims = RequiringClaims,
|
||||
SchemaInfo = SchemaInfo
|
||||
};
|
||||
}
|
||||
|
||||
public sealed record ParameterDescriptor
|
||||
{
|
||||
public required string Name { get; init; }
|
||||
public required ParameterSource Source { get; init; }
|
||||
public required Type Type { get; init; }
|
||||
public bool IsRequired { get; init; } = true;
|
||||
public object? DefaultValue { get; init; }
|
||||
public string? Description { get; init; }
|
||||
}
|
||||
|
||||
public enum ParameterSource { Route, Query, Header, Body, Services }
|
||||
|
||||
public sealed record ResponseDescriptor
|
||||
{
|
||||
public required int StatusCode { get; init; }
|
||||
public Type? ResponseType { get; init; }
|
||||
public string? Description { get; init; }
|
||||
public string? ContentType { get; init; } = "application/json";
|
||||
}
|
||||
|
||||
public enum AuthorizationSource { None, AspNetMetadata, YamlOverride, Hybrid }
|
||||
```
|
||||
|
||||
### 3. IAspNetEndpointDiscoveryProvider
|
||||
|
||||
Discovery provider interface:
|
||||
|
||||
```csharp
|
||||
public interface IAspNetEndpointDiscoveryProvider : IEndpointDiscoveryProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Discover ASP.NET endpoints with full metadata.
|
||||
/// </summary>
|
||||
IReadOnlyList<AspNetEndpointDescriptor> DiscoverAspNetEndpoints();
|
||||
}
|
||||
```
|
||||
|
||||
### 4. IAuthorizationClaimMapper
|
||||
|
||||
Authorization-to-claims mapping interface:
|
||||
|
||||
```csharp
|
||||
public interface IAuthorizationClaimMapper
|
||||
{
|
||||
/// <summary>
|
||||
/// Map ASP.NET authorization metadata to Router claim requirements.
|
||||
/// </summary>
|
||||
Task<AuthorizationMappingResult> MapAsync(
|
||||
RouteEndpoint endpoint,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public sealed record AuthorizationMappingResult
|
||||
{
|
||||
public IReadOnlyList<ClaimRequirement> Claims { get; init; } = [];
|
||||
public IReadOnlyList<string> Policies { get; init; } = [];
|
||||
public IReadOnlyList<string> Roles { get; init; } = [];
|
||||
public bool AllowAnonymous { get; init; }
|
||||
public AuthorizationSource Source { get; init; }
|
||||
}
|
||||
```
|
||||
|
||||
### 5. IAspNetRouterRequestDispatcher
|
||||
|
||||
Request dispatch interface:
|
||||
|
||||
```csharp
|
||||
public interface IAspNetRouterRequestDispatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// Dispatch a Router request frame through ASP.NET pipeline.
|
||||
/// </summary>
|
||||
Task<ResponseFrame> DispatchAsync(
|
||||
RequestFrame request,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Endpoint Discovery Algorithm
|
||||
|
||||
### Step 1: Enumerate Endpoints
|
||||
|
||||
```csharp
|
||||
var endpoints = endpointDataSource.Endpoints
|
||||
.OfType<RouteEndpoint>()
|
||||
.Where(e => e.Metadata.GetMetadata<HttpMethodMetadata>() is not null)
|
||||
.Where(options.EndpointFilter ?? (_ => true));
|
||||
```
|
||||
|
||||
### Step 2: Extract Metadata per Endpoint
|
||||
|
||||
For each `RouteEndpoint`:
|
||||
|
||||
1. **HTTP Method**: From `HttpMethodMetadata`
|
||||
2. **Path**: Normalize route pattern (see below)
|
||||
3. **Authorization**: From `IAuthorizeData`, `IAllowAnonymous`
|
||||
4. **Parameters**: From route pattern + parameter binding metadata
|
||||
5. **Responses**: From `IProducesResponseTypeMetadata`
|
||||
6. **OpenAPI**: From `IEndpointNameMetadata`, `IEndpointSummaryMetadata`, `ITagsMetadata`
|
||||
|
||||
### Step 3: Normalize Route Pattern
|
||||
|
||||
```csharp
|
||||
public static string NormalizeRoutePattern(RoutePattern pattern)
|
||||
{
|
||||
var raw = pattern.RawText ?? BuildFromSegments(pattern);
|
||||
|
||||
// 1. Ensure leading slash
|
||||
if (!raw.StartsWith('/'))
|
||||
raw = "/" + raw;
|
||||
|
||||
// 2. Strip constraints: {id:int} → {id}
|
||||
raw = Regex.Replace(raw, @"\{(\*?)([A-Za-z0-9_]+)(:[^}]+)?\}", "{$2}");
|
||||
|
||||
// 3. Normalize catch-all: {**path} → {path}
|
||||
raw = raw.Replace("**", "", StringComparison.Ordinal);
|
||||
|
||||
// 4. Remove trailing slash
|
||||
raw = raw.TrimEnd('/');
|
||||
|
||||
// 5. Empty path becomes "/"
|
||||
return string.IsNullOrEmpty(raw) ? "/" : raw;
|
||||
}
|
||||
```
|
||||
|
||||
### Step 4: Deterministic Ordering
|
||||
|
||||
Sort endpoints for stable HELLO payloads:
|
||||
|
||||
```csharp
|
||||
var ordered = endpoints
|
||||
.OrderBy(e => e.Path, StringComparer.OrdinalIgnoreCase)
|
||||
.ThenBy(e => GetMethodOrder(e.Method))
|
||||
.ThenBy(e => e.OriginalEndpoint?.DisplayName ?? "");
|
||||
|
||||
static int GetMethodOrder(string method) => method.ToUpperInvariant() switch
|
||||
{
|
||||
"GET" => 0,
|
||||
"POST" => 1,
|
||||
"PUT" => 2,
|
||||
"PATCH" => 3,
|
||||
"DELETE" => 4,
|
||||
"OPTIONS" => 5,
|
||||
"HEAD" => 6,
|
||||
_ => 7
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Authorization Mapping
|
||||
|
||||
### Mapping Rules
|
||||
|
||||
| ASP.NET Metadata | Router Mapping |
|
||||
|------------------|----------------|
|
||||
| `[Authorize]` (no args) | Empty `RequiringClaims` (authenticated required) |
|
||||
| `[Authorize(Policy = "X")]` | Resolve policy → claims via `IAuthorizationPolicyProvider` |
|
||||
| `[Authorize(Roles = "A,B")]` | `ClaimRequirement(ClaimTypes.Role, "A")`, `ClaimRequirement(ClaimTypes.Role, "B")` |
|
||||
| `[AllowAnonymous]` | `AllowAnonymous = true`, empty claims |
|
||||
| `.RequireAuthorization("Policy")` | Same as `[Authorize(Policy)]` |
|
||||
|
||||
### Policy Resolution
|
||||
|
||||
```csharp
|
||||
public async Task<IReadOnlyList<ClaimRequirement>> ResolvePolicyAsync(
|
||||
string policyName,
|
||||
IAuthorizationPolicyProvider policyProvider)
|
||||
{
|
||||
var policy = await policyProvider.GetPolicyAsync(policyName);
|
||||
if (policy is null)
|
||||
return [];
|
||||
|
||||
var claims = new List<ClaimRequirement>();
|
||||
|
||||
foreach (var requirement in policy.Requirements)
|
||||
{
|
||||
switch (requirement)
|
||||
{
|
||||
case ClaimsAuthorizationRequirement claimsReq:
|
||||
foreach (var value in claimsReq.AllowedValues ?? [null])
|
||||
{
|
||||
claims.Add(new ClaimRequirement
|
||||
{
|
||||
Type = claimsReq.ClaimType,
|
||||
Value = value
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case RolesAuthorizationRequirement rolesReq:
|
||||
foreach (var role in rolesReq.AllowedRoles)
|
||||
{
|
||||
claims.Add(new ClaimRequirement
|
||||
{
|
||||
Type = ClaimTypes.Role,
|
||||
Value = role
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
// Other requirement types: log warning, continue
|
||||
}
|
||||
}
|
||||
|
||||
return claims;
|
||||
}
|
||||
```
|
||||
|
||||
### YAML Override Merge
|
||||
|
||||
When `AuthorizationMappingStrategy.Hybrid`:
|
||||
|
||||
```csharp
|
||||
public IReadOnlyList<ClaimRequirement> MergeWithYaml(
|
||||
IReadOnlyList<ClaimRequirement> aspNetClaims,
|
||||
EndpointOverrideConfig? yamlOverride)
|
||||
{
|
||||
if (yamlOverride?.RequiringClaims is not { Count: > 0 } yamlClaims)
|
||||
return aspNetClaims;
|
||||
|
||||
// YAML completely replaces ASP.NET claims when specified
|
||||
return yamlClaims;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Request Dispatch
|
||||
|
||||
### Dispatch Flow
|
||||
|
||||
```
|
||||
RequestFrame (from Router)
|
||||
│
|
||||
▼
|
||||
┌───────────────────────────────────────┐
|
||||
│ 1. Create DI Scope │
|
||||
│ var scope = CreateAsyncScope() │
|
||||
└───────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────────────────────────────┐
|
||||
│ 2. Build HttpContext │
|
||||
│ • Method, Path, QueryString │
|
||||
│ • Headers (including identity) │
|
||||
│ • Body stream │
|
||||
│ • RequestServices = scope.Provider │
|
||||
│ • CancellationToken wiring │
|
||||
└───────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────────────────────────────┐
|
||||
│ 3. Match Endpoint │
|
||||
│ Use ASP.NET EndpointSelector │
|
||||
│ Preserves constraints/precedence │
|
||||
└───────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────────────────────────────┐
|
||||
│ 4. Populate Identity │
|
||||
│ Map X-StellaOps-* headers to │
|
||||
│ ClaimsPrincipal on HttpContext │
|
||||
└───────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────────────────────────────┐
|
||||
│ 5. Execute RequestDelegate │
|
||||
│ Runs full ASP.NET pipeline: │
|
||||
│ • Endpoint filters │
|
||||
│ • Authorization filters │
|
||||
│ • Model binding │
|
||||
│ • Handler execution │
|
||||
└───────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────────────────────────────┐
|
||||
│ 6. Capture Response │
|
||||
│ • Status code │
|
||||
│ • Headers (filtered) │
|
||||
│ • Body bytes (buffered) │
|
||||
└───────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────────────────────────────┐
|
||||
│ 7. Dispose Scope │
|
||||
│ await scope.DisposeAsync() │
|
||||
└───────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
ResponseFrame (to Router)
|
||||
```
|
||||
|
||||
### HttpContext Construction
|
||||
|
||||
```csharp
|
||||
public async Task<ResponseFrame> DispatchAsync(
|
||||
RequestFrame request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
await using var scope = _serviceProvider.CreateAsyncScope();
|
||||
|
||||
var httpContext = new DefaultHttpContext
|
||||
{
|
||||
RequestServices = scope.ServiceProvider
|
||||
};
|
||||
|
||||
// Link cancellation
|
||||
var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(
|
||||
cancellationToken,
|
||||
httpContext.RequestAborted);
|
||||
httpContext.RequestAborted = linkedCts.Token;
|
||||
|
||||
// Populate request
|
||||
var httpRequest = httpContext.Request;
|
||||
httpRequest.Method = request.Method;
|
||||
(httpRequest.Path, httpRequest.QueryString) = ParsePathAndQuery(request.Path);
|
||||
|
||||
foreach (var (key, value) in request.Headers)
|
||||
{
|
||||
httpRequest.Headers[key] = value;
|
||||
}
|
||||
|
||||
if (request.Body is { Length: > 0 })
|
||||
{
|
||||
httpRequest.Body = new MemoryStream(request.Body);
|
||||
httpRequest.ContentLength = request.Body.Length;
|
||||
}
|
||||
|
||||
// Set trace identifier
|
||||
httpContext.TraceIdentifier = request.CorrelationId;
|
||||
|
||||
// Populate identity from headers
|
||||
PopulateIdentity(httpContext, request.Headers);
|
||||
|
||||
// Match and execute endpoint
|
||||
var endpoint = await MatchEndpointAsync(httpContext);
|
||||
if (endpoint is null)
|
||||
{
|
||||
return CreateNotFoundResponse(request.CorrelationId);
|
||||
}
|
||||
|
||||
httpContext.SetEndpoint(endpoint);
|
||||
|
||||
// Capture response
|
||||
var responseBody = new MemoryStream();
|
||||
httpContext.Response.Body = responseBody;
|
||||
|
||||
try
|
||||
{
|
||||
await endpoint.RequestDelegate!(httpContext);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return CreateErrorResponse(request.CorrelationId, ex);
|
||||
}
|
||||
|
||||
// Build response frame
|
||||
return new ResponseFrame
|
||||
{
|
||||
CorrelationId = request.CorrelationId,
|
||||
StatusCode = httpContext.Response.StatusCode,
|
||||
Headers = CaptureResponseHeaders(httpContext.Response),
|
||||
Body = responseBody.ToArray()
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Identity Population
|
||||
|
||||
Map Gateway-provided identity headers to `ClaimsPrincipal`:
|
||||
|
||||
```csharp
|
||||
private void PopulateIdentity(HttpContext httpContext, IReadOnlyDictionary<string, string> headers)
|
||||
{
|
||||
var claims = new List<Claim>();
|
||||
|
||||
if (headers.TryGetValue("X-StellaOps-Actor", out var actor) && !string.IsNullOrEmpty(actor))
|
||||
{
|
||||
claims.Add(new Claim(ClaimTypes.NameIdentifier, actor));
|
||||
claims.Add(new Claim(StellaOpsClaimTypes.Subject, actor));
|
||||
}
|
||||
|
||||
if (headers.TryGetValue("X-StellaOps-Tenant", out var tenant) && !string.IsNullOrEmpty(tenant))
|
||||
{
|
||||
claims.Add(new Claim(StellaOpsClaimTypes.Tenant, tenant));
|
||||
}
|
||||
|
||||
if (headers.TryGetValue("X-StellaOps-Scopes", out var scopes) && !string.IsNullOrEmpty(scopes))
|
||||
{
|
||||
foreach (var scope in scopes.Split(' ', StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
claims.Add(new Claim(StellaOpsClaimTypes.ScopeItem, scope));
|
||||
}
|
||||
}
|
||||
|
||||
if (claims.Count > 0)
|
||||
{
|
||||
var identity = new ClaimsIdentity(claims, "StellaRouter");
|
||||
httpContext.User = new ClaimsPrincipal(identity);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Program.cs Integration
|
||||
|
||||
### Service Registration
|
||||
|
||||
```csharp
|
||||
public static class StellaRouterBridgeExtensions
|
||||
{
|
||||
public static IServiceCollection AddStellaRouterBridge(
|
||||
this IServiceCollection services,
|
||||
Action<StellaRouterBridgeOptions> configure)
|
||||
{
|
||||
var options = new StellaRouterBridgeOptions
|
||||
{
|
||||
ServiceName = "",
|
||||
Version = "",
|
||||
Region = ""
|
||||
};
|
||||
configure(options);
|
||||
ValidateOptions(options);
|
||||
|
||||
services.AddSingleton(options);
|
||||
services.AddSingleton<IAuthorizationClaimMapper, DefaultAuthorizationClaimMapper>();
|
||||
services.AddSingleton<IAspNetEndpointDiscoveryProvider, AspNetCoreEndpointDiscoveryProvider>();
|
||||
services.AddSingleton<IAspNetRouterRequestDispatcher, AspNetRouterRequestDispatcher>();
|
||||
|
||||
// Register as IEndpointDiscoveryProvider for Router SDK integration
|
||||
services.AddSingleton<IEndpointDiscoveryProvider>(sp =>
|
||||
sp.GetRequiredService<IAspNetEndpointDiscoveryProvider>());
|
||||
|
||||
// Wire into Router SDK
|
||||
services.AddStellaMicroservice(microserviceOptions =>
|
||||
{
|
||||
microserviceOptions.ServiceName = options.ServiceName;
|
||||
microserviceOptions.Version = options.Version;
|
||||
microserviceOptions.Region = options.Region;
|
||||
microserviceOptions.InstanceId = options.InstanceId ?? Guid.NewGuid().ToString();
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IApplicationBuilder UseStellaRouterBridge(this IApplicationBuilder app)
|
||||
{
|
||||
// Ensure EndpointDataSource is available (after UseRouting)
|
||||
var endpointDataSource = app.ApplicationServices
|
||||
.GetService<EndpointDataSource>()
|
||||
?? throw new InvalidOperationException(
|
||||
"UseStellaRouterBridge must be called after UseRouting()");
|
||||
|
||||
// Discovery happens on first Router HELLO
|
||||
// Dispatch is handled by Router SDK
|
||||
|
||||
return app;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## YAML Override Format
|
||||
|
||||
The existing `microservice.yaml` format is extended:
|
||||
|
||||
```yaml
|
||||
microservice:
|
||||
serviceName: scanner
|
||||
version: "1.0.0"
|
||||
region: "${REGION:default}"
|
||||
|
||||
endpoints:
|
||||
# Override by method + path
|
||||
- method: POST
|
||||
path: /api/reports
|
||||
timeoutSeconds: 60
|
||||
supportsStreaming: false
|
||||
requiringClaims:
|
||||
- type: "scanner.reports.read"
|
||||
# Replaces any ASP.NET-derived claims for this endpoint
|
||||
|
||||
# Endpoint with no authorization (explicitly allow authenticated)
|
||||
- method: GET
|
||||
path: /api/health
|
||||
requiringClaims: [] # Empty = authenticated only, no specific claims
|
||||
|
||||
# Override specific claim type mapping
|
||||
- method: DELETE
|
||||
path: /api/scans/{id}
|
||||
requiringClaims:
|
||||
- type: "role"
|
||||
value: "scanner-admin"
|
||||
- type: "scanner.scans.delete"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ASP.NET Feature Support Matrix
|
||||
|
||||
### Fully Supported
|
||||
|
||||
| Feature | Discovery | Dispatch | Notes |
|
||||
|---------|-----------|----------|-------|
|
||||
| Minimal APIs (`MapGet`, etc.) | ✓ | ✓ | Primary use case |
|
||||
| Controllers (`[ApiController]`) | ✓ | ✓ | Full support |
|
||||
| Route groups (`MapGroup`) | ✓ | ✓ | Path composition |
|
||||
| `[Authorize]` attribute | ✓ | ✓ | Claims extraction |
|
||||
| `[AllowAnonymous]` | ✓ | ✓ | Explicit anonymous |
|
||||
| `.RequireAuthorization()` | ✓ | ✓ | Policy resolution |
|
||||
| `[FromBody]` binding | ✓ (type) | ✓ | JSON deserialization |
|
||||
| `[FromRoute]` binding | ✓ | ✓ | Path parameters |
|
||||
| `[FromQuery]` binding | ✓ | ✓ | Query parameters |
|
||||
| `[FromHeader]` binding | ✓ | ✓ | Header values |
|
||||
| `[FromServices]` injection | N/A | ✓ | DI resolution |
|
||||
| `.Produces<T>()` | ✓ | N/A | Schema metadata |
|
||||
| `.WithName()` / `.WithSummary()` | ✓ | N/A | OpenAPI metadata |
|
||||
| `.WithTags()` | ✓ | N/A | Grouping |
|
||||
| Endpoint filters | N/A | ✓ | Filter pipeline |
|
||||
| `CancellationToken` | N/A | ✓ | From Router frame |
|
||||
| Route constraints (`{id:int}`) | ✓ (stripped) | ✓ | ASP.NET matcher |
|
||||
| Catch-all routes (`{**path}`) | ✓ | ✓ | Normalized |
|
||||
|
||||
### Not Supported (v0.1)
|
||||
|
||||
| Feature | Reason | Workaround |
|
||||
|---------|--------|------------|
|
||||
| SignalR hubs | Different protocol | Use native ASP.NET |
|
||||
| gRPC services | Different protocol | Use native gRPC |
|
||||
| Streaming request bodies | SDK limitation | Use `IRawStellaEndpoint` |
|
||||
| Custom constraints | Complexity | Use standard constraints |
|
||||
| API versioning (header/query) | Complexity | Path-based versioning |
|
||||
| `IFormFile` uploads | Not buffered | Use raw endpoint |
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Discovery Errors
|
||||
|
||||
| Condition | Behavior | Configuration |
|
||||
|-----------|----------|---------------|
|
||||
| No authorization metadata | Fail discovery | `OnMissingAuthorization = RequireExplicit` |
|
||||
| Unsupported constraint | Log warning, strip | `OnUnsupportedConstraint = WarnAndStrip` |
|
||||
| Duplicate endpoints | Log warning, keep first | Always |
|
||||
| Invalid route pattern | Skip endpoint, log error | Always |
|
||||
|
||||
### Dispatch Errors
|
||||
|
||||
| Condition | Response |
|
||||
|-----------|----------|
|
||||
| No matching endpoint | 404 Not Found |
|
||||
| Authorization failure | 403 Forbidden |
|
||||
| Model binding failure | 400 Bad Request |
|
||||
| Handler exception | 500 Internal Server Error |
|
||||
| Cancellation | No response (connection closed) |
|
||||
|
||||
---
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests
|
||||
|
||||
1. **Discovery determinism**: Same endpoints → same descriptor order
|
||||
2. **Route normalization**: Constraints stripped, paths normalized
|
||||
3. **Authorization mapping**: Policies → claims correctly
|
||||
4. **Metadata extraction**: All ASP.NET metadata captured
|
||||
|
||||
### Integration Tests
|
||||
|
||||
1. **Minimal API dispatch**: Route parameters, query, body binding
|
||||
2. **Controller dispatch**: Attribute routing, model binding
|
||||
3. **Authorization flow**: Claims checked, 403 on failure
|
||||
4. **Filter execution**: Endpoint filters run correctly
|
||||
5. **Error mapping**: Exceptions → correct status codes
|
||||
|
||||
### End-to-End Tests
|
||||
|
||||
1. **HELLO registration**: Bridge endpoints appear in Gateway
|
||||
2. **Gateway routing**: HTTP request → Router → ASP.NET → response
|
||||
3. **OpenAPI aggregation**: Bridged endpoints in Gateway OpenAPI
|
||||
|
||||
---
|
||||
|
||||
## Migration Guide
|
||||
|
||||
### From HTTP-Only Service
|
||||
|
||||
```csharp
|
||||
// Before: HTTP only
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
builder.Services.AddControllers();
|
||||
|
||||
var app = builder.Build();
|
||||
app.MapControllers();
|
||||
await app.RunAsync();
|
||||
|
||||
// After: HTTP + Router bridge
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
builder.Services.AddControllers();
|
||||
builder.Services.AddStellaRouterBridge(options =>
|
||||
{
|
||||
options.ServiceName = "myservice";
|
||||
options.Version = "1.0.0";
|
||||
options.Region = "default";
|
||||
});
|
||||
builder.Services.AddMessagingTransportClient(); // Add transport
|
||||
|
||||
var app = builder.Build();
|
||||
app.UseRouting();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
app.UseStellaRouterBridge(); // Enable bridge
|
||||
app.MapControllers();
|
||||
await app.RunAsync();
|
||||
```
|
||||
|
||||
### From Dual Registration (HTTP + `[StellaEndpoint]`)
|
||||
|
||||
1. Remove `[StellaEndpoint]` handler classes
|
||||
2. Add `AddStellaRouterBridge()` configuration
|
||||
3. Add `UseStellaRouterBridge()` middleware
|
||||
4. Add/update `microservice.yaml` for claim overrides
|
||||
5. Remove duplicate endpoint registrations
|
||||
|
||||
---
|
||||
|
||||
## Related Documents
|
||||
|
||||
- Router architecture: `docs/modules/router/architecture.md`
|
||||
- Migration guide: `docs/modules/router/migration-guide.md`
|
||||
- Gateway identity policy: `docs/modules/gateway/identity-header-policy.md`
|
||||
- Implementation sprint: `docs/implplan/SPRINT_8100_0011_0001_router_sdk_aspnet_bridge.md`
|
||||
75
docs/modules/router/messaging-valkey-transport.md
Normal file
75
docs/modules/router/messaging-valkey-transport.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# Router · Messaging Transport over Valkey (Draft v0.1)
|
||||
|
||||
## Status
|
||||
- Draft; intended for implementation via a dedicated sprint.
|
||||
- Last updated: 2025-12-23 (UTC).
|
||||
|
||||
## Purpose
|
||||
Enable Gateway ↔ microservice Router traffic over an offline-friendly, Redis-compatible transport (Valkey) by using the existing **Messaging** transport layer:
|
||||
- Router transport: `StellaOps.Router.Transport.Messaging`
|
||||
- Messaging backend: `StellaOps.Messaging.Transport.Valkey`
|
||||
|
||||
This supports environments where direct TCP/TLS microservice connections are undesirable, and where an internal message bus is the preferred control plane.
|
||||
|
||||
## What Exists Today (Repository Reality)
|
||||
- Messaging transport server/client:
|
||||
- `src/__Libraries/StellaOps.Router.Transport.Messaging/MessagingTransportServer.cs`
|
||||
- `src/__Libraries/StellaOps.Router.Transport.Messaging/MessagingTransportClient.cs`
|
||||
- Valkey-backed message queue factory:
|
||||
- `src/__Libraries/StellaOps.Messaging.Transport.Valkey/ValkeyMessageQueueFactory.cs`
|
||||
- Gateway WebService currently starts only TCP/TLS transport servers:
|
||||
- `src/Gateway/StellaOps.Gateway.WebService/Program.cs`
|
||||
- `src/Gateway/StellaOps.Gateway.WebService/Services/GatewayHostedService.cs`
|
||||
|
||||
## High-Level Flow
|
||||
1) Microservice connects via messaging transport:
|
||||
- publishes a HELLO message to the gateway request queue
|
||||
2) Gateway processes HELLO:
|
||||
- registers instance + endpoints into routing state
|
||||
3) Gateway routes an HTTP request to a microservice:
|
||||
- publishes a REQUEST message to the service request queue
|
||||
4) Microservice handles request:
|
||||
- executes handler (or ASP.NET bridge) and publishes a RESPONSE message
|
||||
5) Gateway returns response to the client.
|
||||
|
||||
## Queue Topology (Conceptual)
|
||||
The Messaging transport uses a small set of queues (names are configurable):
|
||||
- **Gateway request queue**: receives HELLO / HEARTBEAT / REQUEST frames from services
|
||||
- **Gateway response queue**: receives RESPONSE frames from services
|
||||
- **Per-service request queues**: gateway publishes REQUEST frames targeted to a service
|
||||
- **Dead letter queues** (optional): for messages that exceed retries/leases
|
||||
|
||||
## Configuration (Draft)
|
||||
### Gateway
|
||||
- Register Valkey messaging services (`StellaOps.Messaging.Transport.Valkey`)
|
||||
- Add messaging transport server (`AddMessagingTransportServer`)
|
||||
- Add Gateway config section for messaging transport options:
|
||||
- Valkey connection info (host/port/auth)
|
||||
- queue naming prefix
|
||||
- consumer group / lease duration / dead-letter suffix
|
||||
|
||||
### Microservice
|
||||
- Register Valkey messaging services (`StellaOps.Messaging.Transport.Valkey`)
|
||||
- Add messaging transport client (`AddMessagingTransportClient`)
|
||||
- Ensure Microservice Router SDK connects via `IMicroserviceTransport`
|
||||
|
||||
## Operational Semantics (Draft)
|
||||
- **At-least-once** delivery: message queues and leases imply retries are possible; handlers should be idempotent where feasible.
|
||||
- **Lease timeouts**: must be tuned to max handler execution time; long-running tasks should respond with 202 + job id rather than blocking.
|
||||
- **Determinism**: message ordering may vary; Router must not depend on arrival order for correctness (only for freshness/telemetry).
|
||||
|
||||
## Security Notes
|
||||
- Messaging transport is internal. External identity must still be enforced at the Gateway.
|
||||
- The Gateway must not trust client-supplied identity headers; it must overwrite reserved headers before dispatch.
|
||||
- See `docs/modules/gateway/identity-header-policy.md`.
|
||||
|
||||
## Gaps / Implementation Work
|
||||
1) Wire Messaging transport into Gateway:
|
||||
- start/stop `MessagingTransportServer`
|
||||
- subscribe to HELLO/HEARTBEAT/RESPONSE events and reuse existing HELLO validation + routing state updates
|
||||
2) Extend Gateway transport client to support `TransportType.Messaging` for dispatch.
|
||||
3) Add config mapping and deployment examples (compose/helm) for Valkey transport.
|
||||
4) Add integration tests covering:
|
||||
- microservice HELLO registration via messaging
|
||||
- request dispatch + response return
|
||||
|
||||
120
docs/modules/router/webservice-integration-guide.md
Normal file
120
docs/modules/router/webservice-integration-guide.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# Stella Router ASP.NET WebService Integration Guide
|
||||
|
||||
This guide explains how to integrate any ASP.NET Core WebService with the Stella Router for automatic endpoint discovery and dispatch.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Add a project reference to `StellaOps.Router.AspNet`:
|
||||
|
||||
```xml
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Router.AspNet/StellaOps.Router.AspNet.csproj" />
|
||||
```
|
||||
|
||||
## Integration Steps
|
||||
|
||||
### 1. Add Router Options to Service Options
|
||||
|
||||
In your service's options class (e.g., `MyServiceOptions.cs`), add:
|
||||
|
||||
```csharp
|
||||
using StellaOps.Router.AspNet;
|
||||
|
||||
public class MyServiceOptions
|
||||
{
|
||||
// ... existing options ...
|
||||
|
||||
/// <summary>
|
||||
/// Stella Router integration configuration (disabled by default).
|
||||
/// </summary>
|
||||
public StellaRouterOptionsBase? Router { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Register Services in Program.cs
|
||||
|
||||
Add the using directive:
|
||||
|
||||
```csharp
|
||||
using StellaOps.Router.AspNet;
|
||||
```
|
||||
|
||||
After service registration (e.g., after `AddControllers()`), add:
|
||||
|
||||
```csharp
|
||||
// Stella Router integration
|
||||
builder.Services.TryAddStellaRouter(
|
||||
serviceName: "my-service-name",
|
||||
version: typeof(Program).Assembly.GetName().Version?.ToString() ?? "1.0.0",
|
||||
routerOptions: options.Router);
|
||||
```
|
||||
|
||||
### 3. Enable Middleware
|
||||
|
||||
After `UseAuthorization()`, add:
|
||||
|
||||
```csharp
|
||||
app.TryUseStellaRouter(resolvedOptions.Router);
|
||||
```
|
||||
|
||||
### 4. Refresh Endpoint Cache
|
||||
|
||||
After all endpoints are mapped (before `app.RunAsync()`), add:
|
||||
|
||||
```csharp
|
||||
app.TryRefreshStellaRouterEndpoints(resolvedOptions.Router);
|
||||
```
|
||||
|
||||
## Configuration Example (YAML)
|
||||
|
||||
```yaml
|
||||
myservice:
|
||||
router:
|
||||
enabled: true
|
||||
region: "us-east-1"
|
||||
defaultTimeoutSeconds: 30
|
||||
heartbeatIntervalSeconds: 10
|
||||
gateways:
|
||||
- host: "router.stellaops.local"
|
||||
port: 9100
|
||||
transportType: "Tcp"
|
||||
useTls: true
|
||||
certificatePath: "/etc/certs/router.pem"
|
||||
```
|
||||
|
||||
## WebServices Requiring Integration
|
||||
|
||||
The following WebServices need to be updated with Router integration:
|
||||
|
||||
| Service | Path | Status |
|
||||
|---------|------|--------|
|
||||
| Scanner.WebService | `src/Scanner/StellaOps.Scanner.WebService` | ✅ Complete |
|
||||
| Concelier.WebService | `src/Concelier/StellaOps.Concelier.WebService` | Pending |
|
||||
| Excititor.WebService | `src/Excititor/StellaOps.Excititor.WebService` | Pending |
|
||||
| Gateway.WebService | `src/Gateway/StellaOps.Gateway.WebService` | Pending |
|
||||
| VexHub.WebService | `src/VexHub/StellaOps.VexHub.WebService` | Pending |
|
||||
| Attestor.WebService | `src/Attestor/StellaOps.Attestor/StellaOps.Attestor.WebService` | Pending |
|
||||
| EvidenceLocker.WebService | `src/EvidenceLocker/StellaOps.EvidenceLocker/StellaOps.EvidenceLocker.WebService` | Pending |
|
||||
| Findings.Ledger.WebService | `src/Findings/StellaOps.Findings.Ledger.WebService` | Pending |
|
||||
| AdvisoryAI.WebService | `src/AdvisoryAI/StellaOps.AdvisoryAI.WebService` | Pending |
|
||||
| IssuerDirectory.WebService | `src/IssuerDirectory/StellaOps.IssuerDirectory/StellaOps.IssuerDirectory.WebService` | Pending |
|
||||
| Notifier.WebService | `src/Notifier/StellaOps.Notifier/StellaOps.Notifier.WebService` | Pending |
|
||||
| Notify.WebService | `src/Notify/StellaOps.Notify.WebService` | Pending |
|
||||
| PacksRegistry.WebService | `src/PacksRegistry/StellaOps.PacksRegistry/StellaOps.PacksRegistry.WebService` | Pending |
|
||||
| RiskEngine.WebService | `src/RiskEngine/StellaOps.RiskEngine/StellaOps.RiskEngine.WebService` | Pending |
|
||||
| Signer.WebService | `src/Signer/StellaOps.Signer/StellaOps.Signer.WebService` | Pending |
|
||||
| TaskRunner.WebService | `src/TaskRunner/StellaOps.TaskRunner/StellaOps.TaskRunner.WebService` | Pending |
|
||||
| TimelineIndexer.WebService | `src/TimelineIndexer/StellaOps.TimelineIndexer/StellaOps.TimelineIndexer.WebService` | Pending |
|
||||
| Orchestrator.WebService | `src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.WebService` | Pending |
|
||||
| Scheduler.WebService | `src/Scheduler/StellaOps.Scheduler.WebService` | Pending |
|
||||
| ExportCenter.WebService | `src/ExportCenter/StellaOps.ExportCenter/StellaOps.ExportCenter.WebService` | Pending |
|
||||
|
||||
## Files Created
|
||||
|
||||
The Router.AspNet library includes the following files:
|
||||
|
||||
- `StellaOps.Router.AspNet.csproj` - Project file
|
||||
- `StellaRouterOptions.cs` - Unified router options
|
||||
- `StellaRouterExtensions.cs` - DI extensions (`AddStellaRouter`, `UseStellaRouter`)
|
||||
- `CompositeRequestDispatcher.cs` - Routes requests to ASP.NET or Stella endpoints
|
||||
- `StellaRouterOptionsBase.cs` - Base options class for embedding in service options
|
||||
- `StellaRouterIntegrationHelper.cs` - Helper methods for conditional integration
|
||||
@@ -1,2 +1,2 @@
|
||||
# SHA256 hashes for LNM v1 fixtures (recorded 2025-11-23)
|
||||
docs/modules/sbomservice/fixtures/lnm-v1/projections.json cec9f64e5672e536a6e7e954e79df0540d47fd3605446b4e510aa63b3cc3924c
|
||||
docs/modules/sbomservice/fixtures/lnm-v1/projections.json a469347019b0cf8d07ded0adce2b1590bbb089e3b306e7a7195b94341aeef18d
|
||||
|
||||
@@ -26,7 +26,7 @@ The Console presents operator dashboards for scans, policies, VEX evidence, runt
|
||||
- Auth smoke tests in `operations/auth-smoke.md`.
|
||||
- Observability runbook + dashboard stub in `operations/observability.md` and `operations/dashboards/console-ui-observability.json` (offline import).
|
||||
- Console architecture doc for layout and SSE fan-out.
|
||||
- Accessibility and security guides in ../../ui/ & ../../security/.
|
||||
- Operator guide: `../../15_UI_GUIDE.md`. Accessibility: `../../accessibility.md`. Security: `../../security/`.
|
||||
|
||||
## Related resources
|
||||
- ./operations/auth-smoke.md
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
> **Ownership:** Console Guild • Docs Guild
|
||||
> **Delivery scope:** `StellaOps.Web` Angular workspace, Console Web Gateway routes (`/console/*`), Downloads manifest surfacing, SSE fan-out for Scheduler & telemetry.
|
||||
> **Related docs:** [Console overview](../../ui/console-overview.md), [Navigation](../../ui/navigation.md), [Runs workspace](../../ui/runs.md), [Downloads](../../ui/downloads.md), [Console security posture](../../security/console-security.md), [Console observability](../../observability/ui-telemetry.md), [Deployment guide](../../deploy/console.md)
|
||||
> **Related docs:** [Console operator guide](../../15_UI_GUIDE.md), [Admin workflows](../../console/admin-tenants.md), [Air-gap workflows](../../console/airgap.md), [Console security posture](../../security/console-security.md), [Console observability](../../console/observability.md), [UI telemetry](../../observability/ui-telemetry.md), [Deployment guide](../../deploy/console.md)
|
||||
|
||||
This dossier describes the end-to-end architecture of the StellaOps Console as delivered in Sprint 23. It covers the Angular workspace layout, API/gateway integration points, live-update channels, performance budgets, offline workflows, and observability hooks needed to keep the console deterministic and air-gap friendly.
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ de9b24675a0a758e40647844a31a13a1be1667750a39fe59465b0353fd0dddd9 exports/observ
|
||||
0edf6cabd636c7bb1f210af2aecaf83de3cc21c82113a646429242ae72618b17 exports/webhook_admissions.ndjson.dsse
|
||||
40fabd4d7bc75c35ae063b2e931e79838c79b447528440456f5f4846951ff59d thresholds.yaml
|
||||
4dc099a742429a7ec300ac4c9eefe2f6b80bc0c10d7a7a3bbaf7f0a0f0ad7f20 thresholds.yaml.dsse
|
||||
f69f953c78134ef504b870cea47ba62d5e37a7a86ec0043d824dcb6073cd43fb kit/verify.sh
|
||||
2c616655e215e2443c748dfd7ee59a8a1c09b8a6c44248e9a0d62136efc181ae kit/verify.sh
|
||||
1cf8f0448881d067e5e001a1dfe9734b4cdfcaaf16c3e9a7321ceae56e4af8f2 kit/README.md
|
||||
eaba054428fa72cd9476cffe7a94450e4345ffe2e294e9079eb7c3703bcf7df0 kit/ed25519.pub
|
||||
ffe919a8b96619c1c5cf5bb8f7a7a61b61984d0f97802c131cf66e182f2d705f evidence/README.md
|
||||
3875d197cea64f5b5a830d6b53efb1246feef62fd8a01550034f192f59583f9d evidence/README.md
|
||||
|
||||
Reference in New Issue
Block a user