stela ops usage fixes roles propagation and timoeut, one account to support multi tenants, migrations consolidation, search to support documentation, doctor and open api vector db search

This commit is contained in:
master
2026-02-22 19:27:54 +02:00
parent a29f438f53
commit bd8fee6ed8
373 changed files with 832097 additions and 3369 deletions

View File

@@ -71,6 +71,40 @@ Consolidated Docker Compose configuration for the StellaOps platform. All profil
## Usage Patterns
### Migration Workflow (Compose)
Use this sequence for deterministic migration handling in compose-based deployments:
```bash
# 1) Start stack (or restart after release image update)
docker compose -f docker-compose.stella-ops.yml up -d
# 2) Check migration status for CLI-registered modules
stella system migrations-status --module all
# 3) Verify checksums
stella system migrations-verify --module all
# 4) Preview release migrations
stella system migrations-run --module all --category release --dry-run
# 5) Execute release migrations when approved
stella system migrations-run --module all --category release --force
# 6) Re-check status
stella system migrations-status --module all
```
This sequence is the canonical migration gate for on-prem upgradeable deployments.
Current behavior details:
- `./postgres-init` scripts execute only during first PostgreSQL initialization (`/docker-entrypoint-initdb.d` mount).
- Some services run startup migrations via hosted services; others are currently CLI-only or not wired yet.
- Use `docs/db/MIGRATION_INVENTORY.md` as the authoritative current-state matrix before production upgrades.
- Consolidation target policy and module cutover waves are defined in `docs/db/MIGRATION_CONSOLIDATION_PLAN.md`.
- UI-driven migration execution must use Platform admin endpoints (`/api/v1/admin/migrations/*`) and never direct browser-to-PostgreSQL access.
### Basic Development
```bash
@@ -83,10 +117,37 @@ docker compose -f docker-compose.stella-ops.yml config
# Start the platform
docker compose -f docker-compose.stella-ops.yml up -d
# RustFS health probe (S3 mode)
curl -fsS http://127.1.1.3:8080/status
# View logs
docker compose -f docker-compose.stella-ops.yml logs -f scanner-web
```
### Router Mode Switching
`router-gateway` now supports a compose-driven route table switch via `ROUTER_GATEWAY_CONFIG`.
```bash
# Default mode: microservice routing over Valkey messaging
ROUTER_GATEWAY_CONFIG=./router-gateway-local.json \
docker compose -f docker-compose.stella-ops.yml up -d
# Reverse-proxy fallback mode (no route-table edits required)
ROUTER_GATEWAY_CONFIG=./router-gateway-local.reverseproxy.json \
docker compose -f docker-compose.stella-ops.yml up -d
```
Validation endpoints:
```bash
# Aggregated OpenAPI
curl -k https://127.1.0.1/openapi.json
# Timeline API schema (through router-gateway)
curl -k https://127.1.0.1/openapi.json | jq '.paths["/api/v1/timeline"]'
```
### With Observability
```bash
@@ -304,12 +365,23 @@ Only externally-reachable services (Authority, Signer, Attestor, Concelier, Scan
## Sigstore Tools
Enable Sigstore CLI tools (rekor-cli, cosign) with the `sigstore` profile:
Enable Sigstore CLI tools (`rekor-cli`, `cosign`) with the `sigstore` profile:
```bash
docker compose -f docker-compose.stella-ops.yml --profile sigstore up -d
```
Enable self-hosted Rekor v2 with the `sigstore-local` profile:
```bash
docker compose -f docker-compose.stella-ops.yml --profile sigstore-local up -d rekor-v2
```
`sigstore-local` requires:
- Rekor signer key mounted at `../../etc/authority/keys/signing-dev.pem`
- Tessera backend config: `REKOR_GCP_BUCKET` and `REKOR_GCP_SPANNER`
- GCP ADC credentials available to the container runtime
---
## GPU Support for Advisory AI
@@ -367,8 +439,8 @@ docker compose -f docker-compose.stella-ops.yml \
```
**Tile Proxy vs Rekor v2:**
- Use `--profile sigstore` when running your own Rekor transparency log locally
- Use `docker-compose.tile-proxy.yml` when caching tiles from public Sigstore (rekor.sigstore.dev)
- Use `--profile sigstore-local` when running your own Rekor transparency log (GCP Tessera backend required).
- Use `docker-compose.tile-proxy.yml` when caching tiles from public Sigstore (`rekor.sigstore.dev`).
**Configuration:**
| Variable | Default | Purpose |

View File

@@ -0,0 +1,26 @@
name: stellaops-advisoryai-knowledge-test
services:
advisoryai-knowledge-postgres:
image: postgres:18.1-alpine
container_name: stellaops-advisoryai-knowledge-postgres-test
restart: unless-stopped
environment:
POSTGRES_DB: advisoryai_knowledge_test
POSTGRES_USER: stellaops_knowledge
POSTGRES_PASSWORD: stellaops_knowledge
TZ: UTC
PGTZ: UTC
ports:
- "55432:5432"
volumes:
- advisoryai-knowledge-postgres-data:/var/lib/postgresql/data
- ./postgres-init/advisoryai-knowledge-test:/docker-entrypoint-initdb.d:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U stellaops_knowledge -d advisoryai_knowledge_test"]
interval: 5s
timeout: 5s
retries: 20
volumes:
advisoryai-knowledge-postgres-data:

View File

@@ -13,7 +13,7 @@
# - PostgreSQL 18.1 on 127.1.1.1:5432 (db.stella-ops.local)
# - Valkey 9.0.1 on 127.1.1.2:6379 (cache.stella-ops.local)
# - SeaweedFS (S3) on 127.1.1.3:8333 (s3.stella-ops.local)
# - Rekor v2 (tiles) on 127.1.1.4:3322 (rekor.stella-ops.local, opt-in sigstore profile)
# - Rekor v2 (tiles) on 127.1.1.4:3322 (rekor.stella-ops.local, opt-in sigstore-local profile)
# - Zot (OCI registry) on 127.1.1.5:80 (registry.stella-ops.local)
# =============================================================================
@@ -70,14 +70,32 @@ services:
rekor-v2:
image: ${REKOR_TILES_IMAGE:-ghcr.io/sigstore/rekor-tiles:latest}
container_name: stellaops-dev-rekor
restart: unless-stopped
profiles: ["sigstore"]
restart: on-failure:5
command:
- rekor-server
- serve
- --http-address
- 0.0.0.0
- --http-port
- "3322"
- --grpc-address
- 0.0.0.0
- --grpc-port
- "3323"
- --signer-filepath
- /etc/rekor/signer.pem
- --gcp-bucket
- ${REKOR_GCP_BUCKET:-stellaops-rekor-dev}
- --gcp-spanner
- ${REKOR_GCP_SPANNER:-projects/stellaops-dev/instances/rekor/databases/rekor}
profiles: ["sigstore-local"]
volumes:
- rekor-tiles-data:/var/lib/rekor-tiles
- ../../etc/authority/keys/signing-dev.pem:/etc/rekor/signer.pem:ro
ports:
- "127.1.1.4:${REKOR_PORT:-3322}:3322"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3322/healthz"]
test: ["CMD", "curl", "-f", "http://localhost:3322/api/v1/log"]
interval: 30s
timeout: 10s
retries: 3

View File

@@ -42,6 +42,26 @@ x-plugin-tmpfs: &plugin-tmpfs
/app/plugins:
mode: "1777"
x-router-microservice-defaults: &router-microservice-defaults
Router__Region: "local"
Router__Gateways__0__Host: "router.stella-ops.local"
Router__Gateways__0__Port: "9100"
Router__Gateways__0__TransportType: "Messaging"
Router__OnMissingAuthorization: "${ROUTER_ON_MISSING_AUTHORIZATION:-WarnAndAllow}"
Router__TransportPlugins__Directory: "/app/plugins/router/transports"
Router__TransportPlugins__SearchPattern: "StellaOps.Router.Transport.*.dll"
Router__Messaging__Transport: "valkey"
Router__Messaging__PluginDirectory: "/app/plugins/messaging"
Router__Messaging__SearchPattern: "StellaOps.Messaging.Transport.*.dll"
Router__Messaging__RequestQueueTemplate: "router:requests:{service}"
Router__Messaging__ResponseQueueName: "router:responses"
Router__Messaging__RequestTimeout: "30s"
Router__Messaging__LeaseDuration: "5m"
Router__Messaging__BatchSize: "10"
Router__Messaging__HeartbeatInterval: "10s"
Router__Messaging__valkey__ConnectionString: "cache.stella-ops.local:6379"
Router__Messaging__valkey__Database: "0"
# ---------------------------------------------------------------------------
# Common anchors for the 60-service stack
# ---------------------------------------------------------------------------
@@ -151,7 +171,7 @@ services:
aliases:
- s3.stella-ops.local
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8333/"]
test: ["CMD-SHELL", "wget -q --spider http://127.0.0.1:8333/status || exit 1"]
interval: 30s
timeout: 10s
retries: 3
@@ -177,16 +197,39 @@ services:
rekor-v2:
image: ${REKOR_TILES_IMAGE:-ghcr.io/sigstore/rekor-tiles:latest}
container_name: stellaops-rekor
restart: unless-stopped
restart: on-failure:5
command:
- rekor-server
- serve
- --http-address
- 0.0.0.0
- --http-port
- "3322"
- --grpc-address
- 0.0.0.0
- --grpc-port
- "3323"
- --signer-filepath
- /etc/rekor/signer.pem
- --gcp-bucket
- ${REKOR_GCP_BUCKET:-stellaops-rekor-dev}
- --gcp-spanner
- ${REKOR_GCP_SPANNER:-projects/stellaops-dev/instances/rekor/databases/rekor}
volumes:
- rekor-tiles-data:/var/lib/rekor-tiles
- ../../etc/authority/keys/signing-dev.pem:/etc/rekor/signer.pem:ro
ports:
- "127.1.1.4:${REKOR_PORT:-3322}:3322"
networks:
stellaops:
aliases:
- rekor.stella-ops.local
profiles: ["sigstore"]
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3322/api/v1/log"]
interval: 30s
timeout: 10s
retries: 3
profiles: ["sigstore-local"]
labels:
<<: *release-labels
com.stellaops.component: "rekor-v2"
@@ -242,6 +285,20 @@ services:
Gateway__Auth__Authority__Issuer: "https://authority.stella-ops.local/"
Gateway__Auth__Authority__RequireHttpsMetadata: "false"
Gateway__Auth__Authority__MetadataAddress: "https://authority.stella-ops.local/.well-known/openid-configuration"
Gateway__Auth__Authority__ClaimsOverridesUrl: "${ROUTER_AUTHORITY_CLAIMS_OVERRIDES_URL:-http://authority.stella-ops.local}"
Gateway__Transports__Messaging__Enabled: "${ROUTER_GATEWAY_MESSAGING_ENABLED:-true}"
Gateway__Transports__Messaging__transport: "valkey"
Gateway__Transports__Messaging__ConnectionString: "cache.stella-ops.local:6379"
Gateway__Transports__Messaging__Database: "0"
Gateway__Transports__Messaging__valkey__ConnectionString: "cache.stella-ops.local:6379"
Gateway__Transports__Messaging__valkey__Database: "0"
Gateway__Transports__Messaging__RequestQueueTemplate: "router:requests:{service}"
Gateway__Transports__Messaging__ResponseQueueName: "router:responses"
Gateway__Transports__Messaging__ConsumerGroup: "router-gateway"
Gateway__Transports__Messaging__RequestTimeout: "30s"
Gateway__Transports__Messaging__LeaseDuration: "5m"
Gateway__Transports__Messaging__BatchSize: "10"
Gateway__Transports__Messaging__HeartbeatInterval: "10s"
# Audience validation disabled until authority includes aud in access tokens
# Gateway__Auth__Authority__Audiences__0: "stella-ops-api"
Logging__LogLevel__Microsoft.AspNetCore.Authentication: "Debug"
@@ -250,7 +307,7 @@ services:
volumes:
- *cert-volume
- console-dist:/app/wwwroot:ro
- ./router-gateway-local.json:/app/appsettings.local.json:ro
- ${ROUTER_GATEWAY_CONFIG:-./router-gateway-local.json}:/app/appsettings.local.json:ro
- ./envsettings-override.json:/app/envsettings-override.json:ro
- ./gateway-ca-bundle.crt:/etc/ssl/certs/ca-certificates.crt:ro
ports:
@@ -274,7 +331,7 @@ services:
depends_on: *depends-infra
environment:
ASPNETCORE_URLS: "http://+:8080"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
ConnectionStrings__Default: *postgres-connection
ConnectionStrings__Redis: "cache.stella-ops.local:6379"
Platform__Authority__Issuer: "https://authority.stella-ops.local/"
@@ -329,6 +386,8 @@ services:
STELLAOPS_SIGNALS_URL: "http://signals.stella-ops.local"
STELLAOPS_ADVISORYAI_URL: "http://advisoryai.stella-ops.local"
STELLAOPS_UNKNOWNS_URL: "http://unknowns.stella-ops.local"
Router__Enabled: "${PLATFORM_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "platform"
volumes:
- *cert-volume
- *ca-bundle
@@ -376,11 +435,15 @@ services:
STELLAOPS_AUTHORITY_AUTHORITY__PLUGINS__DESCRIPTORS__standard__Type: "standard"
STELLAOPS_AUTHORITY_AUTHORITY__PLUGINS__DESCRIPTORS__standard__AssemblyName: "StellaOps.Authority.Plugin.Standard"
STELLAOPS_AUTHORITY_AUTHORITY__PLUGINS__DESCRIPTORS__standard__Enabled: "true"
STELLAOPS_AUTHORITY_AUTHORITY__PLUGINS__DESCRIPTORS__standard__TenantId: "demo-prod"
STELLAOPS_AUTHORITY_AUTHORITY__PLUGINS__DESCRIPTORS__standard__BootstrapUser__Username: "admin"
STELLAOPS_AUTHORITY_AUTHORITY__PLUGINS__DESCRIPTORS__standard__BootstrapUser__Password: "password"
<<: *router-microservice-defaults
Router__Enabled: "${AUTHORITY_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "authority"
volumes:
- ../../etc/authority:/app/etc/authority:ro
- ../../etc/certificates/trust-roots:/etc/ssl/certs/stellaops:ro
tmpfs:
- /app/plugins:mode=1777
ports:
- "127.1.0.4:80:80"
networks:
@@ -401,12 +464,14 @@ services:
depends_on: *depends-infra
environment:
ASPNETCORE_URLS: "http://+:80;http://+:8080"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
ConnectionStrings__Default: *postgres-connection
ConnectionStrings__Redis: "cache.stella-ops.local:6379"
Gateway__Auth__DpopEnabled: "false"
Gateway__Auth__Authority__Issuer: "https://authority.stella-ops.local/"
Gateway__Auth__Authority__RequireHttpsMetadata: "false"
Router__Enabled: "${GATEWAY_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "gateway"
volumes:
- *cert-volume
- *ca-bundle
@@ -432,9 +497,12 @@ services:
- signer
environment:
ASPNETCORE_URLS: "http://+:8442"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
ATTESTOR_ATTESTOR__SIGNER__BASEURL: "http://signer.stella-ops.local"
ATTESTOR_ATTESTOR__POSTGRES__CONNECTIONSTRING: *postgres-connection
ConnectionStrings__Default: *postgres-connection
Router__Enabled: "${ATTESTOR_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "attestor"
volumes:
- *cert-volume
- *ca-bundle
@@ -485,7 +553,7 @@ services:
depends_on: *depends-infra
environment:
ASPNETCORE_URLS: "http://+:8080"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
EvidenceLocker__Database__ConnectionString: *postgres-connection
EvidenceLocker__Database__ApplyMigrationsAtStartup: "true"
EvidenceLocker__ObjectStore__Kind: "FileSystem"
@@ -506,6 +574,8 @@ services:
Authority__ResourceServer__BypassNetworks__2: "::1/128"
Authority__ResourceServer__BypassNetworks__3: "0.0.0.0/0"
Authority__ResourceServer__BypassNetworks__4: "::/0"
Router__Enabled: "${EVIDENCELOCKER_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "evidencelocker"
volumes:
- *cert-volume
- *ca-bundle
@@ -561,11 +631,11 @@ services:
valkey:
condition: service_healthy
rustfs:
condition: service_started
condition: service_healthy
environment:
ASPNETCORE_URLS: "http://+:8444"
<<: *kestrel-cert
SCANNER_SCANNER__PLUGINS__BASEDIRECTORY: "/app"
<<: [*kestrel-cert, *router-microservice-defaults]
SCANNER_SCANNER__PLUGINS__BASEDIRECTORY: "/tmp/stellaops"
SCANNER_SCANNER__STORAGE__DRIVER: "postgres"
SCANNER_SCANNER__STORAGE__DSN: *postgres-connection
SCANNER_SCANNER__STORAGE__COMMANDTIMEOUTSECONDS: "30"
@@ -595,6 +665,8 @@ services:
SCANNER_SURFACE_SECRETS_ROOT: "${SCANNER_SURFACE_SECRETS_ROOT:-/etc/stellaops/secrets}"
SCANNER_SURFACE_SECRETS_FALLBACK_PROVIDER: "${SCANNER_SURFACE_SECRETS_FALLBACK_PROVIDER:-}"
SCANNER_SURFACE_SECRETS_ALLOW_INLINE: "${SCANNER_SURFACE_SECRETS_ALLOW_INLINE:-false}"
Router__Enabled: "${SCANNER_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "scanner"
volumes:
- ../../etc/scanner:/app/etc/scanner:ro
- ../../etc/certificates/trust-roots:/etc/ssl/certs/stellaops:ro
@@ -603,7 +675,6 @@ services:
- ${SCANNER_OFFLINEKIT_REKOR_SNAPSHOT_HOST_PATH:-./offline/rekor-snapshot}:${SCANNER_OFFLINEKIT_REKORSNAPSHOTDIRECTORY:-/var/lib/stellaops/rekor-snapshot}:ro
- *cert-volume
tmpfs:
- /app/plugins:mode=1777
- /var/lib/stellaops/surface:mode=1777
ports:
- "127.1.0.8:80:80"
@@ -627,7 +698,7 @@ services:
valkey:
condition: service_healthy
rustfs:
condition: service_started
condition: service_healthy
environment:
<<: *kestrel-cert
# Scanner worker options
@@ -672,23 +743,23 @@ services:
valkey:
condition: service_healthy
rustfs:
condition: service_started
condition: service_healthy
environment:
ASPNETCORE_URLS: "http://+:8080"
<<: *kestrel-cert
CONCELIER_PLUGINS__BASEDIRECTORY: "/app"
<<: [*kestrel-cert, *router-microservice-defaults]
CONCELIER_PLUGINS__BASEDIRECTORY: "/tmp/stellaops"
CONCELIER_POSTGRESSTORAGE__CONNECTIONSTRING: *postgres-connection
CONCELIER_POSTGRESSTORAGE__ENABLED: "true"
CONCELIER_S3__ENDPOINT: "http://s3.stella-ops.local:8333"
CONCELIER_AUTHORITY__BASEURL: "https://authority.stella-ops.local"
CONCELIER_AUTHORITY__RESILIENCE__ALLOWOFFLINECACHEFALLBACK: "true"
CONCELIER_AUTHORITY__RESILIENCE__OFFLINECACHETOLERANCE: "${AUTHORITY_OFFLINE_CACHE_TOLERANCE:-00:30:00}"
Router__Enabled: "${CONCELIER_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "concelier"
volumes:
- concelier-jobs:/var/lib/concelier/jobs
- *cert-volume
- *ca-bundle
tmpfs:
- /app/plugins:mode=1777
ports:
- "127.1.0.9:80:80"
networks:
@@ -709,7 +780,7 @@ services:
depends_on: *depends-infra
environment:
ASPNETCORE_URLS: "http://+:8080"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
# Postgres options (section: Postgres:Excititor)
Postgres__Excititor__ConnectionString: *postgres-connection
Postgres__Excititor__SchemaName: "vex"
@@ -720,11 +791,11 @@ services:
Excititor__Authority__BaseUrls__default: "https://authority.stella-ops.local"
# IssuerDirectoryClientOptions.Validate() requires BaseAddress
IssuerDirectory__Client__BaseAddress: "http://issuerdirectory.stella-ops.local"
Router__Enabled: "${EXCITITOR_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "excititor"
volumes:
- *cert-volume
- *ca-bundle
tmpfs:
- /app/plugins:mode=1777
ports:
- "127.1.0.10:80:80"
networks:
@@ -778,11 +849,13 @@ services:
depends_on: *depends-infra
environment:
ASPNETCORE_URLS: "http://+:8080"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
ConnectionStrings__Default: *postgres-connection
ConnectionStrings__Redis: "cache.stella-ops.local:6379"
Postgres__ConnectionString: *postgres-connection
Postgres__SchemaName: "vexhub"
Router__Enabled: "${VEXHUB_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "vexhub"
volumes:
- *cert-volume
ports:
@@ -805,9 +878,11 @@ services:
depends_on: *depends-infra
environment:
ASPNETCORE_URLS: "http://+:8080"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
ConnectionStrings__Default: *postgres-connection
ConnectionStrings__Redis: "cache.stella-ops.local:6379"
Router__Enabled: "${VEXLENS_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "vexlens"
volumes:
- *cert-volume
ports:
@@ -830,9 +905,11 @@ services:
depends_on: *depends-infra
environment:
ASPNETCORE_URLS: "http://+:8080"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
ConnectionStrings__Default: *postgres-connection
ConnectionStrings__Redis: "cache.stella-ops.local:6379"
Router__Enabled: "${VULNEXPLORER_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "vulnexplorer"
volumes:
- *cert-volume
ports:
@@ -855,7 +932,7 @@ services:
depends_on: *depends-infra
environment:
ASPNETCORE_URLS: "http://+:8080"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
STELLAOPS_POLICY_ENGINE_Postgres__Policy__ConnectionString: *postgres-connection
STELLAOPS_POLICY_ENGINE_ConnectionStrings__Redis: "cache.stella-ops.local:6379"
STELLAOPS_POLICY_ENGINE_PolicyEngine__ResourceServer__Authority: "https://authority.stella-ops.local/"
@@ -877,6 +954,8 @@ services:
PolicyEngine__ResourceServer__BypassNetworks__2: "::1/128"
Logging__LogLevel__Microsoft.AspNetCore.Authentication: "Debug"
Logging__LogLevel__Microsoft.IdentityModel: "Debug"
Router__Enabled: "${POLICY_ENGINE_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "policy-engine"
volumes:
- *cert-volume
- *ca-bundle
@@ -900,7 +979,7 @@ services:
depends_on: *depends-infra
environment:
ASPNETCORE_URLS: "http://+:8084"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
ConnectionStrings__Default: *postgres-connection
ConnectionStrings__Redis: "cache.stella-ops.local:6379"
Postgres__Policy__ConnectionString: *postgres-connection
@@ -919,6 +998,8 @@ services:
STELLAOPS_POLICY_GATEWAY_PolicyGateway__ResourceServer__RequiredScopes__0: "policy:read"
STELLAOPS_POLICY_GATEWAY_PolicyGateway__PolicyEngine__ClientCredentials__Enabled: "false"
STELLAOPS_POLICY_GATEWAY_Postgres__Policy__ConnectionString: *postgres-connection
Router__Enabled: "${POLICY_GATEWAY_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "policy-gateway"
volumes:
- *cert-volume
- *ca-bundle
@@ -942,9 +1023,11 @@ services:
depends_on: *depends-infra
environment:
ASPNETCORE_URLS: "http://+:8080"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
ConnectionStrings__Default: *postgres-connection
ConnectionStrings__Redis: "cache.stella-ops.local:6379"
Router__Enabled: "${RISKENGINE_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "riskengine"
volumes:
- *cert-volume
ports:
@@ -986,9 +1069,11 @@ services:
depends_on: *depends-infra
environment:
ASPNETCORE_URLS: "http://+:8080"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
ConnectionStrings__Default: *postgres-connection
ConnectionStrings__Redis: "cache.stella-ops.local:6379"
Router__Enabled: "${ORCHESTRATOR_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "orchestrator"
volumes:
- *cert-volume
ports:
@@ -1030,9 +1115,11 @@ services:
depends_on: *depends-infra
environment:
ASPNETCORE_URLS: "http://+:8080"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
ConnectionStrings__Default: *postgres-connection
ConnectionStrings__Redis: "cache.stella-ops.local:6379"
Router__Enabled: "${TASKRUNNER_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "taskrunner"
volumes:
- *cert-volume
ports:
@@ -1082,7 +1169,7 @@ services:
depends_on: *depends-infra
environment:
ASPNETCORE_URLS: "http://+:8080"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
ConnectionStrings__Default: *postgres-connection
ConnectionStrings__Redis: "cache.stella-ops.local:6379"
Scheduler__Authority__Enabled: "false"
@@ -1095,10 +1182,11 @@ services:
Scheduler__Worker__Graph__Cartographer__BaseAddress: "http://cartographer.stella-ops.local"
Scheduler__Worker__Graph__SchedulerApi__BaseAddress: "http://scheduler.stella-ops.local"
Scheduler__Worker__Policy__Api__BaseAddress: "http://policy.stella-ops.local"
Router__Enabled: "${SCHEDULER_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "scheduler"
volumes:
- *cert-volume
tmpfs:
- /app/plugins:mode=1777
- /plugins:mode=1777
ports:
- "127.1.0.19:80:80"
@@ -1156,9 +1244,11 @@ services:
depends_on: *depends-infra
environment:
ASPNETCORE_URLS: "http://+:8080"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
ConnectionStrings__Default: *postgres-connection
ConnectionStrings__Redis: "cache.stella-ops.local:6379"
Router__Enabled: "${GRAPH_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "graph"
volumes:
- *cert-volume
ports:
@@ -1181,9 +1271,11 @@ services:
depends_on: *depends-infra
environment:
ASPNETCORE_URLS: "http://+:8080"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
ConnectionStrings__Default: *postgres-connection
ConnectionStrings__Redis: "cache.stella-ops.local:6379"
Router__Enabled: "${CARTOGRAPHER_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "cartographer"
volumes:
- *cert-volume
ports:
@@ -1206,9 +1298,11 @@ services:
depends_on: *depends-infra
environment:
ASPNETCORE_URLS: "http://+:8080"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
ConnectionStrings__Default: *postgres-connection
ConnectionStrings__Redis: "cache.stella-ops.local:6379"
Router__Enabled: "${REACHGRAPH_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "reachgraph"
volumes:
- *cert-volume
ports:
@@ -1231,9 +1325,12 @@ services:
depends_on: *depends-infra
environment:
ASPNETCORE_URLS: "http://+:8080"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
ConnectionStrings__Default: *postgres-connection
ConnectionStrings__Redis: "cache.stella-ops.local:6379"
TIMELINE_Postgres__Timeline__ConnectionString: *postgres-connection
Router__Enabled: "${TIMELINE_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "timelineindexer"
volumes:
- *cert-volume
ports:
@@ -1257,6 +1354,7 @@ services:
<<: *kestrel-cert
ConnectionStrings__Default: *postgres-connection
ConnectionStrings__Redis: "cache.stella-ops.local:6379"
TIMELINE_Postgres__Timeline__ConnectionString: *postgres-connection
volumes:
- *cert-volume
healthcheck:
@@ -1275,9 +1373,11 @@ services:
depends_on: *depends-infra
environment:
ASPNETCORE_URLS: "http://+:8080"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
ConnectionStrings__Default: *postgres-connection
ConnectionStrings__Redis: "cache.stella-ops.local:6379"
Router__Enabled: "${TIMELINE_SERVICE_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "timeline"
volumes:
- *cert-volume
ports:
@@ -1300,7 +1400,7 @@ services:
depends_on: *depends-infra
environment:
ASPNETCORE_URLS: "http://+:8080"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
ConnectionStrings__Default: *postgres-connection
ConnectionStrings__FindingsLedger: *postgres-connection
ConnectionStrings__Redis: "cache.stella-ops.local:6379"
@@ -1318,6 +1418,8 @@ services:
findings__ledger__Attachments__SignedUrlSecret: "dev-signed-url-secret"
findings__ledger__Attachments__SignedUrlLifetime: "00:15:00"
findings__ledger__Attachments__RequireConsoleCsrf: "false"
Router__Enabled: "${FINDINGS_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "findings-ledger"
volumes:
- *cert-volume
- *ca-bundle
@@ -1341,12 +1443,14 @@ services:
depends_on: *depends-infra
environment:
ASPNETCORE_URLS: "http://+:8080"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
ConnectionStrings__Default: *postgres-connection
ConnectionStrings__Redis: "cache.stella-ops.local:6379"
Doctor__Authority__Issuer: "https://authority.stella-ops.local/"
Doctor__Authority__RequireHttpsMetadata: "false"
Doctor__Authority__BypassNetworks__0: "172.19.0.0/16"
Router__Enabled: "${DOCTOR_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "doctor"
volumes:
- *cert-volume
- *ca-bundle
@@ -1368,13 +1472,17 @@ services:
restart: unless-stopped
depends_on: *depends-infra
environment:
<<: *kestrel-cert
ASPNETCORE_URLS: "http://+:80"
<<: [*kestrel-cert, *router-microservice-defaults]
ConnectionStrings__Default: *postgres-connection
ConnectionStrings__Redis: "cache.stella-ops.local:6379"
Router__Enabled: "${DOCTOR_SCHEDULER_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "doctor-scheduler"
volumes:
- *cert-volume
healthcheck:
<<: *healthcheck-worker
test: ["CMD-SHELL", "bash -c 'echo > /dev/tcp/$(hostname)/80'"]
<<: *healthcheck-tcp
networks:
stellaops:
aliases:
@@ -1389,9 +1497,11 @@ services:
depends_on: *depends-infra
environment:
ASPNETCORE_URLS: "http://+:8080"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
ConnectionStrings__Default: *postgres-connection
ConnectionStrings__Redis: "cache.stella-ops.local:6379"
Router__Enabled: "${OPSMEMORY_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "opsmemory"
volumes:
- *cert-volume
ports:
@@ -1414,9 +1524,11 @@ services:
depends_on: *depends-infra
environment:
ASPNETCORE_URLS: "http://+:8080"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
ConnectionStrings__Default: *postgres-connection
ConnectionStrings__Redis: "cache.stella-ops.local:6379"
Router__Enabled: "${NOTIFIER_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "notifier"
volumes:
- *cert-volume
ports:
@@ -1461,22 +1573,22 @@ services:
depends_on: *depends-infra
environment:
ASPNETCORE_URLS: "http://+:8080"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
DOTNET_ENVIRONMENT: Production
NOTIFY_NOTIFY__STORAGE__DRIVER: "postgres"
NOTIFY_NOTIFY__STORAGE__CONNECTIONSTRING: *postgres-connection
NOTIFY_NOTIFY__STORAGE__DATABASE: "notify"
NOTIFY_NOTIFY__PLUGINS__BASEDIRECTORY: "/app"
NOTIFY_NOTIFY__PLUGINS__BASEDIRECTORY: "/tmp/stellaops"
NOTIFY_NOTIFY__AUTHORITY__ENABLED: "false"
NOTIFY_NOTIFY__AUTHORITY__ALLOWANONYMOUSFALLBACK: "true"
NOTIFY_NOTIFY__AUTHORITY__DEVELOPMENTSIGNINGKEY: "StellaOps-Development-Key-NotifyService-2026!!"
NOTIFY_Postgres__Notify__ConnectionString: *postgres-connection
Postgres__Notify__ConnectionString: *postgres-connection
Router__Enabled: "${NOTIFY_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "notify"
volumes:
- ../../etc/notify:/app/etc/notify:ro
- *cert-volume
tmpfs:
- /app/plugins:mode=1777
ports:
- "127.1.0.29:80:80"
networks:
@@ -1499,9 +1611,11 @@ services:
- valkey
environment:
ASPNETCORE_URLS: "http://+:8441"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
ConnectionStrings__KeyManagement: *postgres-connection
ConnectionStrings__Default: *postgres-connection
Router__Enabled: "${SIGNER_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "signer"
volumes:
- *cert-volume
ports:
@@ -1524,9 +1638,11 @@ services:
depends_on: *depends-infra
environment:
ASPNETCORE_URLS: "http://+:8080"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
ConnectionStrings__Default: *postgres-connection
ConnectionStrings__Redis: "cache.stella-ops.local:6379"
Router__Enabled: "${SMREMOTE_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "smremote"
volumes:
- *cert-volume
ports:
@@ -1549,9 +1665,11 @@ services:
depends_on: *depends-infra
environment:
ASPNETCORE_URLS: "http://+:8080"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
ConnectionStrings__Default: *postgres-connection
ConnectionStrings__Redis: "cache.stella-ops.local:6379"
Router__Enabled: "${AIRGAP_CONTROLLER_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "airgap-controller"
volumes:
- *cert-volume
ports:
@@ -1575,8 +1693,10 @@ services:
depends_on: *depends-infra
environment:
ASPNETCORE_URLS: "http://+:8080"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
ConnectionStrings__Default: *postgres-connection
Router__Enabled: "${AIRGAP_TIME_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "airgap-time"
volumes:
- *cert-volume
ports:
@@ -1599,9 +1719,11 @@ services:
depends_on: *depends-infra
environment:
ASPNETCORE_URLS: "http://+:8080"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
ConnectionStrings__Default: *postgres-connection
ConnectionStrings__Redis: "cache.stella-ops.local:6379"
Router__Enabled: "${PACKSREGISTRY_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "packsregistry"
volumes:
- *cert-volume
ports:
@@ -1643,7 +1765,7 @@ services:
depends_on: *depends-infra
environment:
ASPNETCORE_URLS: "http://+:8080"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
ConnectionStrings__Default: *postgres-connection
RegistryTokenService__Signing__Issuer: "http://registry-token.stella-ops.local"
RegistryTokenService__Signing__KeyPath: "/app/etc/certs/kestrel-dev.pfx"
@@ -1656,6 +1778,8 @@ services:
RegistryTokenService__Plans__0__Repositories__0__Pattern: "*"
RegistryTokenService__Plans__0__Repositories__0__Actions__0: "pull"
RegistryTokenService__Plans__0__Repositories__0__Actions__1: "push"
Router__Enabled: "${REGISTRY_TOKEN_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "registry-token"
volumes:
- *cert-volume
- *ca-bundle
@@ -1679,9 +1803,11 @@ services:
depends_on: *depends-infra
environment:
ASPNETCORE_URLS: "http://+:8080"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
ConnectionStrings__Default: *postgres-connection
ConnectionStrings__Redis: "cache.stella-ops.local:6379"
Router__Enabled: "${BINARYINDEX_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "binaryindex"
volumes:
- *cert-volume
ports:
@@ -1706,7 +1832,7 @@ services:
- authority
environment:
ASPNETCORE_URLS: "http://+:8080"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
ISSUERDIRECTORY__AUTHORITY__ENABLED: "true"
ISSUERDIRECTORY__AUTHORITY__ISSUER: "${AUTHORITY_ISSUER:-http://authority.stella-ops.local}"
ISSUERDIRECTORY__AUTHORITY__AUDIENCES__0: "api://issuer-directory"
@@ -1714,6 +1840,8 @@ services:
ISSUERDIRECTORY__PERSISTENCE__PROVIDER: "Postgres"
ISSUERDIRECTORY__PERSISTENCE__POSTGRESCONNECTIONSTRING: *postgres-connection
ISSUERDIRECTORY__SEEDCSAFPUBLISHERS: "false"
Router__Enabled: "${ISSUERDIRECTORY_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "issuerdirectory"
volumes:
- ../../etc/issuer-directory:/app/etc/issuer-directory:ro
- *cert-volume
@@ -1737,12 +1865,14 @@ services:
depends_on: *depends-infra
environment:
ASPNETCORE_URLS: "http://+:8080"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
ConnectionStrings__Default: *postgres-connection
ConnectionStrings__Redis: "cache.stella-ops.local:6379"
Authority__ResourceServer__Authority: "https://authority.stella-ops.local/"
Authority__ResourceServer__RequireHttpsMetadata: "false"
Authority__ResourceServer__BypassNetworks__0: "172.19.0.0/16"
Router__Enabled: "${SYMBOLS_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "symbols"
volumes:
- *cert-volume
- *ca-bundle
@@ -1766,9 +1896,11 @@ services:
depends_on: *depends-infra
environment:
ASPNETCORE_URLS: "http://+:8080"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
ConnectionStrings__Default: *postgres-connection
ConnectionStrings__Redis: "cache.stella-ops.local:6379"
Router__Enabled: "${SBOMSERVICE_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "sbomservice"
volumes:
- *cert-volume
ports:
@@ -1791,7 +1923,7 @@ services:
depends_on: *depends-infra
environment:
ASPNETCORE_URLS: "http://+:8080"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
ConnectionStrings__Default: *postgres-connection
ConnectionStrings__Redis: "cache.stella-ops.local:6379"
Export__AllowInMemoryRepositories: "true"
@@ -1804,6 +1936,8 @@ services:
Authority__ResourceServer__BypassNetworks__2: "::1/128"
Authority__ResourceServer__BypassNetworks__3: "0.0.0.0/0"
Authority__ResourceServer__BypassNetworks__4: "::/0"
Router__Enabled: "${EXPORTCENTER_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "exportcenter"
volumes:
- *cert-volume
- *ca-bundle
@@ -1853,9 +1987,11 @@ services:
depends_on: *depends-infra
environment:
ASPNETCORE_URLS: "http://+:8080"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
ConnectionStrings__Default: *postgres-connection
ConnectionStrings__Redis: "cache.stella-ops.local:6379"
Router__Enabled: "${REPLAY_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "replay"
volumes:
- *cert-volume
ports:
@@ -1878,13 +2014,14 @@ services:
depends_on: *depends-infra
environment:
ASPNETCORE_URLS: "http://+:8080"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
ConnectionStrings__IntegrationsDb: *postgres-connection
ConnectionStrings__Default: *postgres-connection
ConnectionStrings__Redis: "cache.stella-ops.local:6379"
Router__Enabled: "${INTEGRATIONS_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "integrations"
volumes:
- *cert-volume
tmpfs:
- /app/plugins:mode=1777
ports:
- "127.1.0.42:80:80"
networks:
@@ -1947,9 +2084,11 @@ services:
depends_on: *depends-infra
environment:
ASPNETCORE_URLS: "http://+:8080"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
ConnectionStrings__Default: *postgres-connection
ConnectionStrings__Redis: "cache.stella-ops.local:6379"
Router__Enabled: "${SIGNALS_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "signals"
volumes:
- *cert-volume
ports:
@@ -1973,14 +2112,18 @@ services:
- scanner-web
environment:
ASPNETCORE_URLS: "http://+:8080"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
ADVISORYAI__AdvisoryAI__SbomBaseAddress: "${ADVISORY_AI_SBOM_BASEADDRESS:-http://scanner.stella-ops.local}"
ADVISORYAI__AdvisoryAI__Queue__DirectoryPath: "/var/lib/advisory-ai/queue"
ADVISORYAI__AdvisoryAI__Storage__PlanCacheDirectory: "/var/lib/advisory-ai/plans"
ADVISORYAI__AdvisoryAI__Storage__OutputDirectory: "/var/lib/advisory-ai/outputs"
ADVISORYAI__AdvisoryAI__Adapters__Llm__Enabled: "${ADVISORY_AI_LLM_ADAPTERS_ENABLED:-true}"
ADVISORYAI__AdvisoryAI__LlmProviders__ConfigDirectory: "${ADVISORY_AI_LLM_PROVIDERS_DIRECTORY:-/app/etc/llm-providers}"
ADVISORYAI__AdvisoryAI__Inference__Mode: "${ADVISORY_AI_INFERENCE_MODE:-Local}"
ADVISORYAI__AdvisoryAI__Inference__Remote__BaseAddress: "${ADVISORY_AI_REMOTE_BASEADDRESS:-}"
ADVISORYAI__AdvisoryAI__Inference__Remote__ApiKey: "${ADVISORY_AI_REMOTE_APIKEY:-}"
Router__Enabled: "${ADVISORYAI_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "advisoryai"
ports:
- "127.1.0.44:80:80"
volumes:
@@ -1989,8 +2132,6 @@ services:
- advisory-ai-queue:/var/lib/advisory-ai/queue
- advisory-ai-plans:/var/lib/advisory-ai/plans
- advisory-ai-outputs:/var/lib/advisory-ai/outputs
tmpfs:
- /app/plugins:mode=1777
networks:
stellaops:
aliases:
@@ -2034,10 +2175,12 @@ services:
depends_on: *depends-infra
environment:
ASPNETCORE_URLS: "http://+:8080"
<<: *kestrel-cert
<<: [*kestrel-cert, *router-microservice-defaults]
ConnectionStrings__Default: *postgres-connection
ConnectionStrings__UnknownsDb: *postgres-connection
ConnectionStrings__Redis: "cache.stella-ops.local:6379"
Router__Enabled: "${UNKNOWNS_ROUTER_ENABLED:-true}"
Router__Messaging__ConsumerGroup: "unknowns"
volumes:
- *cert-volume
ports:

View File

@@ -26,6 +26,18 @@ VALKEY_PORT=6379
# RustFS Object Storage
RUSTFS_HTTP_PORT=8333
# =============================================================================
# ROUTER GATEWAY MODE
# =============================================================================
# Router route table file mounted to /app/appsettings.local.json
# Microservice + Valkey mode (default):
ROUTER_GATEWAY_CONFIG=./router-gateway-local.json
# Reverse-proxy fallback mode:
# ROUTER_GATEWAY_CONFIG=./router-gateway-local.reverseproxy.json
# Authority claims override endpoint base URL consumed by router-gateway.
ROUTER_AUTHORITY_CLAIMS_OVERRIDES_URL=http://authority.stella-ops.local
# =============================================================================
# CORE SERVICES
# =============================================================================
@@ -109,12 +121,16 @@ SCHEDULER_SCANNER_BASEADDRESS=http://scanner.stella-ops.local
# REKOR / SIGSTORE CONFIGURATION
# =============================================================================
# Rekor server URL (default: public Sigstore, use http://rekor-v2:3000 for local)
# Rekor server URL (default: public Sigstore, use http://rekor-v2:3322 for local)
REKOR_SERVER_URL=https://rekor.sigstore.dev
REKOR_VERSION=V2
REKOR_TILE_BASE_URL=
REKOR_LOG_ID=c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d
REKOR_TILES_IMAGE=ghcr.io/sigstore/rekor-tiles:latest
# Local Rekor v2 (`--profile sigstore-local`) uses Tessera GCP backend.
# Override these with your actual GCP bucket/database identifiers.
REKOR_GCP_BUCKET=stellaops-rekor-dev
REKOR_GCP_SPANNER=projects/stellaops-dev/instances/rekor/databases/rekor
# =============================================================================
# ADVISORY AI CONFIGURATION

View File

@@ -6,7 +6,7 @@
"tokenEndpoint": "https://authority.stella-ops.local/connect/token",
"redirectUri": "https://stella-ops.local/auth/callback",
"postLogoutRedirectUri": "https://stella-ops.local/",
"scope": "openid profile email offline_access ui.read ui.admin authority:tenants.read authority:users.read authority:roles.read authority:clients.read authority:tokens.read authority:branding.read authority.audit.read graph:read sbom:read scanner:read policy:read policy:simulate policy:author policy:review policy:approve orch:read analytics.read advisory:read vex:read exceptions:read exceptions:approve aoc:verify findings:read release:read scheduler:read scheduler:operate notify.viewer notify.operator notify.admin notify.escalate evidence:read export.viewer export.operator export.admin vuln:view vuln:investigate vuln:operate vuln:audit",
"scope": "openid profile email offline_access ui.read ui.admin authority:tenants.read authority:users.read authority:roles.read authority:clients.read authority:tokens.read authority:branding.read authority.audit.read graph:read sbom:read scanner:read policy:read policy:simulate policy:author policy:review policy:approve orch:read analytics.read advisory:read vex:read exceptions:read exceptions:approve aoc:verify findings:read release:read scheduler:read scheduler:operate notify.viewer notify.operator notify.admin notify.escalate evidence:read export.viewer export.operator export.admin vuln:view vuln:investigate vuln:operate vuln:audit platform.context.read platform.context.write doctor:run doctor:admin",
"audience": "stella-ops-api",
"dpopAlgorithms": [
"ES256"

View File

View File

View File

@@ -0,0 +1,37 @@
[
{
"Path": "/platform/envsettings.json",
"Type": "StaticFile",
"TranslatesTo": "/app/envsettings-override.json"
},
{
"Path": "/platform",
"Type": "ReverseProxy",
"TranslatesTo": "http://platform.stella-ops.local/platform"
},
{
"Path": "/rekor",
"Type": "ReverseProxy",
"TranslatesTo": "http://rekor.stella-ops.local:3322"
},
{
"Path": "/envsettings.json",
"Type": "ReverseProxy",
"TranslatesTo": "http://platform.stella-ops.local/platform/envsettings.json"
},
{
"Path": "/",
"Type": "StaticFiles",
"TranslatesTo": "/app/wwwroot"
},
{
"Path": "/_error/404",
"Type": "NotFoundPage",
"TranslatesTo": "/app/wwwroot/index.html"
},
{
"Path": "/_error/500",
"Type": "ServerErrorPage",
"TranslatesTo": "/app/wwwroot/index.html"
}
]

View File

@@ -0,0 +1,4 @@
[
"/api/v1/advisory-ai/adapters/llm/providers",
"/api/v1/advisory-ai/adapters/llm/{providerId}/chat/completions"
]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,14 @@
{
"generatedUtc": "2026-02-22T15:58:47.8552877Z",
"operationsTotal": 2190,
"missingSummary": 0,
"missingDescription": 1417,
"missingSecurity": 1935,
"missingTimeoutExtension": 0,
"missingGatewayAuthExtension": 0,
"timelineOperations": 4,
"timelineMissingSummary": 0,
"timelineMissingDescription": 0,
"timelineMissingTimeoutExtension": 0,
"timelineMissingGatewayAuthExtension": 0
}

View File

@@ -0,0 +1,14 @@
{
"generatedUtc": "2026-02-22T15:56:44.2132655Z",
"operationsTotal": 1758,
"missingSummary": 0,
"missingDescription": 1230,
"missingSecurity": 1532,
"missingTimeoutExtension": 0,
"missingGatewayAuthExtension": 0,
"timelineOperations": 13,
"timelineMissingSummary": 0,
"timelineMissingDescription": 0,
"timelineMissingTimeoutExtension": 0,
"timelineMissingGatewayAuthExtension": 0
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,116 @@
"RouteType","RoutePath","RouteTarget","SelectedOpenApiPath","StatusCode"
"Microservice","/api/v1/release-orchestrator","http://orchestrator.stella-ops.local/api/v1/release-orchestrator","/api/v1/release-orchestrator/releases","200"
"Microservice","/api/v1/vex","https://vexhub.stella-ops.local/api/v1/vex","/api/v1/vex/index","200"
"Microservice","/api/v1/vexlens","http://vexlens.stella-ops.local/api/v1/vexlens","/api/v1/vexlens/stats","200"
"Microservice","/api/v1/notify","http://notify.stella-ops.local/api/v1/notify","/api/v1/notify/audit","400"
"Microservice","/api/v1/notifier","http://notifier.stella-ops.local/api/v1/notifier",,
"Microservice","/api/v1/concelier","http://concelier.stella-ops.local/api/v1/concelier","/api/v1/concelier/bundles","200"
"Microservice","/api/v1/platform","http://platform.stella-ops.local/api/v1/platform","/api/v1/platform/search","400"
"Microservice","/api/v1/scanner","http://scanner.stella-ops.local/api/v1/scanner",,
"Microservice","/api/v1/findings","http://findings.stella-ops.local/api/v1/findings","/api/v1/findings/summaries","200"
"Microservice","/api/v1/integrations","http://integrations.stella-ops.local/api/v1/integrations","/api/v1/integrations","401"
"Microservice","/api/v1/policy","http://policy-gateway.stella-ops.local/api/v1/policy","/api/v1/policy/gate/health","200"
"Microservice","/api/v1/reachability","http://reachgraph.stella-ops.local/api/v1/reachability",,
"Microservice","/api/v1/attestor","http://attestor.stella-ops.local/api/v1/attestor","/api/v1/attestor/predicates","200"
"Microservice","/api/v1/attestations","http://attestor.stella-ops.local/api/v1/attestations","/api/v1/attestations","200"
"Microservice","/api/v1/sbom","http://sbomservice.stella-ops.local/api/v1/sbom",,
"Microservice","/api/v1/signals","http://signals.stella-ops.local/api/v1/signals",,
"Microservice","/api/v1/orchestrator","http://orchestrator.stella-ops.local/api/v1/orchestrator","/api/v1/orchestrator/jobs","400"
"Microservice","/api/v1/authority/quotas","http://platform.stella-ops.local/api/v1/authority/quotas","/api/v1/authority/quotas","400"
"Microservice","/api/v1/authority","https://authority.stella-ops.local/api/v1/authority","/api/v1/authority/quotas","400"
"Microservice","/api/v1/trust","https://authority.stella-ops.local/api/v1/trust",,
"Microservice","/api/v1/evidence","https://evidencelocker.stella-ops.local/api/v1/evidence","/api/v1/evidence","200"
"Microservice","/api/v1/proofs","https://evidencelocker.stella-ops.local/api/v1/proofs",,
"Microservice","/api/v1/timeline","http://timelineindexer.stella-ops.local/api/v1/timeline","/api/v1/timeline","401"
"Microservice","/api/v1/advisory-ai/adapters","http://advisoryai.stella-ops.local/v1/advisory-ai/adapters","/","200"
"Microservice","/api/v1/advisory-ai","http://advisoryai.stella-ops.local/api/v1/advisory-ai","/","200"
"Microservice","/api/v1/advisory","http://advisoryai.stella-ops.local/api/v1/advisory","/","200"
"Microservice","/api/v1/vulnerabilities","http://scanner.stella-ops.local/api/v1/vulnerabilities",,
"Microservice","/api/v1/watchlist","http://scanner.stella-ops.local/api/v1/watchlist",,
"Microservice","/api/v1/resolve","http://binaryindex.stella-ops.local/api/v1/resolve",,
"Microservice","/api/v1/ops/binaryindex","http://binaryindex.stella-ops.local/api/v1/ops/binaryindex","/api/v1/ops/binaryindex/cache","200"
"Microservice","/api/v1/verdicts","https://evidencelocker.stella-ops.local/api/v1/verdicts","/api/v1/verdicts/{verdictId}","404"
"Microservice","/api/v1/lineage","http://sbomservice.stella-ops.local/api/v1/lineage","/api/v1/lineage/diff","400"
"Microservice","/api/v1/export","https://exportcenter.stella-ops.local/api/v1/export",,
"Microservice","/api/v1/triage","http://scanner.stella-ops.local/api/v1/triage","/api/v1/triage/inbox","401"
"Microservice","/api/v1/governance","http://policy-gateway.stella-ops.local/api/v1/governance","/api/v1/governance/audit/events","400"
"Microservice","/api/v1/determinization","http://policy-engine.stella-ops.local/api/v1/determinization",,
"Microservice","/api/v1/opsmemory","http://opsmemory.stella-ops.local/api/v1/opsmemory","/api/v1/opsmemory/stats","400"
"Microservice","/api/v1/secrets","http://scanner.stella-ops.local/api/v1/secrets","/api/v1/secrets/config/rules/categories","401"
"Microservice","/api/v1/sources","http://sbomservice.stella-ops.local/api/v1/sources",,
"Microservice","/api/v1/workflows","http://orchestrator.stella-ops.local/api/v1/workflows",,
"Microservice","/api/v1/witnesses","http://attestor.stella-ops.local/api/v1/witnesses",,
"Microservice","/v1/evidence-packs","https://evidencelocker.stella-ops.local/v1/evidence-packs",,
"Microservice","/v1/runs","http://orchestrator.stella-ops.local/v1/runs","/v1/runs/{id}","404"
"Microservice","/v1/advisory-ai/adapters","http://advisoryai.stella-ops.local/v1/advisory-ai/adapters","/","200"
"Microservice","/v1/advisory-ai","http://advisoryai.stella-ops.local/v1/advisory-ai","/v1/advisory-ai/consent","200"
"Microservice","/v1/audit-bundles","https://exportcenter.stella-ops.local/v1/audit-bundles","/v1/audit-bundles","200"
"Microservice","/policy","http://policy-gateway.stella-ops.local","/policyEngine","302"
"Microservice","/api/cvss","http://policy-gateway.stella-ops.local/api/cvss","/api/cvss/policies","401"
"Microservice","/api/policy","http://policy-gateway.stella-ops.local/api/policy","/api/policy/packs","401"
"Microservice","/api/risk","http://policy-engine.stella-ops.local/api/risk","/api/risk/events","400"
"Microservice","/api/analytics","http://platform.stella-ops.local/api/analytics","/api/analytics/backlog","400"
"Microservice","/api/release-orchestrator","http://orchestrator.stella-ops.local/api/release-orchestrator","/api/release-orchestrator/releases","200"
"Microservice","/api/releases","http://orchestrator.stella-ops.local/api/releases",,
"Microservice","/api/approvals","http://orchestrator.stella-ops.local/api/approvals",,
"Microservice","/api/gate","http://policy-gateway.stella-ops.local/api/gate",,
"Microservice","/api/risk-budget","http://policy-engine.stella-ops.local/api/risk-budget",,
"Microservice","/api/fix-verification","http://scanner.stella-ops.local/api/fix-verification",,
"Microservice","/api/compare","http://sbomservice.stella-ops.local/api/compare",,
"Microservice","/api/change-traces","http://sbomservice.stella-ops.local/api/change-traces",,
"Microservice","/api/exceptions","http://policy-gateway.stella-ops.local/api/exceptions",,
"Microservice","/api/verdicts","https://evidencelocker.stella-ops.local/api/verdicts",,
"Microservice","/api/orchestrator","http://orchestrator.stella-ops.local/api/orchestrator",,
"Microservice","/api/v1/gateway/rate-limits","http://platform.stella-ops.local/api/v1/gateway/rate-limits","/api/v1/gateway/rate-limits","400"
"Microservice","/api/sbomservice","http://sbomservice.stella-ops.local/api/sbomservice",,
"Microservice","/api/vuln-explorer","http://vulnexplorer.stella-ops.local/api/vuln-explorer",,
"Microservice","/api/vex","https://vexhub.stella-ops.local/api/vex",,
"Microservice","/api/admin","http://platform.stella-ops.local/api/admin",,
"Microservice","/api/scheduler","http://scheduler.stella-ops.local/api/scheduler",,
"Microservice","/api/v1/doctor/scheduler","http://doctor-scheduler.stella-ops.local/api/v1/doctor/scheduler","/api/v1/doctor/scheduler/trends","200"
"Microservice","/api/doctor","http://doctor.stella-ops.local/api/doctor",,
"Microservice","/api","http://platform.stella-ops.local/api","/api/v1/search","400"
"Microservice","/connect","https://authority.stella-ops.local/connect","/","200"
"Microservice","/.well-known","https://authority.stella-ops.local/well-known","/","200"
"Microservice","/jwks","https://authority.stella-ops.local/jwks","/","200"
"Microservice","/authority","https://authority.stella-ops.local/authority","/authority/audit/airgap","401"
"Microservice","/console","https://authority.stella-ops.local/console","/console/filters","401"
"Microservice","/gateway","http://gateway.stella-ops.local",,
"Microservice","/scanner","http://scanner.stella-ops.local","/scanner/api/v1/agents","401"
"Microservice","/policyGateway","http://policy-gateway.stella-ops.local","/policyGateway","302"
"Microservice","/policyEngine","http://policy-engine.stella-ops.local","/policyEngine","302"
"Microservice","/concelier","http://concelier.stella-ops.local","/concelier/jobs","200"
"Microservice","/attestor","http://attestor.stella-ops.local","/attestor/api/v1/bundles","400"
"Microservice","/notify","http://notify.stella-ops.local","/notify/api/v1/notify/audit","400"
"Microservice","/notifier","http://notifier.stella-ops.local","/notifier/api/v2/ack","400"
"Microservice","/scheduler","http://scheduler.stella-ops.local","/scheduler/graphs/jobs","401"
"Microservice","/signals","http://signals.stella-ops.local","/signals/signals/ping","403"
"Microservice","/excititor","http://excititor.stella-ops.local","/excititor/vex/raw","400"
"Microservice","/findingsLedger","http://findings.stella-ops.local","/findingsLedger/v1/alerts","400"
"Microservice","/vexhub","https://vexhub.stella-ops.local","/vexhub/api/v1/vex/index","200"
"Microservice","/vexlens","http://vexlens.stella-ops.local","/vexlens/api/v1/vexlens/stats","200"
"Microservice","/orchestrator","http://orchestrator.stella-ops.local","/orchestrator/scale/load","200"
"Microservice","/taskrunner","http://taskrunner.stella-ops.local","/taskrunner","302"
"Microservice","/cartographer","http://cartographer.stella-ops.local",,
"Microservice","/reachgraph","http://reachgraph.stella-ops.local","/reachgraph/v1/cve-mappings/stats","400"
"Microservice","/doctor","http://doctor.stella-ops.local","/doctor/api/v1/doctor/checks","401"
"Microservice","/integrations","http://integrations.stella-ops.local","/integrations/api/v1/integrations","401"
"Microservice","/replay","http://replay.stella-ops.local","/replay/v1/pit/advisory/{cveId}","400"
"Microservice","/exportcenter","https://exportcenter.stella-ops.local","/exportcenter/exports","410"
"Microservice","/evidencelocker","https://evidencelocker.stella-ops.local","/evidencelocker/evidence/score","400"
"Microservice","/signer","http://signer.stella-ops.local","/signer","200"
"Microservice","/binaryindex","http://binaryindex.stella-ops.local","/binaryindex/api/v1/golden-sets","200"
"Microservice","/riskengine","http://riskengine.stella-ops.local","/riskengine/risk-scores/providers","200"
"Microservice","/vulnexplorer","http://vulnexplorer.stella-ops.local","/vulnexplorer/v1/vulns","400"
"Microservice","/sbomservice","http://sbomservice.stella-ops.local","/sbomservice/sbom/paths","400"
"Microservice","/advisoryai","http://advisoryai.stella-ops.local","/advisoryai/v1/evidence-packs","401"
"Microservice","/unknowns","http://unknowns.stella-ops.local","/unknowns/api/unknowns","400"
"Microservice","/timelineindexer","http://timelineindexer.stella-ops.local","/timelineindexer/timeline","401"
"Microservice","/opsmemory","http://opsmemory.stella-ops.local","/opsmemory/api/v1/opsmemory/stats","400"
"Microservice","/issuerdirectory","http://issuerdirectory.stella-ops.local","/issuerdirectory/issuer-directory/issuers","400"
"Microservice","/symbols","http://symbols.stella-ops.local","/symbols/v1/symbols/manifests","404"
"Microservice","/packsregistry","http://packsregistry.stella-ops.local","/packsregistry/api/v1/packs","403"
"Microservice","/registryTokenservice","http://registry-token.stella-ops.local",,
"Microservice","/airgapController","http://airgap-controller.stella-ops.local","/","200"
"Microservice","/airgapTime","http://airgap-time.stella-ops.local",,
"Microservice","/smremote","http://smremote.stella-ops.local","/","200"
1 RouteType RoutePath RouteTarget SelectedOpenApiPath StatusCode
2 Microservice /api/v1/release-orchestrator http://orchestrator.stella-ops.local/api/v1/release-orchestrator /api/v1/release-orchestrator/releases 200
3 Microservice /api/v1/vex https://vexhub.stella-ops.local/api/v1/vex /api/v1/vex/index 200
4 Microservice /api/v1/vexlens http://vexlens.stella-ops.local/api/v1/vexlens /api/v1/vexlens/stats 200
5 Microservice /api/v1/notify http://notify.stella-ops.local/api/v1/notify /api/v1/notify/audit 400
6 Microservice /api/v1/notifier http://notifier.stella-ops.local/api/v1/notifier
7 Microservice /api/v1/concelier http://concelier.stella-ops.local/api/v1/concelier /api/v1/concelier/bundles 200
8 Microservice /api/v1/platform http://platform.stella-ops.local/api/v1/platform /api/v1/platform/search 400
9 Microservice /api/v1/scanner http://scanner.stella-ops.local/api/v1/scanner
10 Microservice /api/v1/findings http://findings.stella-ops.local/api/v1/findings /api/v1/findings/summaries 200
11 Microservice /api/v1/integrations http://integrations.stella-ops.local/api/v1/integrations /api/v1/integrations 401
12 Microservice /api/v1/policy http://policy-gateway.stella-ops.local/api/v1/policy /api/v1/policy/gate/health 200
13 Microservice /api/v1/reachability http://reachgraph.stella-ops.local/api/v1/reachability
14 Microservice /api/v1/attestor http://attestor.stella-ops.local/api/v1/attestor /api/v1/attestor/predicates 200
15 Microservice /api/v1/attestations http://attestor.stella-ops.local/api/v1/attestations /api/v1/attestations 200
16 Microservice /api/v1/sbom http://sbomservice.stella-ops.local/api/v1/sbom
17 Microservice /api/v1/signals http://signals.stella-ops.local/api/v1/signals
18 Microservice /api/v1/orchestrator http://orchestrator.stella-ops.local/api/v1/orchestrator /api/v1/orchestrator/jobs 400
19 Microservice /api/v1/authority/quotas http://platform.stella-ops.local/api/v1/authority/quotas /api/v1/authority/quotas 400
20 Microservice /api/v1/authority https://authority.stella-ops.local/api/v1/authority /api/v1/authority/quotas 400
21 Microservice /api/v1/trust https://authority.stella-ops.local/api/v1/trust
22 Microservice /api/v1/evidence https://evidencelocker.stella-ops.local/api/v1/evidence /api/v1/evidence 200
23 Microservice /api/v1/proofs https://evidencelocker.stella-ops.local/api/v1/proofs
24 Microservice /api/v1/timeline http://timelineindexer.stella-ops.local/api/v1/timeline /api/v1/timeline 401
25 Microservice /api/v1/advisory-ai/adapters http://advisoryai.stella-ops.local/v1/advisory-ai/adapters / 200
26 Microservice /api/v1/advisory-ai http://advisoryai.stella-ops.local/api/v1/advisory-ai / 200
27 Microservice /api/v1/advisory http://advisoryai.stella-ops.local/api/v1/advisory / 200
28 Microservice /api/v1/vulnerabilities http://scanner.stella-ops.local/api/v1/vulnerabilities
29 Microservice /api/v1/watchlist http://scanner.stella-ops.local/api/v1/watchlist
30 Microservice /api/v1/resolve http://binaryindex.stella-ops.local/api/v1/resolve
31 Microservice /api/v1/ops/binaryindex http://binaryindex.stella-ops.local/api/v1/ops/binaryindex /api/v1/ops/binaryindex/cache 200
32 Microservice /api/v1/verdicts https://evidencelocker.stella-ops.local/api/v1/verdicts /api/v1/verdicts/{verdictId} 404
33 Microservice /api/v1/lineage http://sbomservice.stella-ops.local/api/v1/lineage /api/v1/lineage/diff 400
34 Microservice /api/v1/export https://exportcenter.stella-ops.local/api/v1/export
35 Microservice /api/v1/triage http://scanner.stella-ops.local/api/v1/triage /api/v1/triage/inbox 401
36 Microservice /api/v1/governance http://policy-gateway.stella-ops.local/api/v1/governance /api/v1/governance/audit/events 400
37 Microservice /api/v1/determinization http://policy-engine.stella-ops.local/api/v1/determinization
38 Microservice /api/v1/opsmemory http://opsmemory.stella-ops.local/api/v1/opsmemory /api/v1/opsmemory/stats 400
39 Microservice /api/v1/secrets http://scanner.stella-ops.local/api/v1/secrets /api/v1/secrets/config/rules/categories 401
40 Microservice /api/v1/sources http://sbomservice.stella-ops.local/api/v1/sources
41 Microservice /api/v1/workflows http://orchestrator.stella-ops.local/api/v1/workflows
42 Microservice /api/v1/witnesses http://attestor.stella-ops.local/api/v1/witnesses
43 Microservice /v1/evidence-packs https://evidencelocker.stella-ops.local/v1/evidence-packs
44 Microservice /v1/runs http://orchestrator.stella-ops.local/v1/runs /v1/runs/{id} 404
45 Microservice /v1/advisory-ai/adapters http://advisoryai.stella-ops.local/v1/advisory-ai/adapters / 200
46 Microservice /v1/advisory-ai http://advisoryai.stella-ops.local/v1/advisory-ai /v1/advisory-ai/consent 200
47 Microservice /v1/audit-bundles https://exportcenter.stella-ops.local/v1/audit-bundles /v1/audit-bundles 200
48 Microservice /policy http://policy-gateway.stella-ops.local /policyEngine 302
49 Microservice /api/cvss http://policy-gateway.stella-ops.local/api/cvss /api/cvss/policies 401
50 Microservice /api/policy http://policy-gateway.stella-ops.local/api/policy /api/policy/packs 401
51 Microservice /api/risk http://policy-engine.stella-ops.local/api/risk /api/risk/events 400
52 Microservice /api/analytics http://platform.stella-ops.local/api/analytics /api/analytics/backlog 400
53 Microservice /api/release-orchestrator http://orchestrator.stella-ops.local/api/release-orchestrator /api/release-orchestrator/releases 200
54 Microservice /api/releases http://orchestrator.stella-ops.local/api/releases
55 Microservice /api/approvals http://orchestrator.stella-ops.local/api/approvals
56 Microservice /api/gate http://policy-gateway.stella-ops.local/api/gate
57 Microservice /api/risk-budget http://policy-engine.stella-ops.local/api/risk-budget
58 Microservice /api/fix-verification http://scanner.stella-ops.local/api/fix-verification
59 Microservice /api/compare http://sbomservice.stella-ops.local/api/compare
60 Microservice /api/change-traces http://sbomservice.stella-ops.local/api/change-traces
61 Microservice /api/exceptions http://policy-gateway.stella-ops.local/api/exceptions
62 Microservice /api/verdicts https://evidencelocker.stella-ops.local/api/verdicts
63 Microservice /api/orchestrator http://orchestrator.stella-ops.local/api/orchestrator
64 Microservice /api/v1/gateway/rate-limits http://platform.stella-ops.local/api/v1/gateway/rate-limits /api/v1/gateway/rate-limits 400
65 Microservice /api/sbomservice http://sbomservice.stella-ops.local/api/sbomservice
66 Microservice /api/vuln-explorer http://vulnexplorer.stella-ops.local/api/vuln-explorer
67 Microservice /api/vex https://vexhub.stella-ops.local/api/vex
68 Microservice /api/admin http://platform.stella-ops.local/api/admin
69 Microservice /api/scheduler http://scheduler.stella-ops.local/api/scheduler
70 Microservice /api/v1/doctor/scheduler http://doctor-scheduler.stella-ops.local/api/v1/doctor/scheduler /api/v1/doctor/scheduler/trends 200
71 Microservice /api/doctor http://doctor.stella-ops.local/api/doctor
72 Microservice /api http://platform.stella-ops.local/api /api/v1/search 400
73 Microservice /connect https://authority.stella-ops.local/connect / 200
74 Microservice /.well-known https://authority.stella-ops.local/well-known / 200
75 Microservice /jwks https://authority.stella-ops.local/jwks / 200
76 Microservice /authority https://authority.stella-ops.local/authority /authority/audit/airgap 401
77 Microservice /console https://authority.stella-ops.local/console /console/filters 401
78 Microservice /gateway http://gateway.stella-ops.local
79 Microservice /scanner http://scanner.stella-ops.local /scanner/api/v1/agents 401
80 Microservice /policyGateway http://policy-gateway.stella-ops.local /policyGateway 302
81 Microservice /policyEngine http://policy-engine.stella-ops.local /policyEngine 302
82 Microservice /concelier http://concelier.stella-ops.local /concelier/jobs 200
83 Microservice /attestor http://attestor.stella-ops.local /attestor/api/v1/bundles 400
84 Microservice /notify http://notify.stella-ops.local /notify/api/v1/notify/audit 400
85 Microservice /notifier http://notifier.stella-ops.local /notifier/api/v2/ack 400
86 Microservice /scheduler http://scheduler.stella-ops.local /scheduler/graphs/jobs 401
87 Microservice /signals http://signals.stella-ops.local /signals/signals/ping 403
88 Microservice /excititor http://excititor.stella-ops.local /excititor/vex/raw 400
89 Microservice /findingsLedger http://findings.stella-ops.local /findingsLedger/v1/alerts 400
90 Microservice /vexhub https://vexhub.stella-ops.local /vexhub/api/v1/vex/index 200
91 Microservice /vexlens http://vexlens.stella-ops.local /vexlens/api/v1/vexlens/stats 200
92 Microservice /orchestrator http://orchestrator.stella-ops.local /orchestrator/scale/load 200
93 Microservice /taskrunner http://taskrunner.stella-ops.local /taskrunner 302
94 Microservice /cartographer http://cartographer.stella-ops.local
95 Microservice /reachgraph http://reachgraph.stella-ops.local /reachgraph/v1/cve-mappings/stats 400
96 Microservice /doctor http://doctor.stella-ops.local /doctor/api/v1/doctor/checks 401
97 Microservice /integrations http://integrations.stella-ops.local /integrations/api/v1/integrations 401
98 Microservice /replay http://replay.stella-ops.local /replay/v1/pit/advisory/{cveId} 400
99 Microservice /exportcenter https://exportcenter.stella-ops.local /exportcenter/exports 410
100 Microservice /evidencelocker https://evidencelocker.stella-ops.local /evidencelocker/evidence/score 400
101 Microservice /signer http://signer.stella-ops.local /signer 200
102 Microservice /binaryindex http://binaryindex.stella-ops.local /binaryindex/api/v1/golden-sets 200
103 Microservice /riskengine http://riskengine.stella-ops.local /riskengine/risk-scores/providers 200
104 Microservice /vulnexplorer http://vulnexplorer.stella-ops.local /vulnexplorer/v1/vulns 400
105 Microservice /sbomservice http://sbomservice.stella-ops.local /sbomservice/sbom/paths 400
106 Microservice /advisoryai http://advisoryai.stella-ops.local /advisoryai/v1/evidence-packs 401
107 Microservice /unknowns http://unknowns.stella-ops.local /unknowns/api/unknowns 400
108 Microservice /timelineindexer http://timelineindexer.stella-ops.local /timelineindexer/timeline 401
109 Microservice /opsmemory http://opsmemory.stella-ops.local /opsmemory/api/v1/opsmemory/stats 400
110 Microservice /issuerdirectory http://issuerdirectory.stella-ops.local /issuerdirectory/issuer-directory/issuers 400
111 Microservice /symbols http://symbols.stella-ops.local /symbols/v1/symbols/manifests 404
112 Microservice /packsregistry http://packsregistry.stella-ops.local /packsregistry/api/v1/packs 403
113 Microservice /registryTokenservice http://registry-token.stella-ops.local
114 Microservice /airgapController http://airgap-controller.stella-ops.local / 200
115 Microservice /airgapTime http://airgap-time.stella-ops.local
116 Microservice /smremote http://smremote.stella-ops.local / 200

View File

@@ -0,0 +1,119 @@
"RouteType","RoutePath","RouteTarget","SelectedOpenApiPath","StatusCode"
"ReverseProxy","/api/v1/release-orchestrator","http://orchestrator.stella-ops.local/api/v1/release-orchestrator","/api/v1/release-orchestrator/releases","200"
"ReverseProxy","/api/v1/vex","https://vexhub.stella-ops.local/api/v1/vex","/api/v1/vex/index","200"
"ReverseProxy","/api/v1/vexlens","http://vexlens.stella-ops.local/api/v1/vexlens","/api/v1/vexlens/stats","200"
"ReverseProxy","/api/v1/notify","http://notify.stella-ops.local/api/v1/notify","/api/v1/notify/audit","400"
"ReverseProxy","/api/v1/notifier","http://notifier.stella-ops.local/api/v1/notifier",,
"ReverseProxy","/api/v1/concelier","http://concelier.stella-ops.local/api/v1/concelier","/api/v1/concelier/bundles","200"
"ReverseProxy","/api/v1/platform","http://platform.stella-ops.local/api/v1/platform","/api/v1/platform/search","401"
"ReverseProxy","/api/v1/scanner","http://scanner.stella-ops.local/api/v1/scanner",,
"ReverseProxy","/api/v1/findings","http://findings.stella-ops.local/api/v1/findings","/api/v1/findings/summaries","401"
"ReverseProxy","/api/v1/integrations","http://integrations.stella-ops.local/api/v1/integrations","/api/v1/integrations","200"
"ReverseProxy","/api/v1/policy","http://policy-gateway.stella-ops.local/api/v1/policy","/api/v1/policy/schema","404"
"ReverseProxy","/api/v1/reachability","http://reachgraph.stella-ops.local/api/v1/reachability",,
"ReverseProxy","/api/v1/attestor","http://attestor.stella-ops.local/api/v1/attestor","/api/v1/attestor/policies","404"
"ReverseProxy","/api/v1/attestations","http://attestor.stella-ops.local/api/v1/attestations","/api/v1/attestations","401"
"ReverseProxy","/api/v1/sbom","http://sbomservice.stella-ops.local/api/v1/sbom","/api/v1/sbom/hot-lookup/components","404"
"ReverseProxy","/api/v1/signals","http://signals.stella-ops.local/api/v1/signals","/api/v1/signals/hot-symbols","404"
"ReverseProxy","/api/v1/orchestrator","http://orchestrator.stella-ops.local/api/v1/orchestrator","/api/v1/orchestrator/jobs","400"
"ReverseProxy","/api/v1/authority/quotas","http://platform.stella-ops.local/api/v1/authority/quotas","/api/v1/authority/quotas","401"
"ReverseProxy","/api/v1/authority","https://authority.stella-ops.local/api/v1/authority","/api/v1/authority/quotas","401"
"ReverseProxy","/api/v1/trust","https://authority.stella-ops.local/api/v1/trust",,
"ReverseProxy","/api/v1/evidence","https://evidencelocker.stella-ops.local/api/v1/evidence","/api/v1/evidence","401"
"ReverseProxy","/api/v1/proofs","https://evidencelocker.stella-ops.local/api/v1/proofs","/api/v1/proofs/id/{proofId}","401"
"ReverseProxy","/api/v1/timeline","http://timelineindexer.stella-ops.local/api/v1/timeline","/api/v1/timeline","401"
"ReverseProxy","/api/v1/advisory-ai/adapters","http://advisoryai.stella-ops.local/v1/advisory-ai/adapters",,
"ReverseProxy","/api/v1/advisory-ai","http://advisoryai.stella-ops.local/api/v1/advisory-ai",,
"ReverseProxy","/api/v1/advisory","http://advisoryai.stella-ops.local/api/v1/advisory","/api/v1/advisory-sources","404"
"ReverseProxy","/api/v1/vulnerabilities","http://scanner.stella-ops.local/api/v1/vulnerabilities",,
"ReverseProxy","/api/v1/watchlist","http://scanner.stella-ops.local/api/v1/watchlist","/api/v1/watchlist","404"
"ReverseProxy","/api/v1/resolve","http://binaryindex.stella-ops.local/api/v1/resolve",,
"ReverseProxy","/api/v1/ops/binaryindex","http://binaryindex.stella-ops.local/api/v1/ops/binaryindex","/api/v1/ops/binaryindex/cache","200"
"ReverseProxy","/api/v1/verdicts","https://evidencelocker.stella-ops.local/api/v1/verdicts","/api/v1/verdicts/{verdictId}","401"
"ReverseProxy","/api/v1/lineage","http://sbomservice.stella-ops.local/api/v1/lineage","/api/v1/lineage/diff","400"
"ReverseProxy","/api/v1/export","https://exportcenter.stella-ops.local/api/v1/export","/api/v1/export/jobs","401"
"ReverseProxy","/api/v1/triage","http://scanner.stella-ops.local/api/v1/triage","/api/v1/triage/inbox","400"
"ReverseProxy","/api/v1/governance","http://policy-gateway.stella-ops.local/api/v1/governance","/api/v1/governance/audit/events","400"
"ReverseProxy","/api/v1/determinization","http://policy-engine.stella-ops.local/api/v1/determinization",,
"ReverseProxy","/api/v1/opsmemory","http://opsmemory.stella-ops.local/api/v1/opsmemory","/api/v1/opsmemory/stats","400"
"ReverseProxy","/api/v1/secrets","http://scanner.stella-ops.local/api/v1/secrets","/api/v1/secrets/config/rules/categories","200"
"ReverseProxy","/api/v1/sources","http://sbomservice.stella-ops.local/api/v1/sources",,
"ReverseProxy","/api/v1/workflows","http://orchestrator.stella-ops.local/api/v1/workflows",,
"ReverseProxy","/api/v1/witnesses","http://attestor.stella-ops.local/api/v1/witnesses","/api/v1/witnesses","404"
"ReverseProxy","/v1/evidence-packs","https://evidencelocker.stella-ops.local/v1/evidence-packs","/v1/evidence-packs","401"
"ReverseProxy","/v1/runs","http://orchestrator.stella-ops.local/v1/runs","/v1/runs/{id}","404"
"ReverseProxy","/v1/advisory-ai/adapters","http://advisoryai.stella-ops.local/v1/advisory-ai/adapters","/","200"
"ReverseProxy","/v1/advisory-ai","http://advisoryai.stella-ops.local/v1/advisory-ai","/v1/advisory-ai/consent","200"
"ReverseProxy","/v1/audit-bundles","https://exportcenter.stella-ops.local/v1/audit-bundles","/v1/audit-bundles","200"
"ReverseProxy","/policy","http://policy-gateway.stella-ops.local","/policy/snapshots","404"
"ReverseProxy","/api/cvss","http://policy-gateway.stella-ops.local/api/cvss","/api/cvss/policies","401"
"ReverseProxy","/api/policy","http://policy-gateway.stella-ops.local/api/policy","/api/policy/packs","401"
"ReverseProxy","/api/risk","http://policy-engine.stella-ops.local/api/risk","/api/risk/events","401"
"ReverseProxy","/api/analytics","http://platform.stella-ops.local/api/analytics","/api/analytics/backlog","401"
"ReverseProxy","/api/release-orchestrator","http://orchestrator.stella-ops.local/api/release-orchestrator","/api/release-orchestrator/releases","200"
"ReverseProxy","/api/releases","http://orchestrator.stella-ops.local/api/releases",,
"ReverseProxy","/api/approvals","http://orchestrator.stella-ops.local/api/approvals",,
"ReverseProxy","/api/gate","http://policy-gateway.stella-ops.local/api/gate",,
"ReverseProxy","/api/risk-budget","http://policy-engine.stella-ops.local/api/risk-budget",,
"ReverseProxy","/api/fix-verification","http://scanner.stella-ops.local/api/fix-verification",,
"ReverseProxy","/api/compare","http://sbomservice.stella-ops.local/api/compare",,
"ReverseProxy","/api/change-traces","http://sbomservice.stella-ops.local/api/change-traces",,
"ReverseProxy","/api/exceptions","http://policy-gateway.stella-ops.local/api/exceptions",,
"ReverseProxy","/api/verdicts","https://evidencelocker.stella-ops.local/api/verdicts",,
"ReverseProxy","/api/orchestrator","http://orchestrator.stella-ops.local/api/orchestrator",,
"ReverseProxy","/api/v1/gateway/rate-limits","http://platform.stella-ops.local/api/v1/gateway/rate-limits","/api/v1/gateway/rate-limits","401"
"ReverseProxy","/api/sbomservice","http://sbomservice.stella-ops.local/api/sbomservice",,
"ReverseProxy","/api/vuln-explorer","http://vulnexplorer.stella-ops.local/api/vuln-explorer",,
"ReverseProxy","/api/vex","https://vexhub.stella-ops.local/api/vex",,
"ReverseProxy","/api/admin","http://platform.stella-ops.local/api/admin","/api/admin/plans","404"
"ReverseProxy","/api/scheduler","http://scheduler.stella-ops.local/api/scheduler",,
"ReverseProxy","/api/v1/doctor/scheduler","http://doctor-scheduler.stella-ops.local/api/v1/doctor/scheduler","/api/v1/doctor/scheduler/trends","200"
"ReverseProxy","/api/doctor","http://doctor.stella-ops.local/api/doctor",,
"ReverseProxy","/api","http://platform.stella-ops.local/api","/api/v2/ack","404"
"ReverseProxy","/platform","http://platform.stella-ops.local/platform","/platform/envsettings/db","401"
"ReverseProxy","/connect","https://authority.stella-ops.local/connect","/","200"
"ReverseProxy","/.well-known","https://authority.stella-ops.local/well-known",,
"ReverseProxy","/jwks","https://authority.stella-ops.local/jwks","/","200"
"ReverseProxy","/authority","https://authority.stella-ops.local/authority","/authority/audit/airgap","401"
"ReverseProxy","/console","https://authority.stella-ops.local/console","/console/vex","404"
"ReverseProxy","/rekor","http://rekor.stella-ops.local:3322",,
"ReverseProxy","/envsettings.json","http://platform.stella-ops.local/platform/envsettings.json","/","200"
"ReverseProxy","/gateway","http://gateway.stella-ops.local",,
"ReverseProxy","/scanner","http://scanner.stella-ops.local",,
"ReverseProxy","/policyGateway","http://policy-gateway.stella-ops.local",,
"ReverseProxy","/policyEngine","http://policy-engine.stella-ops.local",,
"ReverseProxy","/concelier","http://concelier.stella-ops.local","/concelier/observations","404"
"ReverseProxy","/attestor","http://attestor.stella-ops.local",,
"ReverseProxy","/notify","http://notify.stella-ops.local",,
"ReverseProxy","/notifier","http://notifier.stella-ops.local",,
"ReverseProxy","/scheduler","http://scheduler.stella-ops.local",,
"ReverseProxy","/signals","http://signals.stella-ops.local","/signals/ping","404"
"ReverseProxy","/excititor","http://excititor.stella-ops.local","/excititor/status","404"
"ReverseProxy","/findingsLedger","http://findings.stella-ops.local",,
"ReverseProxy","/vexhub","https://vexhub.stella-ops.local",,
"ReverseProxy","/vexlens","http://vexlens.stella-ops.local",,
"ReverseProxy","/orchestrator","http://orchestrator.stella-ops.local",,
"ReverseProxy","/taskrunner","http://taskrunner.stella-ops.local",,
"ReverseProxy","/cartographer","http://cartographer.stella-ops.local",,
"ReverseProxy","/reachgraph","http://reachgraph.stella-ops.local",,
"ReverseProxy","/doctor","http://doctor.stella-ops.local",,
"ReverseProxy","/integrations","http://integrations.stella-ops.local",,
"ReverseProxy","/replay","http://replay.stella-ops.local",,
"ReverseProxy","/exportcenter","https://exportcenter.stella-ops.local",,
"ReverseProxy","/evidencelocker","https://evidencelocker.stella-ops.local",,
"ReverseProxy","/signer","http://signer.stella-ops.local",,
"ReverseProxy","/binaryindex","http://binaryindex.stella-ops.local",,
"ReverseProxy","/riskengine","http://riskengine.stella-ops.local",,
"ReverseProxy","/vulnexplorer","http://vulnexplorer.stella-ops.local",,
"ReverseProxy","/sbomservice","http://sbomservice.stella-ops.local",,
"ReverseProxy","/advisoryai","http://advisoryai.stella-ops.local",,
"ReverseProxy","/unknowns","http://unknowns.stella-ops.local",,
"ReverseProxy","/timelineindexer","http://timelineindexer.stella-ops.local",,
"ReverseProxy","/opsmemory","http://opsmemory.stella-ops.local",,
"ReverseProxy","/issuerdirectory","http://issuerdirectory.stella-ops.local",,
"ReverseProxy","/symbols","http://symbols.stella-ops.local",,
"ReverseProxy","/packsregistry","http://packsregistry.stella-ops.local",,
"ReverseProxy","/registryTokenservice","http://registry-token.stella-ops.local",,
"ReverseProxy","/airgapController","http://airgap-controller.stella-ops.local",,
"ReverseProxy","/airgapTime","http://airgap-time.stella-ops.local",,
"ReverseProxy","/smremote","http://smremote.stella-ops.local",,
1 RouteType RoutePath RouteTarget SelectedOpenApiPath StatusCode
2 ReverseProxy /api/v1/release-orchestrator http://orchestrator.stella-ops.local/api/v1/release-orchestrator /api/v1/release-orchestrator/releases 200
3 ReverseProxy /api/v1/vex https://vexhub.stella-ops.local/api/v1/vex /api/v1/vex/index 200
4 ReverseProxy /api/v1/vexlens http://vexlens.stella-ops.local/api/v1/vexlens /api/v1/vexlens/stats 200
5 ReverseProxy /api/v1/notify http://notify.stella-ops.local/api/v1/notify /api/v1/notify/audit 400
6 ReverseProxy /api/v1/notifier http://notifier.stella-ops.local/api/v1/notifier
7 ReverseProxy /api/v1/concelier http://concelier.stella-ops.local/api/v1/concelier /api/v1/concelier/bundles 200
8 ReverseProxy /api/v1/platform http://platform.stella-ops.local/api/v1/platform /api/v1/platform/search 401
9 ReverseProxy /api/v1/scanner http://scanner.stella-ops.local/api/v1/scanner
10 ReverseProxy /api/v1/findings http://findings.stella-ops.local/api/v1/findings /api/v1/findings/summaries 401
11 ReverseProxy /api/v1/integrations http://integrations.stella-ops.local/api/v1/integrations /api/v1/integrations 200
12 ReverseProxy /api/v1/policy http://policy-gateway.stella-ops.local/api/v1/policy /api/v1/policy/schema 404
13 ReverseProxy /api/v1/reachability http://reachgraph.stella-ops.local/api/v1/reachability
14 ReverseProxy /api/v1/attestor http://attestor.stella-ops.local/api/v1/attestor /api/v1/attestor/policies 404
15 ReverseProxy /api/v1/attestations http://attestor.stella-ops.local/api/v1/attestations /api/v1/attestations 401
16 ReverseProxy /api/v1/sbom http://sbomservice.stella-ops.local/api/v1/sbom /api/v1/sbom/hot-lookup/components 404
17 ReverseProxy /api/v1/signals http://signals.stella-ops.local/api/v1/signals /api/v1/signals/hot-symbols 404
18 ReverseProxy /api/v1/orchestrator http://orchestrator.stella-ops.local/api/v1/orchestrator /api/v1/orchestrator/jobs 400
19 ReverseProxy /api/v1/authority/quotas http://platform.stella-ops.local/api/v1/authority/quotas /api/v1/authority/quotas 401
20 ReverseProxy /api/v1/authority https://authority.stella-ops.local/api/v1/authority /api/v1/authority/quotas 401
21 ReverseProxy /api/v1/trust https://authority.stella-ops.local/api/v1/trust
22 ReverseProxy /api/v1/evidence https://evidencelocker.stella-ops.local/api/v1/evidence /api/v1/evidence 401
23 ReverseProxy /api/v1/proofs https://evidencelocker.stella-ops.local/api/v1/proofs /api/v1/proofs/id/{proofId} 401
24 ReverseProxy /api/v1/timeline http://timelineindexer.stella-ops.local/api/v1/timeline /api/v1/timeline 401
25 ReverseProxy /api/v1/advisory-ai/adapters http://advisoryai.stella-ops.local/v1/advisory-ai/adapters
26 ReverseProxy /api/v1/advisory-ai http://advisoryai.stella-ops.local/api/v1/advisory-ai
27 ReverseProxy /api/v1/advisory http://advisoryai.stella-ops.local/api/v1/advisory /api/v1/advisory-sources 404
28 ReverseProxy /api/v1/vulnerabilities http://scanner.stella-ops.local/api/v1/vulnerabilities
29 ReverseProxy /api/v1/watchlist http://scanner.stella-ops.local/api/v1/watchlist /api/v1/watchlist 404
30 ReverseProxy /api/v1/resolve http://binaryindex.stella-ops.local/api/v1/resolve
31 ReverseProxy /api/v1/ops/binaryindex http://binaryindex.stella-ops.local/api/v1/ops/binaryindex /api/v1/ops/binaryindex/cache 200
32 ReverseProxy /api/v1/verdicts https://evidencelocker.stella-ops.local/api/v1/verdicts /api/v1/verdicts/{verdictId} 401
33 ReverseProxy /api/v1/lineage http://sbomservice.stella-ops.local/api/v1/lineage /api/v1/lineage/diff 400
34 ReverseProxy /api/v1/export https://exportcenter.stella-ops.local/api/v1/export /api/v1/export/jobs 401
35 ReverseProxy /api/v1/triage http://scanner.stella-ops.local/api/v1/triage /api/v1/triage/inbox 400
36 ReverseProxy /api/v1/governance http://policy-gateway.stella-ops.local/api/v1/governance /api/v1/governance/audit/events 400
37 ReverseProxy /api/v1/determinization http://policy-engine.stella-ops.local/api/v1/determinization
38 ReverseProxy /api/v1/opsmemory http://opsmemory.stella-ops.local/api/v1/opsmemory /api/v1/opsmemory/stats 400
39 ReverseProxy /api/v1/secrets http://scanner.stella-ops.local/api/v1/secrets /api/v1/secrets/config/rules/categories 200
40 ReverseProxy /api/v1/sources http://sbomservice.stella-ops.local/api/v1/sources
41 ReverseProxy /api/v1/workflows http://orchestrator.stella-ops.local/api/v1/workflows
42 ReverseProxy /api/v1/witnesses http://attestor.stella-ops.local/api/v1/witnesses /api/v1/witnesses 404
43 ReverseProxy /v1/evidence-packs https://evidencelocker.stella-ops.local/v1/evidence-packs /v1/evidence-packs 401
44 ReverseProxy /v1/runs http://orchestrator.stella-ops.local/v1/runs /v1/runs/{id} 404
45 ReverseProxy /v1/advisory-ai/adapters http://advisoryai.stella-ops.local/v1/advisory-ai/adapters / 200
46 ReverseProxy /v1/advisory-ai http://advisoryai.stella-ops.local/v1/advisory-ai /v1/advisory-ai/consent 200
47 ReverseProxy /v1/audit-bundles https://exportcenter.stella-ops.local/v1/audit-bundles /v1/audit-bundles 200
48 ReverseProxy /policy http://policy-gateway.stella-ops.local /policy/snapshots 404
49 ReverseProxy /api/cvss http://policy-gateway.stella-ops.local/api/cvss /api/cvss/policies 401
50 ReverseProxy /api/policy http://policy-gateway.stella-ops.local/api/policy /api/policy/packs 401
51 ReverseProxy /api/risk http://policy-engine.stella-ops.local/api/risk /api/risk/events 401
52 ReverseProxy /api/analytics http://platform.stella-ops.local/api/analytics /api/analytics/backlog 401
53 ReverseProxy /api/release-orchestrator http://orchestrator.stella-ops.local/api/release-orchestrator /api/release-orchestrator/releases 200
54 ReverseProxy /api/releases http://orchestrator.stella-ops.local/api/releases
55 ReverseProxy /api/approvals http://orchestrator.stella-ops.local/api/approvals
56 ReverseProxy /api/gate http://policy-gateway.stella-ops.local/api/gate
57 ReverseProxy /api/risk-budget http://policy-engine.stella-ops.local/api/risk-budget
58 ReverseProxy /api/fix-verification http://scanner.stella-ops.local/api/fix-verification
59 ReverseProxy /api/compare http://sbomservice.stella-ops.local/api/compare
60 ReverseProxy /api/change-traces http://sbomservice.stella-ops.local/api/change-traces
61 ReverseProxy /api/exceptions http://policy-gateway.stella-ops.local/api/exceptions
62 ReverseProxy /api/verdicts https://evidencelocker.stella-ops.local/api/verdicts
63 ReverseProxy /api/orchestrator http://orchestrator.stella-ops.local/api/orchestrator
64 ReverseProxy /api/v1/gateway/rate-limits http://platform.stella-ops.local/api/v1/gateway/rate-limits /api/v1/gateway/rate-limits 401
65 ReverseProxy /api/sbomservice http://sbomservice.stella-ops.local/api/sbomservice
66 ReverseProxy /api/vuln-explorer http://vulnexplorer.stella-ops.local/api/vuln-explorer
67 ReverseProxy /api/vex https://vexhub.stella-ops.local/api/vex
68 ReverseProxy /api/admin http://platform.stella-ops.local/api/admin /api/admin/plans 404
69 ReverseProxy /api/scheduler http://scheduler.stella-ops.local/api/scheduler
70 ReverseProxy /api/v1/doctor/scheduler http://doctor-scheduler.stella-ops.local/api/v1/doctor/scheduler /api/v1/doctor/scheduler/trends 200
71 ReverseProxy /api/doctor http://doctor.stella-ops.local/api/doctor
72 ReverseProxy /api http://platform.stella-ops.local/api /api/v2/ack 404
73 ReverseProxy /platform http://platform.stella-ops.local/platform /platform/envsettings/db 401
74 ReverseProxy /connect https://authority.stella-ops.local/connect / 200
75 ReverseProxy /.well-known https://authority.stella-ops.local/well-known
76 ReverseProxy /jwks https://authority.stella-ops.local/jwks / 200
77 ReverseProxy /authority https://authority.stella-ops.local/authority /authority/audit/airgap 401
78 ReverseProxy /console https://authority.stella-ops.local/console /console/vex 404
79 ReverseProxy /rekor http://rekor.stella-ops.local:3322
80 ReverseProxy /envsettings.json http://platform.stella-ops.local/platform/envsettings.json / 200
81 ReverseProxy /gateway http://gateway.stella-ops.local
82 ReverseProxy /scanner http://scanner.stella-ops.local
83 ReverseProxy /policyGateway http://policy-gateway.stella-ops.local
84 ReverseProxy /policyEngine http://policy-engine.stella-ops.local
85 ReverseProxy /concelier http://concelier.stella-ops.local /concelier/observations 404
86 ReverseProxy /attestor http://attestor.stella-ops.local
87 ReverseProxy /notify http://notify.stella-ops.local
88 ReverseProxy /notifier http://notifier.stella-ops.local
89 ReverseProxy /scheduler http://scheduler.stella-ops.local
90 ReverseProxy /signals http://signals.stella-ops.local /signals/ping 404
91 ReverseProxy /excititor http://excititor.stella-ops.local /excititor/status 404
92 ReverseProxy /findingsLedger http://findings.stella-ops.local
93 ReverseProxy /vexhub https://vexhub.stella-ops.local
94 ReverseProxy /vexlens http://vexlens.stella-ops.local
95 ReverseProxy /orchestrator http://orchestrator.stella-ops.local
96 ReverseProxy /taskrunner http://taskrunner.stella-ops.local
97 ReverseProxy /cartographer http://cartographer.stella-ops.local
98 ReverseProxy /reachgraph http://reachgraph.stella-ops.local
99 ReverseProxy /doctor http://doctor.stella-ops.local
100 ReverseProxy /integrations http://integrations.stella-ops.local
101 ReverseProxy /replay http://replay.stella-ops.local
102 ReverseProxy /exportcenter https://exportcenter.stella-ops.local
103 ReverseProxy /evidencelocker https://evidencelocker.stella-ops.local
104 ReverseProxy /signer http://signer.stella-ops.local
105 ReverseProxy /binaryindex http://binaryindex.stella-ops.local
106 ReverseProxy /riskengine http://riskengine.stella-ops.local
107 ReverseProxy /vulnexplorer http://vulnexplorer.stella-ops.local
108 ReverseProxy /sbomservice http://sbomservice.stella-ops.local
109 ReverseProxy /advisoryai http://advisoryai.stella-ops.local
110 ReverseProxy /unknowns http://unknowns.stella-ops.local
111 ReverseProxy /timelineindexer http://timelineindexer.stella-ops.local
112 ReverseProxy /opsmemory http://opsmemory.stella-ops.local
113 ReverseProxy /issuerdirectory http://issuerdirectory.stella-ops.local
114 ReverseProxy /symbols http://symbols.stella-ops.local
115 ReverseProxy /packsregistry http://packsregistry.stella-ops.local
116 ReverseProxy /registryTokenservice http://registry-token.stella-ops.local
117 ReverseProxy /airgapController http://airgap-controller.stella-ops.local
118 ReverseProxy /airgapTime http://airgap-time.stella-ops.local
119 ReverseProxy /smremote http://smremote.stella-ops.local

View File

@@ -0,0 +1,2 @@
"RouteType","RoutePath","RouteTarget","SelectedOpenApiPath","StatusCode"
"StaticFiles","/","/app/wwwroot","/jwks","401"
1 RouteType RoutePath RouteTarget SelectedOpenApiPath StatusCode
2 StaticFiles / /app/wwwroot /jwks 401

View File

@@ -0,0 +1,2 @@
"RouteType","RoutePath","RouteTarget","SelectedOpenApiPath","StatusCode"
"StaticFiles","/","/app/wwwroot","/","200"
1 RouteType RoutePath RouteTarget SelectedOpenApiPath StatusCode
2 StaticFiles / /app/wwwroot / 200

View File

@@ -0,0 +1,33 @@
{
"generatedUtc": "2026-02-22T15:58:47.9702451Z",
"mode": "microservice",
"targets": [
{
"url": "https://127.1.0.1/openapi.json",
"samples": 15,
"p50Ms": 54.8,
"p95Ms": 69.98,
"minMs": 45.37,
"maxMs": 86.82,
"statusCodes": "200=15"
},
{
"url": "https://127.1.0.1/api/v1/timeline/events?limit=1",
"samples": 15,
"p50Ms": 18.39,
"p95Ms": 33.66,
"minMs": 17.16,
"maxMs": 41.44,
"statusCodes": "401=15"
},
{
"url": "https://127.1.0.1/api/v1/advisory-ai/adapters/llm/providers",
"samples": 15,
"p50Ms": 185.37,
"p95Ms": 189.69,
"minMs": 84.44,
"maxMs": 189.8,
"statusCodes": "403=15"
}
]
}

View File

@@ -0,0 +1,40 @@
{
"generatedUtc": "2026-02-22T15:58:52.2066363Z",
"baseline": "reverseproxy",
"candidate": "microservice",
"deltas": [
{
"url": "https://127.1.0.1/openapi.json",
"reverse_p50_ms": 71.57,
"micro_p50_ms": 54.8,
"delta_p50_ms": -16.77,
"reverse_p95_ms": 85.53,
"micro_p95_ms": 69.98,
"delta_p95_ms": -15.55,
"reverse_status_codes": "200=15",
"micro_status_codes": "200=15"
},
{
"url": "https://127.1.0.1/api/v1/timeline/events?limit=1",
"reverse_p50_ms": 16.51,
"micro_p50_ms": 18.39,
"delta_p50_ms": 1.88,
"reverse_p95_ms": 18.67,
"micro_p95_ms": 33.66,
"delta_p95_ms": 14.99,
"reverse_status_codes": "401=15",
"micro_status_codes": "401=15"
},
{
"url": "https://127.1.0.1/api/v1/advisory-ai/adapters/llm/providers",
"reverse_p50_ms": 16.03,
"micro_p50_ms": 185.37,
"delta_p50_ms": 169.34,
"reverse_p95_ms": 17.49,
"micro_p95_ms": 189.69,
"delta_p95_ms": 172.20,
"reverse_status_codes": "403=15",
"micro_status_codes": "403=15"
}
]
}

View File

@@ -0,0 +1,33 @@
{
"generatedUtc": "2026-02-22T15:56:44.2272953Z",
"mode": "reverseproxy",
"targets": [
{
"url": "https://127.1.0.1/openapi.json",
"samples": 15,
"p50Ms": 71.57,
"p95Ms": 85.53,
"minMs": 63,
"maxMs": 88.26,
"statusCodes": "200=15"
},
{
"url": "https://127.1.0.1/api/v1/timeline/events?limit=1",
"samples": 15,
"p50Ms": 16.51,
"p95Ms": 18.67,
"minMs": 14.32,
"maxMs": 24.53,
"statusCodes": "401=15"
},
{
"url": "https://127.1.0.1/api/v1/advisory-ai/adapters/llm/providers",
"samples": 15,
"p50Ms": 16.03,
"p95Ms": 17.49,
"minMs": 13.59,
"maxMs": 98.31,
"statusCodes": "403=15"
}
]
}

View File

@@ -0,0 +1,611 @@
-- Authority Schema: Consolidated Initial Schema
-- Consolidated from migrations 001-005 (pre_1.0 archived)
-- Creates the complete authority schema for IAM, tenants, users, tokens, RLS, and audit
-- ============================================================================
-- SECTION 1: Schema Creation
-- ============================================================================
CREATE SCHEMA IF NOT EXISTS authority;
CREATE SCHEMA IF NOT EXISTS authority_app;
-- ============================================================================
-- SECTION 2: Helper Functions
-- ============================================================================
-- Function to update updated_at timestamp
CREATE OR REPLACE FUNCTION authority.update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Tenant context helper function for RLS
CREATE OR REPLACE FUNCTION authority_app.require_current_tenant()
RETURNS TEXT
LANGUAGE plpgsql STABLE SECURITY DEFINER
AS $$
DECLARE
v_tenant TEXT;
BEGIN
v_tenant := current_setting('app.tenant_id', true);
IF v_tenant IS NULL OR v_tenant = '' THEN
RAISE EXCEPTION 'app.tenant_id session variable not set'
USING HINT = 'Set via: SELECT set_config(''app.tenant_id'', ''<tenant>'', false)',
ERRCODE = 'P0001';
END IF;
RETURN v_tenant;
END;
$$;
REVOKE ALL ON FUNCTION authority_app.require_current_tenant() FROM PUBLIC;
-- ============================================================================
-- SECTION 3: Core Tables
-- ============================================================================
-- Tenants table (NOT RLS-protected - defines tenant boundaries)
CREATE TABLE IF NOT EXISTS authority.tenants (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
display_name TEXT,
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'suspended', 'deleted')),
settings JSONB NOT NULL DEFAULT '{}',
metadata JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by TEXT,
updated_by TEXT
);
CREATE INDEX idx_tenants_status ON authority.tenants(status);
CREATE INDEX idx_tenants_created_at ON authority.tenants(created_at);
COMMENT ON TABLE authority.tenants IS
'Tenant registry. Not RLS-protected - defines tenant boundaries for the system.';
-- Users table
CREATE TABLE IF NOT EXISTS authority.users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id TEXT NOT NULL REFERENCES authority.tenants(tenant_id),
username TEXT NOT NULL,
email TEXT,
display_name TEXT,
password_hash TEXT,
password_salt TEXT,
enabled BOOLEAN NOT NULL DEFAULT TRUE,
password_algorithm TEXT DEFAULT 'argon2id',
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'inactive', 'locked', 'deleted')),
email_verified BOOLEAN NOT NULL DEFAULT FALSE,
mfa_enabled BOOLEAN NOT NULL DEFAULT FALSE,
mfa_secret TEXT,
mfa_backup_codes TEXT,
failed_login_attempts INT NOT NULL DEFAULT 0,
locked_until TIMESTAMPTZ,
last_login_at TIMESTAMPTZ,
password_changed_at TIMESTAMPTZ,
last_password_change_at TIMESTAMPTZ,
password_expires_at TIMESTAMPTZ,
settings JSONB NOT NULL DEFAULT '{}',
metadata JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by TEXT,
updated_by TEXT,
UNIQUE(tenant_id, username),
UNIQUE(tenant_id, email)
);
CREATE INDEX idx_users_tenant_id ON authority.users(tenant_id);
CREATE INDEX idx_users_status ON authority.users(tenant_id, status);
CREATE INDEX idx_users_email ON authority.users(tenant_id, email);
-- Roles table
CREATE TABLE IF NOT EXISTS authority.roles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id TEXT NOT NULL REFERENCES authority.tenants(tenant_id),
name TEXT NOT NULL,
display_name TEXT,
description TEXT,
is_system BOOLEAN NOT NULL DEFAULT FALSE,
metadata JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(tenant_id, name)
);
CREATE INDEX idx_roles_tenant_id ON authority.roles(tenant_id);
-- Permissions table
CREATE TABLE IF NOT EXISTS authority.permissions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id TEXT NOT NULL REFERENCES authority.tenants(tenant_id),
name TEXT NOT NULL,
resource TEXT NOT NULL,
action TEXT NOT NULL,
description TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(tenant_id, name)
);
CREATE INDEX idx_permissions_tenant_id ON authority.permissions(tenant_id);
CREATE INDEX idx_permissions_resource ON authority.permissions(tenant_id, resource);
-- Role-Permission assignments
CREATE TABLE IF NOT EXISTS authority.role_permissions (
role_id UUID NOT NULL REFERENCES authority.roles(id) ON DELETE CASCADE,
permission_id UUID NOT NULL REFERENCES authority.permissions(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (role_id, permission_id)
);
-- User-Role assignments
CREATE TABLE IF NOT EXISTS authority.user_roles (
user_id UUID NOT NULL REFERENCES authority.users(id) ON DELETE CASCADE,
role_id UUID NOT NULL REFERENCES authority.roles(id) ON DELETE CASCADE,
granted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
granted_by TEXT,
expires_at TIMESTAMPTZ,
PRIMARY KEY (user_id, role_id)
);
-- API Keys table
CREATE TABLE IF NOT EXISTS authority.api_keys (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id TEXT NOT NULL REFERENCES authority.tenants(tenant_id),
user_id UUID REFERENCES authority.users(id) ON DELETE CASCADE,
name TEXT NOT NULL,
key_hash TEXT NOT NULL,
key_prefix TEXT NOT NULL,
scopes TEXT[] NOT NULL DEFAULT '{}',
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'revoked', 'expired')),
last_used_at TIMESTAMPTZ,
expires_at TIMESTAMPTZ,
metadata JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
revoked_at TIMESTAMPTZ,
revoked_by TEXT
);
CREATE INDEX idx_api_keys_tenant_id ON authority.api_keys(tenant_id);
CREATE INDEX idx_api_keys_key_prefix ON authority.api_keys(key_prefix);
CREATE INDEX idx_api_keys_user_id ON authority.api_keys(user_id);
CREATE INDEX idx_api_keys_status ON authority.api_keys(tenant_id, status);
-- Tokens table (access tokens)
CREATE TABLE IF NOT EXISTS authority.tokens (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id TEXT NOT NULL REFERENCES authority.tenants(tenant_id),
user_id UUID REFERENCES authority.users(id) ON DELETE CASCADE,
token_hash TEXT NOT NULL UNIQUE,
token_type TEXT NOT NULL DEFAULT 'access' CHECK (token_type IN ('access', 'refresh', 'api')),
scopes TEXT[] NOT NULL DEFAULT '{}',
client_id TEXT,
issued_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
expires_at TIMESTAMPTZ NOT NULL,
revoked_at TIMESTAMPTZ,
revoked_by TEXT,
metadata JSONB NOT NULL DEFAULT '{}'
);
CREATE INDEX idx_tokens_tenant_id ON authority.tokens(tenant_id);
CREATE INDEX idx_tokens_user_id ON authority.tokens(user_id);
CREATE INDEX idx_tokens_expires_at ON authority.tokens(expires_at);
CREATE INDEX idx_tokens_token_hash ON authority.tokens(token_hash);
-- Refresh Tokens table
CREATE TABLE IF NOT EXISTS authority.refresh_tokens (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id TEXT NOT NULL REFERENCES authority.tenants(tenant_id),
user_id UUID NOT NULL REFERENCES authority.users(id) ON DELETE CASCADE,
token_hash TEXT NOT NULL UNIQUE,
access_token_id UUID REFERENCES authority.tokens(id) ON DELETE SET NULL,
client_id TEXT,
issued_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
expires_at TIMESTAMPTZ NOT NULL,
revoked_at TIMESTAMPTZ,
revoked_by TEXT,
replaced_by UUID,
metadata JSONB NOT NULL DEFAULT '{}'
);
CREATE INDEX idx_refresh_tokens_tenant_id ON authority.refresh_tokens(tenant_id);
CREATE INDEX idx_refresh_tokens_user_id ON authority.refresh_tokens(user_id);
CREATE INDEX idx_refresh_tokens_expires_at ON authority.refresh_tokens(expires_at);
-- Sessions table
CREATE TABLE IF NOT EXISTS authority.sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id TEXT NOT NULL REFERENCES authority.tenants(tenant_id),
user_id UUID NOT NULL REFERENCES authority.users(id) ON DELETE CASCADE,
session_token_hash TEXT NOT NULL UNIQUE,
ip_address TEXT,
user_agent TEXT,
started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
last_activity_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
expires_at TIMESTAMPTZ NOT NULL,
ended_at TIMESTAMPTZ,
end_reason TEXT,
metadata JSONB NOT NULL DEFAULT '{}'
);
CREATE INDEX idx_sessions_tenant_id ON authority.sessions(tenant_id);
CREATE INDEX idx_sessions_user_id ON authority.sessions(user_id);
CREATE INDEX idx_sessions_expires_at ON authority.sessions(expires_at);
-- Audit log table
CREATE TABLE IF NOT EXISTS authority.audit (
id BIGSERIAL PRIMARY KEY,
tenant_id TEXT NOT NULL,
user_id UUID,
action TEXT NOT NULL,
resource_type TEXT NOT NULL,
resource_id TEXT,
old_value JSONB,
new_value JSONB,
ip_address TEXT,
user_agent TEXT,
correlation_id TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_audit_tenant_id ON authority.audit(tenant_id);
CREATE INDEX idx_audit_user_id ON authority.audit(user_id);
CREATE INDEX idx_audit_action ON authority.audit(action);
CREATE INDEX idx_audit_resource ON authority.audit(resource_type, resource_id);
CREATE INDEX idx_audit_created_at ON authority.audit(created_at);
CREATE INDEX idx_audit_correlation_id ON authority.audit(correlation_id);
-- ============================================================================
-- SECTION 4: OIDC and Mongo Store Equivalent Tables
-- ============================================================================
-- Bootstrap invites
CREATE TABLE IF NOT EXISTS authority.bootstrap_invites (
id TEXT PRIMARY KEY,
token TEXT NOT NULL UNIQUE,
type TEXT NOT NULL,
provider TEXT,
target TEXT,
expires_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
issued_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
issued_by TEXT,
reserved_until TIMESTAMPTZ,
reserved_by TEXT,
consumed BOOLEAN NOT NULL DEFAULT FALSE,
status TEXT NOT NULL DEFAULT 'pending',
metadata JSONB NOT NULL DEFAULT '{}'
);
-- Service accounts
CREATE TABLE IF NOT EXISTS authority.service_accounts (
id TEXT PRIMARY KEY,
account_id TEXT NOT NULL UNIQUE,
tenant TEXT NOT NULL,
display_name TEXT NOT NULL,
description TEXT,
enabled BOOLEAN NOT NULL DEFAULT TRUE,
allowed_scopes TEXT[] NOT NULL DEFAULT '{}',
authorized_clients TEXT[] NOT NULL DEFAULT '{}',
attributes JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_service_accounts_tenant ON authority.service_accounts(tenant);
-- Clients
CREATE TABLE IF NOT EXISTS authority.clients (
id TEXT PRIMARY KEY,
client_id TEXT NOT NULL UNIQUE,
client_secret TEXT,
secret_hash TEXT,
display_name TEXT,
description TEXT,
plugin TEXT,
sender_constraint TEXT,
enabled BOOLEAN NOT NULL DEFAULT TRUE,
redirect_uris TEXT[] NOT NULL DEFAULT '{}',
post_logout_redirect_uris TEXT[] NOT NULL DEFAULT '{}',
allowed_scopes TEXT[] NOT NULL DEFAULT '{}',
allowed_grant_types TEXT[] NOT NULL DEFAULT '{}',
require_client_secret BOOLEAN NOT NULL DEFAULT TRUE,
require_pkce BOOLEAN NOT NULL DEFAULT FALSE,
allow_plain_text_pkce BOOLEAN NOT NULL DEFAULT FALSE,
client_type TEXT,
properties JSONB NOT NULL DEFAULT '{}',
certificate_bindings JSONB NOT NULL DEFAULT '[]',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Revocations
CREATE TABLE IF NOT EXISTS authority.revocations (
id TEXT PRIMARY KEY,
category TEXT NOT NULL,
revocation_id TEXT NOT NULL,
subject_id TEXT,
client_id TEXT,
token_id TEXT,
reason TEXT NOT NULL,
reason_description TEXT,
revoked_at TIMESTAMPTZ NOT NULL,
effective_at TIMESTAMPTZ NOT NULL,
expires_at TIMESTAMPTZ,
metadata JSONB NOT NULL DEFAULT '{}'
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_revocations_category_revocation_id
ON authority.revocations(category, revocation_id);
-- Login attempts
CREATE TABLE IF NOT EXISTS authority.login_attempts (
id TEXT PRIMARY KEY,
subject_id TEXT,
client_id TEXT,
event_type TEXT NOT NULL,
outcome TEXT NOT NULL,
reason TEXT,
ip_address TEXT,
user_agent TEXT,
occurred_at TIMESTAMPTZ NOT NULL,
properties JSONB NOT NULL DEFAULT '[]'
);
CREATE INDEX IF NOT EXISTS idx_login_attempts_subject ON authority.login_attempts(subject_id, occurred_at DESC);
-- OIDC tokens
CREATE TABLE IF NOT EXISTS authority.oidc_tokens (
id TEXT PRIMARY KEY,
token_id TEXT NOT NULL UNIQUE,
subject_id TEXT,
client_id TEXT,
token_type TEXT NOT NULL,
reference_id TEXT,
created_at TIMESTAMPTZ NOT NULL,
expires_at TIMESTAMPTZ,
redeemed_at TIMESTAMPTZ,
payload TEXT,
properties JSONB NOT NULL DEFAULT '{}'
);
CREATE INDEX IF NOT EXISTS idx_oidc_tokens_subject ON authority.oidc_tokens(subject_id);
CREATE INDEX IF NOT EXISTS idx_oidc_tokens_client ON authority.oidc_tokens(client_id);
CREATE INDEX IF NOT EXISTS idx_oidc_tokens_reference ON authority.oidc_tokens(reference_id);
-- OIDC refresh tokens
CREATE TABLE IF NOT EXISTS authority.oidc_refresh_tokens (
id TEXT PRIMARY KEY,
token_id TEXT NOT NULL UNIQUE,
subject_id TEXT,
client_id TEXT,
handle TEXT,
created_at TIMESTAMPTZ NOT NULL,
expires_at TIMESTAMPTZ,
consumed_at TIMESTAMPTZ,
payload TEXT
);
CREATE INDEX IF NOT EXISTS idx_oidc_refresh_tokens_subject ON authority.oidc_refresh_tokens(subject_id);
CREATE INDEX IF NOT EXISTS idx_oidc_refresh_tokens_handle ON authority.oidc_refresh_tokens(handle);
-- Airgap audit
CREATE TABLE IF NOT EXISTS authority.airgap_audit (
id TEXT PRIMARY KEY,
event_type TEXT NOT NULL,
operator_id TEXT,
component_id TEXT,
outcome TEXT NOT NULL,
reason TEXT,
occurred_at TIMESTAMPTZ NOT NULL,
properties JSONB NOT NULL DEFAULT '[]'
);
CREATE INDEX IF NOT EXISTS idx_airgap_audit_occurred_at ON authority.airgap_audit(occurred_at DESC);
-- Revocation export state (singleton row with optimistic concurrency)
CREATE TABLE IF NOT EXISTS authority.revocation_export_state (
id INT PRIMARY KEY DEFAULT 1,
sequence BIGINT NOT NULL DEFAULT 0,
bundle_id TEXT,
issued_at TIMESTAMPTZ
);
-- Offline Kit Audit
CREATE TABLE IF NOT EXISTS authority.offline_kit_audit (
event_id UUID PRIMARY KEY,
tenant_id TEXT NOT NULL,
event_type TEXT NOT NULL,
timestamp TIMESTAMPTZ NOT NULL,
actor TEXT NOT NULL,
details JSONB NOT NULL,
result TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_offline_kit_audit_ts ON authority.offline_kit_audit(timestamp DESC);
CREATE INDEX IF NOT EXISTS idx_offline_kit_audit_type ON authority.offline_kit_audit(event_type);
CREATE INDEX IF NOT EXISTS idx_offline_kit_audit_tenant_ts ON authority.offline_kit_audit(tenant_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS idx_offline_kit_audit_result ON authority.offline_kit_audit(tenant_id, result, timestamp DESC);
-- Verdict manifests table
CREATE TABLE IF NOT EXISTS authority.verdict_manifests (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
manifest_id TEXT NOT NULL,
tenant TEXT NOT NULL,
asset_digest TEXT NOT NULL,
vulnerability_id TEXT NOT NULL,
inputs_json JSONB NOT NULL,
status TEXT NOT NULL CHECK (status IN ('affected', 'not_affected', 'fixed', 'under_investigation')),
confidence DOUBLE PRECISION NOT NULL CHECK (confidence >= 0 AND confidence <= 1),
result_json JSONB NOT NULL,
policy_hash TEXT NOT NULL,
lattice_version TEXT NOT NULL,
evaluated_at TIMESTAMPTZ NOT NULL,
manifest_digest TEXT NOT NULL,
signature_base64 TEXT,
rekor_log_id TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT uq_verdict_manifest_id UNIQUE (tenant, manifest_id)
);
CREATE INDEX IF NOT EXISTS idx_verdict_asset_vuln
ON authority.verdict_manifests(tenant, asset_digest, vulnerability_id);
CREATE INDEX IF NOT EXISTS idx_verdict_policy
ON authority.verdict_manifests(tenant, policy_hash, lattice_version);
CREATE INDEX IF NOT EXISTS idx_verdict_time
ON authority.verdict_manifests USING BRIN (evaluated_at);
CREATE UNIQUE INDEX IF NOT EXISTS idx_verdict_replay
ON authority.verdict_manifests(tenant, asset_digest, vulnerability_id, policy_hash, lattice_version);
CREATE INDEX IF NOT EXISTS idx_verdict_digest
ON authority.verdict_manifests(manifest_digest);
COMMENT ON TABLE authority.verdict_manifests IS 'VEX verdict manifests for deterministic replay verification';
-- ============================================================================
-- SECTION 5: Triggers
-- ============================================================================
CREATE TRIGGER trg_tenants_updated_at
BEFORE UPDATE ON authority.tenants
FOR EACH ROW EXECUTE FUNCTION authority.update_updated_at();
CREATE TRIGGER trg_users_updated_at
BEFORE UPDATE ON authority.users
FOR EACH ROW EXECUTE FUNCTION authority.update_updated_at();
CREATE TRIGGER trg_roles_updated_at
BEFORE UPDATE ON authority.roles
FOR EACH ROW EXECUTE FUNCTION authority.update_updated_at();
-- ============================================================================
-- SECTION 6: Row-Level Security
-- ============================================================================
-- authority.users
ALTER TABLE authority.users ENABLE ROW LEVEL SECURITY;
ALTER TABLE authority.users FORCE ROW LEVEL SECURITY;
CREATE POLICY users_tenant_isolation ON authority.users
FOR ALL
USING (tenant_id = authority_app.require_current_tenant())
WITH CHECK (tenant_id = authority_app.require_current_tenant());
-- authority.roles
ALTER TABLE authority.roles ENABLE ROW LEVEL SECURITY;
ALTER TABLE authority.roles FORCE ROW LEVEL SECURITY;
CREATE POLICY roles_tenant_isolation ON authority.roles
FOR ALL
USING (tenant_id = authority_app.require_current_tenant())
WITH CHECK (tenant_id = authority_app.require_current_tenant());
-- authority.permissions
ALTER TABLE authority.permissions ENABLE ROW LEVEL SECURITY;
ALTER TABLE authority.permissions FORCE ROW LEVEL SECURITY;
CREATE POLICY permissions_tenant_isolation ON authority.permissions
FOR ALL
USING (tenant_id = authority_app.require_current_tenant())
WITH CHECK (tenant_id = authority_app.require_current_tenant());
-- authority.role_permissions (FK-based, inherits from roles)
ALTER TABLE authority.role_permissions ENABLE ROW LEVEL SECURITY;
ALTER TABLE authority.role_permissions FORCE ROW LEVEL SECURITY;
CREATE POLICY role_permissions_tenant_isolation ON authority.role_permissions
FOR ALL
USING (
role_id IN (
SELECT id FROM authority.roles
WHERE tenant_id = authority_app.require_current_tenant()
)
);
-- authority.user_roles (FK-based, inherits from users)
ALTER TABLE authority.user_roles ENABLE ROW LEVEL SECURITY;
ALTER TABLE authority.user_roles FORCE ROW LEVEL SECURITY;
CREATE POLICY user_roles_tenant_isolation ON authority.user_roles
FOR ALL
USING (
user_id IN (
SELECT id FROM authority.users
WHERE tenant_id = authority_app.require_current_tenant()
)
);
-- authority.api_keys
ALTER TABLE authority.api_keys ENABLE ROW LEVEL SECURITY;
ALTER TABLE authority.api_keys FORCE ROW LEVEL SECURITY;
CREATE POLICY api_keys_tenant_isolation ON authority.api_keys
FOR ALL
USING (tenant_id = authority_app.require_current_tenant())
WITH CHECK (tenant_id = authority_app.require_current_tenant());
-- authority.tokens
ALTER TABLE authority.tokens ENABLE ROW LEVEL SECURITY;
ALTER TABLE authority.tokens FORCE ROW LEVEL SECURITY;
CREATE POLICY tokens_tenant_isolation ON authority.tokens
FOR ALL
USING (tenant_id = authority_app.require_current_tenant())
WITH CHECK (tenant_id = authority_app.require_current_tenant());
-- authority.refresh_tokens
ALTER TABLE authority.refresh_tokens ENABLE ROW LEVEL SECURITY;
ALTER TABLE authority.refresh_tokens FORCE ROW LEVEL SECURITY;
CREATE POLICY refresh_tokens_tenant_isolation ON authority.refresh_tokens
FOR ALL
USING (tenant_id = authority_app.require_current_tenant())
WITH CHECK (tenant_id = authority_app.require_current_tenant());
-- authority.sessions
ALTER TABLE authority.sessions ENABLE ROW LEVEL SECURITY;
ALTER TABLE authority.sessions FORCE ROW LEVEL SECURITY;
CREATE POLICY sessions_tenant_isolation ON authority.sessions
FOR ALL
USING (tenant_id = authority_app.require_current_tenant())
WITH CHECK (tenant_id = authority_app.require_current_tenant());
-- authority.audit
ALTER TABLE authority.audit ENABLE ROW LEVEL SECURITY;
ALTER TABLE authority.audit FORCE ROW LEVEL SECURITY;
CREATE POLICY audit_tenant_isolation ON authority.audit
FOR ALL
USING (tenant_id = authority_app.require_current_tenant())
WITH CHECK (tenant_id = authority_app.require_current_tenant());
-- authority.offline_kit_audit
ALTER TABLE authority.offline_kit_audit ENABLE ROW LEVEL SECURITY;
ALTER TABLE authority.offline_kit_audit FORCE ROW LEVEL SECURITY;
CREATE POLICY offline_kit_audit_tenant_isolation ON authority.offline_kit_audit
FOR ALL
USING (tenant_id = authority_app.require_current_tenant())
WITH CHECK (tenant_id = authority_app.require_current_tenant());
-- authority.verdict_manifests
ALTER TABLE authority.verdict_manifests ENABLE ROW LEVEL SECURITY;
CREATE POLICY verdict_tenant_isolation ON authority.verdict_manifests
USING (tenant = current_setting('app.current_tenant', true))
WITH CHECK (tenant = current_setting('app.current_tenant', true));
-- ============================================================================
-- SECTION 7: Roles and Permissions
-- ============================================================================
DO $$
BEGIN
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'authority_admin') THEN
CREATE ROLE authority_admin WITH NOLOGIN BYPASSRLS;
END IF;
END
$$;
-- Grant permissions (if role exists)
DO $$
BEGIN
IF EXISTS (SELECT FROM pg_roles WHERE rolname = 'stellaops_app') THEN
GRANT SELECT, INSERT, UPDATE, DELETE ON authority.verdict_manifests TO stellaops_app;
GRANT USAGE ON SCHEMA authority TO stellaops_app;
END IF;
END
$$;

View File

@@ -0,0 +1,114 @@
-- Policy exceptions schema bootstrap for compose environments.
-- Ensures exception endpoints can start against a clean database.
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE SCHEMA IF NOT EXISTS policy;
CREATE TABLE IF NOT EXISTS policy.recheck_policies (
policy_id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
name TEXT NOT NULL,
conditions JSONB NOT NULL,
default_action TEXT NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_recheck_policies_tenant
ON policy.recheck_policies (tenant_id, is_active);
CREATE TABLE IF NOT EXISTS policy.exceptions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id TEXT NOT NULL,
name TEXT NOT NULL,
description TEXT,
rule_pattern TEXT,
resource_pattern TEXT,
artifact_pattern TEXT,
project_id TEXT,
reason TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('proposed', 'approved', 'active', 'expired', 'revoked')),
expires_at TIMESTAMPTZ,
approved_by TEXT,
approved_at TIMESTAMPTZ,
revoked_by TEXT,
revoked_at TIMESTAMPTZ,
metadata JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by TEXT,
exception_id TEXT NOT NULL UNIQUE,
version INTEGER NOT NULL DEFAULT 1,
type TEXT NOT NULL DEFAULT 'policy' CHECK (type IN ('vulnerability', 'policy', 'unknown', 'component')),
artifact_digest TEXT,
purl_pattern TEXT,
vulnerability_id TEXT,
policy_rule_id TEXT,
environments TEXT[] NOT NULL DEFAULT '{}',
owner_id TEXT,
requester_id TEXT,
approver_ids TEXT[] NOT NULL DEFAULT '{}',
reason_code TEXT DEFAULT 'other' CHECK (reason_code IN (
'false_positive',
'accepted_risk',
'compensating_control',
'test_only',
'vendor_not_affected',
'scheduled_fix',
'deprecation_in_progress',
'runtime_mitigation',
'network_isolation',
'other'
)),
rationale TEXT,
evidence_refs JSONB NOT NULL DEFAULT '[]',
compensating_controls JSONB NOT NULL DEFAULT '[]',
ticket_ref TEXT,
recheck_policy_id TEXT REFERENCES policy.recheck_policies(policy_id),
last_recheck_result JSONB,
last_recheck_at TIMESTAMPTZ,
UNIQUE (tenant_id, name)
);
CREATE INDEX IF NOT EXISTS idx_exceptions_tenant ON policy.exceptions(tenant_id);
CREATE INDEX IF NOT EXISTS idx_exceptions_status ON policy.exceptions(tenant_id, status);
CREATE INDEX IF NOT EXISTS idx_exceptions_expires ON policy.exceptions(expires_at) WHERE status = 'active';
CREATE INDEX IF NOT EXISTS idx_exceptions_project ON policy.exceptions(tenant_id, project_id);
CREATE INDEX IF NOT EXISTS idx_exceptions_vuln_id ON policy.exceptions(vulnerability_id) WHERE vulnerability_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_exceptions_purl ON policy.exceptions(purl_pattern) WHERE purl_pattern IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_exceptions_artifact ON policy.exceptions(artifact_digest) WHERE artifact_digest IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_exceptions_policy_rule ON policy.exceptions(policy_rule_id) WHERE policy_rule_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_exceptions_owner ON policy.exceptions(owner_id) WHERE owner_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_exceptions_recheck_policy ON policy.exceptions(tenant_id, recheck_policy_id) WHERE recheck_policy_id IS NOT NULL;
CREATE TABLE IF NOT EXISTS policy.exception_events (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
exception_id TEXT NOT NULL REFERENCES policy.exceptions(exception_id) ON DELETE CASCADE,
sequence_number INTEGER NOT NULL,
event_type TEXT NOT NULL CHECK (event_type IN (
'created',
'updated',
'approved',
'activated',
'extended',
'revoked',
'expired',
'evidence_attached',
'compensating_control_added',
'rejected'
)),
actor_id TEXT NOT NULL,
occurred_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
previous_status TEXT,
new_status TEXT NOT NULL,
new_version INTEGER NOT NULL,
description TEXT,
details JSONB NOT NULL DEFAULT '{}',
client_info TEXT,
UNIQUE (exception_id, sequence_number)
);
CREATE INDEX IF NOT EXISTS idx_exception_events_exception ON policy.exception_events(exception_id);
CREATE INDEX IF NOT EXISTS idx_exception_events_time ON policy.exception_events USING BRIN (occurred_at);

View File

@@ -0,0 +1,245 @@
-- Attestor Schema Migration 001: Initial Schema (Compacted)
-- Consolidated from 20251214000001_AddProofChainSchema.sql and 20251216_001_create_rekor_submission_queue.sql
-- for 1.0.0 release
-- Creates the proofchain schema for proof chain persistence and attestor schema for Rekor queue
-- ============================================================================
-- Extensions
-- ============================================================================
CREATE EXTENSION IF NOT EXISTS pgcrypto;
-- ============================================================================
-- Schema Creation
-- ============================================================================
CREATE SCHEMA IF NOT EXISTS proofchain;
CREATE SCHEMA IF NOT EXISTS attestor;
-- ============================================================================
-- Enum Types
-- ============================================================================
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'verification_result' AND typnamespace = 'proofchain'::regnamespace) THEN
CREATE TYPE proofchain.verification_result AS ENUM ('pass', 'fail', 'pending');
END IF;
END $$;
-- ============================================================================
-- ProofChain Schema Tables
-- ============================================================================
-- Trust anchors table (create first - no dependencies)
CREATE TABLE IF NOT EXISTS proofchain.trust_anchors (
anchor_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
purl_pattern TEXT NOT NULL,
allowed_keyids TEXT[] NOT NULL,
allowed_predicate_types TEXT[],
policy_ref TEXT,
policy_version TEXT,
revoked_keys TEXT[] DEFAULT '{}',
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_trust_anchors_pattern ON proofchain.trust_anchors(purl_pattern);
CREATE INDEX IF NOT EXISTS idx_trust_anchors_active ON proofchain.trust_anchors(is_active) WHERE is_active = TRUE;
COMMENT ON TABLE proofchain.trust_anchors IS 'Trust anchor configurations for dependency verification';
COMMENT ON COLUMN proofchain.trust_anchors.purl_pattern IS 'PURL glob pattern (e.g., pkg:npm/*)';
COMMENT ON COLUMN proofchain.trust_anchors.revoked_keys IS 'Key IDs that have been revoked but may appear in old proofs';
-- SBOM entries table
CREATE TABLE IF NOT EXISTS proofchain.sbom_entries (
entry_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
bom_digest VARCHAR(64) NOT NULL,
purl TEXT NOT NULL,
version TEXT,
artifact_digest VARCHAR(64),
trust_anchor_id UUID REFERENCES proofchain.trust_anchors(anchor_id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT uq_sbom_entry UNIQUE (bom_digest, purl, version)
);
CREATE INDEX IF NOT EXISTS idx_sbom_entries_bom_digest ON proofchain.sbom_entries(bom_digest);
CREATE INDEX IF NOT EXISTS idx_sbom_entries_purl ON proofchain.sbom_entries(purl);
CREATE INDEX IF NOT EXISTS idx_sbom_entries_artifact ON proofchain.sbom_entries(artifact_digest);
CREATE INDEX IF NOT EXISTS idx_sbom_entries_anchor ON proofchain.sbom_entries(trust_anchor_id);
COMMENT ON TABLE proofchain.sbom_entries IS 'SBOM component entries with content-addressed identifiers';
COMMENT ON COLUMN proofchain.sbom_entries.bom_digest IS 'SHA-256 hash of the parent SBOM document';
COMMENT ON COLUMN proofchain.sbom_entries.purl IS 'Package URL (PURL) of the component';
COMMENT ON COLUMN proofchain.sbom_entries.artifact_digest IS 'SHA-256 hash of the component artifact if available';
-- DSSE envelopes table
CREATE TABLE IF NOT EXISTS proofchain.dsse_envelopes (
env_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
entry_id UUID NOT NULL REFERENCES proofchain.sbom_entries(entry_id) ON DELETE CASCADE,
predicate_type TEXT NOT NULL,
signer_keyid TEXT NOT NULL,
body_hash VARCHAR(64) NOT NULL,
envelope_blob_ref TEXT NOT NULL,
signed_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT uq_dsse_envelope UNIQUE (entry_id, predicate_type, body_hash)
);
CREATE INDEX IF NOT EXISTS idx_dsse_entry_predicate ON proofchain.dsse_envelopes(entry_id, predicate_type);
CREATE INDEX IF NOT EXISTS idx_dsse_signer ON proofchain.dsse_envelopes(signer_keyid);
CREATE INDEX IF NOT EXISTS idx_dsse_body_hash ON proofchain.dsse_envelopes(body_hash);
COMMENT ON TABLE proofchain.dsse_envelopes IS 'Signed DSSE envelopes for proof chain statements';
COMMENT ON COLUMN proofchain.dsse_envelopes.predicate_type IS 'Predicate type URI (e.g., evidence.stella/v1)';
COMMENT ON COLUMN proofchain.dsse_envelopes.envelope_blob_ref IS 'Reference to blob storage (OCI, S3, file)';
-- Spines table
CREATE TABLE IF NOT EXISTS proofchain.spines (
entry_id UUID PRIMARY KEY REFERENCES proofchain.sbom_entries(entry_id) ON DELETE CASCADE,
bundle_id VARCHAR(64) NOT NULL,
evidence_ids TEXT[] NOT NULL,
reasoning_id VARCHAR(64) NOT NULL,
vex_id VARCHAR(64) NOT NULL,
anchor_id UUID REFERENCES proofchain.trust_anchors(anchor_id) ON DELETE SET NULL,
policy_version TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT uq_spine_bundle UNIQUE (bundle_id)
);
CREATE INDEX IF NOT EXISTS idx_spines_bundle ON proofchain.spines(bundle_id);
CREATE INDEX IF NOT EXISTS idx_spines_anchor ON proofchain.spines(anchor_id);
CREATE INDEX IF NOT EXISTS idx_spines_policy ON proofchain.spines(policy_version);
COMMENT ON TABLE proofchain.spines IS 'Proof spines linking evidence to verdicts via merkle aggregation';
COMMENT ON COLUMN proofchain.spines.bundle_id IS 'ProofBundleID (merkle root of all components)';
COMMENT ON COLUMN proofchain.spines.evidence_ids IS 'Array of EvidenceIDs in sorted order';
-- Rekor entries table
CREATE TABLE IF NOT EXISTS proofchain.rekor_entries (
dsse_sha256 VARCHAR(64) PRIMARY KEY,
log_index BIGINT NOT NULL,
log_id TEXT NOT NULL,
uuid TEXT NOT NULL,
integrated_time BIGINT NOT NULL,
inclusion_proof JSONB NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
env_id UUID REFERENCES proofchain.dsse_envelopes(env_id) ON DELETE SET NULL
);
CREATE INDEX IF NOT EXISTS idx_rekor_log_index ON proofchain.rekor_entries(log_index);
CREATE INDEX IF NOT EXISTS idx_rekor_log_id ON proofchain.rekor_entries(log_id);
CREATE INDEX IF NOT EXISTS idx_rekor_uuid ON proofchain.rekor_entries(uuid);
CREATE INDEX IF NOT EXISTS idx_rekor_env ON proofchain.rekor_entries(env_id);
COMMENT ON TABLE proofchain.rekor_entries IS 'Rekor transparency log entries for verification';
COMMENT ON COLUMN proofchain.rekor_entries.inclusion_proof IS 'Merkle inclusion proof from Rekor';
-- Audit log table
CREATE TABLE IF NOT EXISTS proofchain.audit_log (
log_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
operation TEXT NOT NULL,
entity_type TEXT NOT NULL,
entity_id TEXT NOT NULL,
actor TEXT,
details JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_audit_entity ON proofchain.audit_log(entity_type, entity_id);
CREATE INDEX IF NOT EXISTS idx_audit_created ON proofchain.audit_log(created_at DESC);
COMMENT ON TABLE proofchain.audit_log IS 'Audit log for proof chain operations';
-- ============================================================================
-- Attestor Schema Tables
-- ============================================================================
-- Rekor submission queue table
CREATE TABLE IF NOT EXISTS attestor.rekor_submission_queue (
id UUID PRIMARY KEY,
tenant_id TEXT NOT NULL,
bundle_sha256 TEXT NOT NULL,
dsse_payload BYTEA NOT NULL,
backend TEXT NOT NULL DEFAULT 'primary',
-- Status lifecycle: pending -> submitting -> submitted | retrying -> dead_letter
status TEXT NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending', 'submitting', 'retrying', 'submitted', 'dead_letter')),
attempt_count INTEGER NOT NULL DEFAULT 0,
max_attempts INTEGER NOT NULL DEFAULT 5,
next_retry_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
-- Populated on success
rekor_uuid TEXT,
rekor_index BIGINT,
-- Populated on failure
last_error TEXT
);
COMMENT ON TABLE attestor.rekor_submission_queue IS
'Durable retry queue for Rekor transparency log submissions';
COMMENT ON COLUMN attestor.rekor_submission_queue.status IS
'Submission lifecycle: pending -> submitting -> (submitted | retrying -> dead_letter)';
COMMENT ON COLUMN attestor.rekor_submission_queue.backend IS
'Target Rekor backend (primary or mirror)';
COMMENT ON COLUMN attestor.rekor_submission_queue.dsse_payload IS
'Serialized DSSE envelope to submit';
-- Index for dequeue operations (status + next_retry_at for SKIP LOCKED queries)
CREATE INDEX IF NOT EXISTS idx_rekor_queue_dequeue
ON attestor.rekor_submission_queue (status, next_retry_at)
WHERE status IN ('pending', 'retrying');
-- Index for tenant-scoped queries
CREATE INDEX IF NOT EXISTS idx_rekor_queue_tenant
ON attestor.rekor_submission_queue (tenant_id);
-- Index for bundle lookup (deduplication check)
CREATE INDEX IF NOT EXISTS idx_rekor_queue_bundle
ON attestor.rekor_submission_queue (tenant_id, bundle_sha256);
-- Index for dead letter management
CREATE INDEX IF NOT EXISTS idx_rekor_queue_dead_letter
ON attestor.rekor_submission_queue (status, updated_at)
WHERE status = 'dead_letter';
-- Index for cleanup of completed submissions
CREATE INDEX IF NOT EXISTS idx_rekor_queue_completed
ON attestor.rekor_submission_queue (status, updated_at)
WHERE status = 'submitted';
-- ============================================================================
-- Trigger Functions
-- ============================================================================
CREATE OR REPLACE FUNCTION proofchain.update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Apply updated_at trigger to trust_anchors
DROP TRIGGER IF EXISTS update_trust_anchors_updated_at ON proofchain.trust_anchors;
CREATE TRIGGER update_trust_anchors_updated_at
BEFORE UPDATE ON proofchain.trust_anchors
FOR EACH ROW
EXECUTE FUNCTION proofchain.update_updated_at_column();
-- Apply updated_at trigger to rekor_submission_queue
DROP TRIGGER IF EXISTS update_rekor_queue_updated_at ON attestor.rekor_submission_queue;
CREATE TRIGGER update_rekor_queue_updated_at
BEFORE UPDATE ON attestor.rekor_submission_queue
FOR EACH ROW
EXECUTE FUNCTION proofchain.update_updated_at_column();

View File

@@ -0,0 +1,95 @@
-- -----------------------------------------------------------------------------
-- Migration: 20260129_001_create_identity_watchlist
-- Sprint: SPRINT_0129_001_ATTESTOR_identity_watchlist_alerting
-- Task: WATCH-004
-- Description: Creates identity watchlist and alert deduplication tables.
-- -----------------------------------------------------------------------------
-- Watchlist entries table
CREATE TABLE IF NOT EXISTS attestor.identity_watchlist (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id TEXT NOT NULL,
scope TEXT NOT NULL DEFAULT 'Tenant',
display_name TEXT NOT NULL,
description TEXT,
-- Identity matching fields (at least one required)
issuer TEXT,
subject_alternative_name TEXT,
key_id TEXT,
match_mode TEXT NOT NULL DEFAULT 'Exact',
-- Alert configuration
severity TEXT NOT NULL DEFAULT 'Warning',
enabled BOOLEAN NOT NULL DEFAULT TRUE,
channel_overrides JSONB,
suppress_duplicates_minutes INT NOT NULL DEFAULT 60,
-- Metadata
tags TEXT[],
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by TEXT NOT NULL,
updated_by TEXT NOT NULL,
-- Constraints
CONSTRAINT chk_at_least_one_identity CHECK (
issuer IS NOT NULL OR
subject_alternative_name IS NOT NULL OR
key_id IS NOT NULL
),
CONSTRAINT chk_scope_valid CHECK (scope IN ('Tenant', 'Global', 'System')),
CONSTRAINT chk_match_mode_valid CHECK (match_mode IN ('Exact', 'Prefix', 'Glob', 'Regex')),
CONSTRAINT chk_severity_valid CHECK (severity IN ('Info', 'Warning', 'Critical')),
CONSTRAINT chk_suppress_duplicates_positive CHECK (suppress_duplicates_minutes >= 1)
);
-- Performance indexes for active entry lookup
CREATE INDEX IF NOT EXISTS idx_watchlist_tenant_enabled
ON attestor.identity_watchlist(tenant_id)
WHERE enabled = TRUE;
CREATE INDEX IF NOT EXISTS idx_watchlist_scope_enabled
ON attestor.identity_watchlist(scope)
WHERE enabled = TRUE;
CREATE INDEX IF NOT EXISTS idx_watchlist_issuer
ON attestor.identity_watchlist(issuer)
WHERE enabled = TRUE AND issuer IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_watchlist_san
ON attestor.identity_watchlist(subject_alternative_name)
WHERE enabled = TRUE AND subject_alternative_name IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_watchlist_keyid
ON attestor.identity_watchlist(key_id)
WHERE enabled = TRUE AND key_id IS NOT NULL;
-- Alert deduplication table
CREATE TABLE IF NOT EXISTS attestor.identity_alert_dedup (
watchlist_id UUID NOT NULL,
identity_hash TEXT NOT NULL,
last_alert_at TIMESTAMPTZ NOT NULL,
alert_count INT NOT NULL DEFAULT 0,
PRIMARY KEY (watchlist_id, identity_hash)
);
-- Index for cleanup
CREATE INDEX IF NOT EXISTS idx_alert_dedup_last_alert
ON attestor.identity_alert_dedup(last_alert_at);
-- Comment documentation
COMMENT ON TABLE attestor.identity_watchlist IS
'Watchlist entries for monitoring signing identity appearances in transparency logs.';
COMMENT ON COLUMN attestor.identity_watchlist.scope IS
'Visibility scope: Tenant (owning tenant only), Global (all tenants), System (read-only).';
COMMENT ON COLUMN attestor.identity_watchlist.match_mode IS
'Pattern matching mode: Exact, Prefix, Glob, or Regex.';
COMMENT ON COLUMN attestor.identity_watchlist.suppress_duplicates_minutes IS
'Deduplication window in minutes. Alerts for same identity within window are suppressed.';
COMMENT ON TABLE attestor.identity_alert_dedup IS
'Tracks alert deduplication state to prevent alert storms.';

View File

@@ -0,0 +1,113 @@
-- Attestor Schema Migration 002: Predicate Type Registry
-- Sprint: SPRINT_20260219_010 (PSR-01)
-- Creates discoverable, versioned registry for all predicate types
-- ============================================================================
-- Predicate Type Registry Table
-- ============================================================================
CREATE TABLE IF NOT EXISTS proofchain.predicate_type_registry (
registry_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
predicate_type_uri TEXT NOT NULL,
display_name TEXT NOT NULL,
version TEXT NOT NULL DEFAULT '1.0.0',
category TEXT NOT NULL DEFAULT 'stella-core'
CHECK (category IN ('stella-core', 'stella-proof', 'stella-delta', 'ecosystem', 'intoto', 'custom')),
json_schema JSONB,
description TEXT,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
validation_mode TEXT NOT NULL DEFAULT 'log-only'
CHECK (validation_mode IN ('log-only', 'warn', 'reject')),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT uq_predicate_type_version UNIQUE (predicate_type_uri, version)
);
CREATE INDEX IF NOT EXISTS idx_predicate_registry_uri
ON proofchain.predicate_type_registry(predicate_type_uri);
CREATE INDEX IF NOT EXISTS idx_predicate_registry_category
ON proofchain.predicate_type_registry(category);
CREATE INDEX IF NOT EXISTS idx_predicate_registry_active
ON proofchain.predicate_type_registry(is_active) WHERE is_active = TRUE;
-- Apply updated_at trigger
DROP TRIGGER IF EXISTS update_predicate_registry_updated_at ON proofchain.predicate_type_registry;
CREATE TRIGGER update_predicate_registry_updated_at
BEFORE UPDATE ON proofchain.predicate_type_registry
FOR EACH ROW
EXECUTE FUNCTION proofchain.update_updated_at_column();
COMMENT ON TABLE proofchain.predicate_type_registry IS 'Discoverable registry of all predicate types accepted by the Attestor';
COMMENT ON COLUMN proofchain.predicate_type_registry.predicate_type_uri IS 'Canonical URI for the predicate type (e.g., https://stella-ops.org/predicates/evidence/v1)';
COMMENT ON COLUMN proofchain.predicate_type_registry.validation_mode IS 'How mismatches are handled: log-only (default), warn, or reject';
-- ============================================================================
-- Seed: stella-core predicates
-- ============================================================================
INSERT INTO proofchain.predicate_type_registry (predicate_type_uri, display_name, version, category, description) VALUES
('https://stella-ops.org/predicates/sbom-linkage/v1', 'SBOM Linkage', '1.0.0', 'stella-core', 'Links SBOM components to evidence and proof spines'),
('https://stella-ops.org/predicates/vex-verdict/v1', 'VEX Verdict', '1.0.0', 'stella-core', 'VEX consensus verdict for an artifact+advisory tuple'),
('https://stella-ops.org/predicates/evidence/v1', 'Evidence', '1.0.0', 'stella-core', 'Generic evidence attestation linking scan results to artifacts'),
('https://stella-ops.org/predicates/reasoning/v1', 'Reasoning', '1.0.0', 'stella-core', 'Policy reasoning chain for a release decision'),
('https://stella-ops.org/predicates/proof-spine/v1', 'Proof Spine', '1.0.0', 'stella-core', 'Merkle-aggregated proof spine linking evidence to verdicts'),
('https://stella-ops.org/predicates/reachability-drift/v1', 'Reachability Drift', '1.0.0', 'stella-core', 'Reachability state changes between consecutive scans'),
('https://stella-ops.org/predicates/reachability-subgraph/v1', 'Reachability Subgraph', '1.0.0', 'stella-core', 'Call graph subgraph for a specific vulnerability path'),
('https://stella-ops.org/predicates/delta-verdict/v1', 'Delta Verdict', '1.0.0', 'stella-core', 'Verdict differences between two scan runs'),
('https://stella-ops.org/predicates/policy-decision/v1', 'Policy Decision', '1.0.0', 'stella-core', 'Policy engine evaluation result for a release gate'),
('https://stella-ops.org/predicates/unknowns-budget/v1', 'Unknowns Budget', '1.0.0', 'stella-core', 'Budget check for unknown reachability components'),
('https://stella-ops.org/predicates/ai-code-guard/v1', 'AI Code Guard', '1.0.0', 'stella-core', 'AI-assisted code security analysis results'),
('https://stella-ops.org/predicates/fix-chain/v1', 'Fix Chain', '1.0.0', 'stella-core', 'Linked chain of fix commits from vulnerability to resolution'),
('https://stella-ops.org/attestation/graph-root/v1', 'Graph Root', '1.0.0', 'stella-core', 'Root attestation for a complete call graph')
ON CONFLICT (predicate_type_uri, version) DO NOTHING;
-- ============================================================================
-- Seed: stella-proof predicates (ProofChain)
-- ============================================================================
INSERT INTO proofchain.predicate_type_registry (predicate_type_uri, display_name, version, category, description) VALUES
('https://stella.ops/predicates/path-witness/v1', 'Path Witness', '1.0.0', 'stella-proof', 'Entrypoint-to-sink call path witness with gate detection'),
('https://stella.ops/predicates/runtime-witness/v1', 'Runtime Witness', '1.0.0', 'stella-proof', 'Runtime micro-witness from eBPF/ETW observations'),
('https://stella.ops/predicates/policy-decision@v2', 'Policy Decision v2', '2.0.0', 'stella-proof', 'Enhanced policy decision with reachability context'),
('https://stellaops.dev/predicates/binary-micro-witness@v1', 'Binary Micro-Witness', '1.0.0', 'stella-proof', 'Binary-level micro-witness with build ID correlation'),
('https://stellaops.dev/predicates/binary-fingerprint-evidence@v1', 'Binary Fingerprint', '1.0.0', 'stella-proof', 'Binary fingerprint evidence for patch detection'),
('https://stellaops.io/attestation/budget-check/v1', 'Budget Check', '1.0.0', 'stella-proof', 'Unknowns budget check attestation'),
('https://stellaops.dev/attestation/vex/v1', 'VEX Attestation', '1.0.0', 'stella-proof', 'DSSE-signed VEX statement attestation'),
('https://stellaops.dev/attestations/vex-override/v1', 'VEX Override', '1.0.0', 'stella-proof', 'Manual VEX override decision with justification'),
('https://stellaops.dev/predicates/trust-verdict@v1', 'Trust Verdict', '1.0.0', 'stella-proof', 'Trust lattice verdict combining P/C/R vectors'),
('https://stellaops.io/attestation/v1/signed-exception', 'Signed Exception', '1.0.0', 'stella-proof', 'Manually approved exception with expiry'),
('https://stellaops.dev/attestation/verification-report/v1', 'Verification Report', '1.0.0', 'stella-proof', 'QA verification report attestation')
ON CONFLICT (predicate_type_uri, version) DO NOTHING;
-- ============================================================================
-- Seed: stella-delta predicates
-- ============================================================================
INSERT INTO proofchain.predicate_type_registry (predicate_type_uri, display_name, version, category, description) VALUES
('stella.ops/changetrace@v1', 'Change Trace', '1.0.0', 'stella-delta', 'File-level change trace between SBOM versions'),
('stella.ops/vex-delta@v1', 'VEX Delta', '1.0.0', 'stella-delta', 'VEX statement differences between consecutive ingestions'),
('stella.ops/sbom-delta@v1', 'SBOM Delta', '1.0.0', 'stella-delta', 'Component differences between two SBOM versions'),
('stella.ops/verdict-delta@v1', 'Verdict Delta', '1.0.0', 'stella-delta', 'Verdict changes between policy evaluations'),
('stellaops.binarydiff.v1', 'Binary Diff', '1.0.0', 'stella-delta', 'Binary diff signatures for patch detection')
ON CONFLICT (predicate_type_uri, version) DO NOTHING;
-- ============================================================================
-- Seed: ecosystem predicates
-- ============================================================================
INSERT INTO proofchain.predicate_type_registry (predicate_type_uri, display_name, version, category, description) VALUES
('https://spdx.dev/Document', 'SPDX Document', '2.3.0', 'ecosystem', 'SPDX 2.x document attestation'),
('https://cyclonedx.org/bom', 'CycloneDX BOM', '1.7.0', 'ecosystem', 'CycloneDX BOM attestation'),
('https://slsa.dev/provenance', 'SLSA Provenance', '1.0.0', 'ecosystem', 'SLSA v1.0 build provenance')
ON CONFLICT (predicate_type_uri, version) DO NOTHING;
-- ============================================================================
-- Seed: in-toto standard predicates
-- ============================================================================
INSERT INTO proofchain.predicate_type_registry (predicate_type_uri, display_name, version, category, description) VALUES
('https://in-toto.io/Statement/v1', 'In-Toto Statement', '1.0.0', 'intoto', 'In-toto attestation statement wrapper'),
('https://in-toto.io/Link/v1', 'In-Toto Link', '1.0.0', 'intoto', 'In-toto supply chain link'),
('https://in-toto.io/Layout/v1', 'In-Toto Layout', '1.0.0', 'intoto', 'In-toto supply chain layout')
ON CONFLICT (predicate_type_uri, version) DO NOTHING;

View File

@@ -0,0 +1,42 @@
-- Migration 003: Artifact Canonical Record materialized view
-- Sprint: SPRINT_20260219_009 (CID-04)
-- Purpose: Unified read projection joining sbom_entries + dsse_envelopes + rekor_entries
-- for the Evidence Thread API (GET /api/v1/evidence/thread/{canonical_id}).
-- Materialized view: one row per canonical_id with aggregated attestation evidence.
CREATE MATERIALIZED VIEW IF NOT EXISTS proofchain.artifact_canonical_records AS
SELECT
se.bom_digest AS canonical_id,
'cyclonedx-jcs:1'::text AS format,
se.artifact_digest,
se.purl,
se.created_at,
COALESCE(
jsonb_agg(
DISTINCT jsonb_build_object(
'predicate_type', de.predicate_type,
'dsse_digest', de.body_hash,
'signer_keyid', de.signer_keyid,
'rekor_entry_id', re.uuid,
'rekor_tile', re.log_id,
'signed_at', de.signed_at
)
) FILTER (WHERE de.env_id IS NOT NULL),
'[]'::jsonb
) AS attestations
FROM proofchain.sbom_entries se
LEFT JOIN proofchain.dsse_envelopes de ON de.entry_id = se.entry_id
LEFT JOIN proofchain.rekor_entries re ON re.env_id = de.env_id
GROUP BY se.entry_id, se.bom_digest, se.artifact_digest, se.purl, se.created_at;
-- Unique index for CONCURRENTLY refresh and fast lookup.
CREATE UNIQUE INDEX IF NOT EXISTS idx_acr_canonical_id
ON proofchain.artifact_canonical_records (canonical_id);
-- Index for PURL-based lookup (Evidence Thread by PURL).
CREATE INDEX IF NOT EXISTS idx_acr_purl
ON proofchain.artifact_canonical_records (purl)
WHERE purl IS NOT NULL;
COMMENT ON MATERIALIZED VIEW proofchain.artifact_canonical_records IS
'Unified read projection for the Evidence Thread API. Joins SBOM entries, DSSE envelopes, and Rekor entries into one row per canonical_id. Refresh via REFRESH MATERIALIZED VIEW CONCURRENTLY.';

View File

@@ -0,0 +1,83 @@
-- -----------------------------------------------------------------------------
-- 001_verdict_ledger_initial.sql
-- Sprint: SPRINT_20260118_015_Attestor_verdict_ledger_foundation
-- Task: VL-001 - Create VerdictLedger database schema
-- Description: Append-only verdict ledger with SHA-256 hash chaining
-- -----------------------------------------------------------------------------
-- Create verdict decision enum
DO $$ BEGIN
CREATE TYPE verdict_decision AS ENUM ('unknown', 'approve', 'reject', 'pending');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
-- Create the verdict_ledger table
CREATE TABLE IF NOT EXISTS verdict_ledger (
ledger_id UUID PRIMARY KEY,
bom_ref VARCHAR(2048) NOT NULL,
cyclonedx_serial VARCHAR(512),
rekor_uuid VARCHAR(128),
decision verdict_decision NOT NULL DEFAULT 'unknown',
reason TEXT NOT NULL,
policy_bundle_id VARCHAR(256) NOT NULL,
policy_bundle_hash VARCHAR(64) NOT NULL,
verifier_image_digest VARCHAR(256) NOT NULL,
signer_keyid VARCHAR(512) NOT NULL,
prev_hash VARCHAR(64), -- SHA-256 hex, null for genesis entry
verdict_hash VARCHAR(64) NOT NULL UNIQUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
tenant_id UUID NOT NULL,
-- Constraints
CONSTRAINT verdict_hash_format CHECK (verdict_hash ~ '^[a-f0-9]{64}$'),
CONSTRAINT prev_hash_format CHECK (prev_hash IS NULL OR prev_hash ~ '^[a-f0-9]{64}$'),
CONSTRAINT policy_hash_format CHECK (policy_bundle_hash ~ '^[a-f0-9]{64}$')
);
-- Indexes for common query patterns
CREATE INDEX IF NOT EXISTS idx_verdict_ledger_bom_ref
ON verdict_ledger (bom_ref);
CREATE INDEX IF NOT EXISTS idx_verdict_ledger_rekor_uuid
ON verdict_ledger (rekor_uuid)
WHERE rekor_uuid IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_verdict_ledger_created_at
ON verdict_ledger (created_at DESC);
CREATE INDEX IF NOT EXISTS idx_verdict_ledger_tenant_created
ON verdict_ledger (tenant_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_verdict_ledger_prev_hash
ON verdict_ledger (prev_hash)
WHERE prev_hash IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_verdict_ledger_decision
ON verdict_ledger (decision);
-- Composite index for chain walking
CREATE INDEX IF NOT EXISTS idx_verdict_ledger_chain
ON verdict_ledger (tenant_id, verdict_hash);
-- Comments
COMMENT ON TABLE verdict_ledger IS 'Append-only ledger of release verdicts with SHA-256 hash chaining for cryptographic audit trail';
COMMENT ON COLUMN verdict_ledger.ledger_id IS 'Unique identifier for this ledger entry';
COMMENT ON COLUMN verdict_ledger.bom_ref IS 'Package URL (purl) or container digest reference';
COMMENT ON COLUMN verdict_ledger.cyclonedx_serial IS 'CycloneDX serialNumber URN linking to SBOM';
COMMENT ON COLUMN verdict_ledger.rekor_uuid IS 'Transparency log entry UUID for external verification';
COMMENT ON COLUMN verdict_ledger.decision IS 'The release decision: unknown, approve, reject, or pending';
COMMENT ON COLUMN verdict_ledger.reason IS 'Human-readable explanation for the decision';
COMMENT ON COLUMN verdict_ledger.policy_bundle_id IS 'Reference to the policy configuration used';
COMMENT ON COLUMN verdict_ledger.policy_bundle_hash IS 'SHA-256 hash of the policy bundle for reproducibility';
COMMENT ON COLUMN verdict_ledger.verifier_image_digest IS 'Container digest of the verifier service';
COMMENT ON COLUMN verdict_ledger.signer_keyid IS 'Key ID that signed this verdict';
COMMENT ON COLUMN verdict_ledger.prev_hash IS 'SHA-256 hash of previous entry (null for genesis)';
COMMENT ON COLUMN verdict_ledger.verdict_hash IS 'SHA-256 hash of this entry canonical JSON form';
COMMENT ON COLUMN verdict_ledger.created_at IS 'Timestamp when this verdict was recorded';
COMMENT ON COLUMN verdict_ledger.tenant_id IS 'Tenant identifier for multi-tenancy';
-- Revoke UPDATE and DELETE for application role (append-only enforcement)
-- This should be run after creating the appropriate role
-- REVOKE UPDATE, DELETE ON verdict_ledger FROM stellaops_app;
-- GRANT INSERT, SELECT ON verdict_ledger TO stellaops_app;

View File

@@ -0,0 +1,107 @@
-- Migration: 001_CreateVerdictAttestations
-- Description: Create verdict_attestations table for storing signed policy verdict attestations
-- Author: Evidence Locker Guild
-- Date: 2025-12-23
-- Create schema if not exists
CREATE SCHEMA IF NOT EXISTS evidence_locker;
-- Create verdict_attestations table
CREATE TABLE IF NOT EXISTS evidence_locker.verdict_attestations (
verdict_id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
run_id TEXT NOT NULL,
policy_id TEXT NOT NULL,
policy_version INTEGER NOT NULL,
finding_id TEXT NOT NULL,
verdict_status TEXT NOT NULL CHECK (verdict_status IN ('passed', 'warned', 'blocked', 'quieted', 'ignored')),
verdict_severity TEXT NOT NULL CHECK (verdict_severity IN ('critical', 'high', 'medium', 'low', 'info', 'none')),
verdict_score NUMERIC(5, 2) NOT NULL CHECK (verdict_score >= 0 AND verdict_score <= 100),
evaluated_at TIMESTAMPTZ NOT NULL,
envelope JSONB NOT NULL,
predicate_digest TEXT NOT NULL,
determinism_hash TEXT,
rekor_log_index BIGINT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Create indexes for common query patterns
CREATE INDEX IF NOT EXISTS idx_verdict_attestations_run
ON evidence_locker.verdict_attestations(run_id);
CREATE INDEX IF NOT EXISTS idx_verdict_attestations_finding
ON evidence_locker.verdict_attestations(finding_id);
CREATE INDEX IF NOT EXISTS idx_verdict_attestations_tenant_evaluated
ON evidence_locker.verdict_attestations(tenant_id, evaluated_at DESC);
CREATE INDEX IF NOT EXISTS idx_verdict_attestations_tenant_status
ON evidence_locker.verdict_attestations(tenant_id, verdict_status);
CREATE INDEX IF NOT EXISTS idx_verdict_attestations_tenant_severity
ON evidence_locker.verdict_attestations(tenant_id, verdict_severity);
CREATE INDEX IF NOT EXISTS idx_verdict_attestations_policy
ON evidence_locker.verdict_attestations(policy_id, policy_version);
-- Create GIN index for JSONB envelope queries
CREATE INDEX IF NOT EXISTS idx_verdict_attestations_envelope
ON evidence_locker.verdict_attestations USING gin(envelope);
-- Create function for updating updated_at timestamp
CREATE OR REPLACE FUNCTION evidence_locker.update_verdict_attestations_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Create trigger to auto-update updated_at
CREATE TRIGGER trigger_verdict_attestations_updated_at
BEFORE UPDATE ON evidence_locker.verdict_attestations
FOR EACH ROW
EXECUTE FUNCTION evidence_locker.update_verdict_attestations_updated_at();
-- Create view for verdict summary (without full envelope)
CREATE OR REPLACE VIEW evidence_locker.verdict_attestations_summary AS
SELECT
verdict_id,
tenant_id,
run_id,
policy_id,
policy_version,
finding_id,
verdict_status,
verdict_severity,
verdict_score,
evaluated_at,
predicate_digest,
determinism_hash,
rekor_log_index,
created_at
FROM evidence_locker.verdict_attestations;
-- Grant permissions (adjust as needed)
-- GRANT SELECT, INSERT ON evidence_locker.verdict_attestations TO evidence_locker_app;
-- GRANT SELECT ON evidence_locker.verdict_attestations_summary TO evidence_locker_app;
-- Add comments for documentation
COMMENT ON TABLE evidence_locker.verdict_attestations IS
'Stores DSSE-signed policy verdict attestations for audit and verification';
COMMENT ON COLUMN evidence_locker.verdict_attestations.verdict_id IS
'Unique verdict identifier (format: verdict:run:{runId}:finding:{findingId})';
COMMENT ON COLUMN evidence_locker.verdict_attestations.envelope IS
'DSSE envelope containing signed verdict predicate';
COMMENT ON COLUMN evidence_locker.verdict_attestations.predicate_digest IS
'SHA256 digest of the canonical JSON predicate payload';
COMMENT ON COLUMN evidence_locker.verdict_attestations.determinism_hash IS
'Determinism hash computed from sorted evidence digests and verdict components';
COMMENT ON COLUMN evidence_locker.verdict_attestations.rekor_log_index IS
'Rekor transparency log index (if anchored), null for offline/air-gap deployments';

View File

@@ -0,0 +1,41 @@
-- Integrations catalog bootstrap schema for compose environments.
-- Creates the EF-backed integrations table when running without EF migrations.
CREATE TABLE IF NOT EXISTS integrations
(
id UUID PRIMARY KEY,
name VARCHAR(256) NOT NULL,
description VARCHAR(1024),
type INTEGER NOT NULL,
provider INTEGER NOT NULL,
status INTEGER NOT NULL,
endpoint VARCHAR(2048) NOT NULL,
auth_ref_uri VARCHAR(1024),
organization_id VARCHAR(256),
config_json JSONB,
last_health_status INTEGER NOT NULL DEFAULT 0,
last_health_check_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL,
created_by VARCHAR(256),
updated_by VARCHAR(256),
tenant_id VARCHAR(128),
tags JSONB,
is_deleted BOOLEAN NOT NULL DEFAULT FALSE
);
CREATE INDEX IF NOT EXISTS ix_integrations_type
ON integrations (type);
CREATE INDEX IF NOT EXISTS ix_integrations_provider
ON integrations (provider);
CREATE INDEX IF NOT EXISTS ix_integrations_status
ON integrations (status);
CREATE INDEX IF NOT EXISTS ix_integrations_tenant
ON integrations (tenant_id);
CREATE UNIQUE INDEX IF NOT EXISTS ix_integrations_tenant_name_active
ON integrations (tenant_id, name)
WHERE is_deleted = FALSE;

View File

@@ -0,0 +1,10 @@
CREATE SCHEMA IF NOT EXISTS advisoryai;
DO $$
BEGIN
CREATE EXTENSION IF NOT EXISTS vector;
EXCEPTION
WHEN OTHERS THEN
RAISE NOTICE 'pgvector extension unavailable in test DB image; AKS falls back to array embeddings.';
END
$$;

View File

View File

@@ -0,0 +1,5 @@
{
"Path": "/rekor",
"Type": "ReverseProxy",
"TranslatesTo": "http://rekor.stella-ops.local:3322"
}

View File

@@ -0,0 +1 @@
{"type":"https://stellaops.org/problems/internal-error","title":"Unexpected server error","status":500,"detail":"The AuthorizationPolicy named: 'scanner.secrets.settings.read' was not found.","instance":"/api/v1/secrets/config/rules/categories","traceId":"41e58f34254db08289098df447d10f7c"}

View File

@@ -1,143 +1,717 @@
{
"Gateway": {
"Auth": {
"DpopEnabled": false,
"AllowAnonymous": true,
"EnableLegacyHeaders": true,
"AllowScopeHeader": false,
"Authority": {
"Issuer": "https://authority.stella-ops.local/",
"RequireHttpsMetadata": false,
"MetadataAddress": "https://authority.stella-ops.local/.well-known/openid-configuration",
"Audiences": []
}
},
"Routes": [
{ "Type": "ReverseProxy", "Path": "/api/v1/release-orchestrator", "TranslatesTo": "http://orchestrator.stella-ops.local/api/v1/release-orchestrator" },
{ "Type": "ReverseProxy", "Path": "/api/v1/vex", "TranslatesTo": "https://vexhub.stella-ops.local/api/v1/vex" },
{ "Type": "ReverseProxy", "Path": "/api/v1/vexlens", "TranslatesTo": "http://vexlens.stella-ops.local/api/v1/vexlens" },
{ "Type": "ReverseProxy", "Path": "/api/v1/notify", "TranslatesTo": "http://notify.stella-ops.local/api/v1/notify" },
{ "Type": "ReverseProxy", "Path": "/api/v1/notifier", "TranslatesTo": "http://notifier.stella-ops.local/api/v1/notifier" },
{ "Type": "ReverseProxy", "Path": "/api/v1/concelier", "TranslatesTo": "http://concelier.stella-ops.local/api/v1/concelier" },
{ "Type": "ReverseProxy", "Path": "/api/v1/platform", "TranslatesTo": "http://platform.stella-ops.local/api/v1/platform" },
{ "Type": "ReverseProxy", "Path": "/api/v1/scanner", "TranslatesTo": "http://scanner.stella-ops.local/api/v1/scanner" },
{ "Type": "ReverseProxy", "Path": "/api/v1/findings", "TranslatesTo": "http://findings.stella-ops.local/api/v1/findings", "PreserveAuthHeaders": true },
{ "Type": "ReverseProxy", "Path": "/api/v1/integrations", "TranslatesTo": "http://integrations.stella-ops.local/api/v1/integrations", "PreserveAuthHeaders": true },
{ "Type": "ReverseProxy", "Path": "/api/v1/policy", "TranslatesTo": "http://policy-gateway.stella-ops.local/api/v1/policy" },
{ "Type": "ReverseProxy", "Path": "/api/v1/reachability", "TranslatesTo": "http://reachgraph.stella-ops.local/api/v1/reachability" },
{ "Type": "ReverseProxy", "Path": "/api/v1/attestor", "TranslatesTo": "http://attestor.stella-ops.local/api/v1/attestor" },
{ "Type": "ReverseProxy", "Path": "/api/v1/attestations", "TranslatesTo": "http://attestor.stella-ops.local/api/v1/attestations" },
{ "Type": "ReverseProxy", "Path": "/api/v1/sbom", "TranslatesTo": "http://sbomservice.stella-ops.local/api/v1/sbom" },
{ "Type": "ReverseProxy", "Path": "/api/v1/signals", "TranslatesTo": "http://signals.stella-ops.local/api/v1/signals" },
{ "Type": "ReverseProxy", "Path": "/api/v1/orchestrator", "TranslatesTo": "http://orchestrator.stella-ops.local/api/v1/orchestrator" },
{ "Type": "ReverseProxy", "Path": "/api/v1/authority/quotas", "TranslatesTo": "http://platform.stella-ops.local/api/v1/authority/quotas", "PreserveAuthHeaders": true },
{ "Type": "ReverseProxy", "Path": "/api/v1/authority", "TranslatesTo": "https://authority.stella-ops.local/api/v1/authority", "PreserveAuthHeaders": true },
{ "Type": "ReverseProxy", "Path": "/api/v1/trust", "TranslatesTo": "https://authority.stella-ops.local/api/v1/trust", "PreserveAuthHeaders": true },
{ "Type": "ReverseProxy", "Path": "/api/v1/evidence", "TranslatesTo": "https://evidencelocker.stella-ops.local/api/v1/evidence" },
{ "Type": "ReverseProxy", "Path": "/api/v1/proofs", "TranslatesTo": "https://evidencelocker.stella-ops.local/api/v1/proofs" },
{ "Type": "ReverseProxy", "Path": "/api/v1/timeline", "TranslatesTo": "http://timelineindexer.stella-ops.local/api/v1/timeline" },
{ "Type": "ReverseProxy", "Path": "/api/v1/advisory-ai", "TranslatesTo": "http://advisoryai.stella-ops.local/api/v1/advisory-ai" },
{ "Type": "ReverseProxy", "Path": "/api/v1/advisory", "TranslatesTo": "http://advisoryai.stella-ops.local/api/v1/advisory" },
{ "Type": "ReverseProxy", "Path": "/api/v1/vulnerabilities", "TranslatesTo": "http://scanner.stella-ops.local/api/v1/vulnerabilities" },
{ "Type": "ReverseProxy", "Path": "/api/v1/watchlist", "TranslatesTo": "http://scanner.stella-ops.local/api/v1/watchlist" },
{ "Type": "ReverseProxy", "Path": "/api/v1/resolve", "TranslatesTo": "http://binaryindex.stella-ops.local/api/v1/resolve" },
{ "Type": "ReverseProxy", "Path": "/api/v1/ops/binaryindex", "TranslatesTo": "http://binaryindex.stella-ops.local/api/v1/ops/binaryindex" },
{ "Type": "ReverseProxy", "Path": "/api/v1/verdicts", "TranslatesTo": "https://evidencelocker.stella-ops.local/api/v1/verdicts" },
{ "Type": "ReverseProxy", "Path": "/api/v1/lineage", "TranslatesTo": "http://sbomservice.stella-ops.local/api/v1/lineage" },
{ "Type": "ReverseProxy", "Path": "/api/v1/export", "TranslatesTo": "https://exportcenter.stella-ops.local/api/v1/export" },
{ "Type": "ReverseProxy", "Path": "/api/v1/triage", "TranslatesTo": "http://scanner.stella-ops.local/api/v1/triage" },
{ "Type": "ReverseProxy", "Path": "/api/v1/governance", "TranslatesTo": "http://policy-gateway.stella-ops.local/api/v1/governance" },
{ "Type": "ReverseProxy", "Path": "/api/v1/determinization", "TranslatesTo": "http://policy-engine.stella-ops.local/api/v1/determinization" },
{ "Type": "ReverseProxy", "Path": "/api/v1/opsmemory", "TranslatesTo": "http://opsmemory.stella-ops.local/api/v1/opsmemory" },
{ "Type": "ReverseProxy", "Path": "/api/v1/secrets", "TranslatesTo": "http://scanner.stella-ops.local/api/v1/secrets" },
{ "Type": "ReverseProxy", "Path": "/api/v1/sources", "TranslatesTo": "http://sbomservice.stella-ops.local/api/v1/sources" },
{ "Type": "ReverseProxy", "Path": "/api/v1/workflows", "TranslatesTo": "http://orchestrator.stella-ops.local/api/v1/workflows" },
{ "Type": "ReverseProxy", "Path": "/api/v1/witnesses", "TranslatesTo": "http://attestor.stella-ops.local/api/v1/witnesses" },
{ "Type": "ReverseProxy", "Path": "/v1/evidence-packs", "TranslatesTo": "https://evidencelocker.stella-ops.local/v1/evidence-packs" },
{ "Type": "ReverseProxy", "Path": "/v1/runs", "TranslatesTo": "http://orchestrator.stella-ops.local/v1/runs" },
{ "Type": "ReverseProxy", "Path": "/v1/advisory-ai", "TranslatesTo": "http://advisoryai.stella-ops.local/v1/advisory-ai" },
{ "Type": "ReverseProxy", "Path": "/v1/audit-bundles", "TranslatesTo": "https://exportcenter.stella-ops.local/v1/audit-bundles" },
{ "Type": "ReverseProxy", "Path": "/policy", "TranslatesTo": "http://policy-gateway.stella-ops.local" },
{ "Type": "ReverseProxy", "Path": "/api/cvss", "TranslatesTo": "http://policy-gateway.stella-ops.local/api/cvss", "PreserveAuthHeaders": true },
{ "Type": "ReverseProxy", "Path": "/api/policy", "TranslatesTo": "http://policy-gateway.stella-ops.local/api/policy", "PreserveAuthHeaders": true },
{ "Type": "ReverseProxy", "Path": "/api/risk", "TranslatesTo": "http://policy-engine.stella-ops.local/api/risk", "PreserveAuthHeaders": true },
{ "Type": "ReverseProxy", "Path": "/api/analytics", "TranslatesTo": "http://platform.stella-ops.local/api/analytics" },
{ "Type": "ReverseProxy", "Path": "/api/release-orchestrator", "TranslatesTo": "http://orchestrator.stella-ops.local/api/release-orchestrator" },
{ "Type": "ReverseProxy", "Path": "/api/releases", "TranslatesTo": "http://orchestrator.stella-ops.local/api/releases" },
{ "Type": "ReverseProxy", "Path": "/api/approvals", "TranslatesTo": "http://orchestrator.stella-ops.local/api/approvals" },
{ "Type": "ReverseProxy", "Path": "/api/gate", "TranslatesTo": "http://policy-gateway.stella-ops.local/api/gate", "PreserveAuthHeaders": true },
{ "Type": "ReverseProxy", "Path": "/api/risk-budget", "TranslatesTo": "http://policy-engine.stella-ops.local/api/risk-budget" },
{ "Type": "ReverseProxy", "Path": "/api/fix-verification", "TranslatesTo": "http://scanner.stella-ops.local/api/fix-verification" },
{ "Type": "ReverseProxy", "Path": "/api/compare", "TranslatesTo": "http://sbomservice.stella-ops.local/api/compare" },
{ "Type": "ReverseProxy", "Path": "/api/change-traces", "TranslatesTo": "http://sbomservice.stella-ops.local/api/change-traces" },
{ "Type": "ReverseProxy", "Path": "/api/exceptions", "TranslatesTo": "http://policy-gateway.stella-ops.local/api/exceptions", "PreserveAuthHeaders": true },
{ "Type": "ReverseProxy", "Path": "/api/verdicts", "TranslatesTo": "https://evidencelocker.stella-ops.local/api/verdicts" },
{ "Type": "ReverseProxy", "Path": "/api/orchestrator", "TranslatesTo": "http://orchestrator.stella-ops.local/api/orchestrator" },
{ "Type": "ReverseProxy", "Path": "/api/v1/gateway/rate-limits", "TranslatesTo": "http://platform.stella-ops.local/api/v1/gateway/rate-limits", "PreserveAuthHeaders": true },
{ "Type": "ReverseProxy", "Path": "/api/sbomservice", "TranslatesTo": "http://sbomservice.stella-ops.local/api/sbomservice" },
{ "Type": "ReverseProxy", "Path": "/api/vuln-explorer", "TranslatesTo": "http://vulnexplorer.stella-ops.local/api/vuln-explorer" },
{ "Type": "ReverseProxy", "Path": "/api/vex", "TranslatesTo": "https://vexhub.stella-ops.local/api/vex" },
{ "Type": "ReverseProxy", "Path": "/api/admin", "TranslatesTo": "http://platform.stella-ops.local/api/admin" },
{ "Type": "ReverseProxy", "Path": "/api/scheduler", "TranslatesTo": "http://scheduler.stella-ops.local/api/scheduler" },
{ "Type": "ReverseProxy", "Path": "/api/doctor", "TranslatesTo": "http://doctor.stella-ops.local/api/doctor" },
{ "Type": "ReverseProxy", "Path": "/api", "TranslatesTo": "http://platform.stella-ops.local/api" },
{ "Type": "StaticFile", "Path": "/platform/envsettings.json", "TranslatesTo": "/app/envsettings-override.json" },
{ "Type": "ReverseProxy", "Path": "/platform", "TranslatesTo": "http://platform.stella-ops.local/platform" },
{ "Type": "ReverseProxy", "Path": "/connect", "TranslatesTo": "https://authority.stella-ops.local", "PreserveAuthHeaders": true },
{ "Type": "ReverseProxy", "Path": "/.well-known", "TranslatesTo": "https://authority.stella-ops.local/.well-known", "PreserveAuthHeaders": true },
{ "Type": "ReverseProxy", "Path": "/jwks", "TranslatesTo": "https://authority.stella-ops.local/jwks", "PreserveAuthHeaders": true },
{ "Type": "ReverseProxy", "Path": "/authority", "TranslatesTo": "https://authority.stella-ops.local/authority", "PreserveAuthHeaders": true },
{ "Type": "ReverseProxy", "Path": "/console", "TranslatesTo": "https://authority.stella-ops.local/console", "PreserveAuthHeaders": true },
{ "Type": "ReverseProxy", "Path": "/envsettings.json", "TranslatesTo": "http://platform.stella-ops.local/platform/envsettings.json" },
{ "Type": "ReverseProxy", "Path": "/gateway", "TranslatesTo": "http://gateway.stella-ops.local" },
{ "Type": "ReverseProxy", "Path": "/scanner", "TranslatesTo": "http://scanner.stella-ops.local" },
{ "Type": "ReverseProxy", "Path": "/policyGateway", "TranslatesTo": "http://policy-gateway.stella-ops.local" },
{ "Type": "ReverseProxy", "Path": "/policyEngine", "TranslatesTo": "http://policy-engine.stella-ops.local" },
{ "Type": "ReverseProxy", "Path": "/concelier", "TranslatesTo": "http://concelier.stella-ops.local" },
{ "Type": "ReverseProxy", "Path": "/attestor", "TranslatesTo": "http://attestor.stella-ops.local" },
{ "Type": "ReverseProxy", "Path": "/notify", "TranslatesTo": "http://notify.stella-ops.local" },
{ "Type": "ReverseProxy", "Path": "/notifier", "TranslatesTo": "http://notifier.stella-ops.local" },
{ "Type": "ReverseProxy", "Path": "/scheduler", "TranslatesTo": "http://scheduler.stella-ops.local" },
{ "Type": "ReverseProxy", "Path": "/signals", "TranslatesTo": "http://signals.stella-ops.local" },
{ "Type": "ReverseProxy", "Path": "/excititor", "TranslatesTo": "http://excititor.stella-ops.local" },
{ "Type": "ReverseProxy", "Path": "/findingsLedger", "TranslatesTo": "http://findings.stella-ops.local" },
{ "Type": "ReverseProxy", "Path": "/vexhub", "TranslatesTo": "https://vexhub.stella-ops.local" },
{ "Type": "ReverseProxy", "Path": "/vexlens", "TranslatesTo": "http://vexlens.stella-ops.local" },
{ "Type": "ReverseProxy", "Path": "/orchestrator", "TranslatesTo": "http://orchestrator.stella-ops.local" },
{ "Type": "ReverseProxy", "Path": "/taskrunner", "TranslatesTo": "http://taskrunner.stella-ops.local" },
{ "Type": "ReverseProxy", "Path": "/cartographer", "TranslatesTo": "http://cartographer.stella-ops.local" },
{ "Type": "ReverseProxy", "Path": "/reachgraph", "TranslatesTo": "http://reachgraph.stella-ops.local" },
{ "Type": "ReverseProxy", "Path": "/doctor", "TranslatesTo": "http://doctor.stella-ops.local" },
{ "Type": "ReverseProxy", "Path": "/integrations", "TranslatesTo": "http://integrations.stella-ops.local" },
{ "Type": "ReverseProxy", "Path": "/replay", "TranslatesTo": "http://replay.stella-ops.local" },
{ "Type": "ReverseProxy", "Path": "/exportcenter", "TranslatesTo": "https://exportcenter.stella-ops.local" },
{ "Type": "ReverseProxy", "Path": "/evidencelocker", "TranslatesTo": "https://evidencelocker.stella-ops.local" },
{ "Type": "ReverseProxy", "Path": "/signer", "TranslatesTo": "http://signer.stella-ops.local" },
{ "Type": "ReverseProxy", "Path": "/binaryindex", "TranslatesTo": "http://binaryindex.stella-ops.local" },
{ "Type": "ReverseProxy", "Path": "/riskengine", "TranslatesTo": "http://riskengine.stella-ops.local" },
{ "Type": "ReverseProxy", "Path": "/vulnexplorer", "TranslatesTo": "http://vulnexplorer.stella-ops.local" },
{ "Type": "ReverseProxy", "Path": "/sbomservice", "TranslatesTo": "http://sbomservice.stella-ops.local" },
{ "Type": "ReverseProxy", "Path": "/advisoryai", "TranslatesTo": "http://advisoryai.stella-ops.local" },
{ "Type": "ReverseProxy", "Path": "/unknowns", "TranslatesTo": "http://unknowns.stella-ops.local" },
{ "Type": "ReverseProxy", "Path": "/timelineindexer", "TranslatesTo": "http://timelineindexer.stella-ops.local" },
{ "Type": "ReverseProxy", "Path": "/opsmemory", "TranslatesTo": "http://opsmemory.stella-ops.local" },
{ "Type": "ReverseProxy", "Path": "/issuerdirectory", "TranslatesTo": "http://issuerdirectory.stella-ops.local" },
{ "Type": "ReverseProxy", "Path": "/symbols", "TranslatesTo": "http://symbols.stella-ops.local" },
{ "Type": "ReverseProxy", "Path": "/packsregistry", "TranslatesTo": "http://packsregistry.stella-ops.local" },
{ "Type": "ReverseProxy", "Path": "/registryTokenservice", "TranslatesTo": "http://registry-token.stella-ops.local" },
{ "Type": "ReverseProxy", "Path": "/airgapController", "TranslatesTo": "http://airgap-controller.stella-ops.local" },
{ "Type": "ReverseProxy", "Path": "/airgapTime", "TranslatesTo": "http://airgap-time.stella-ops.local" },
{ "Type": "ReverseProxy", "Path": "/smremote", "TranslatesTo": "http://smremote.stella-ops.local" },
{ "Type": "StaticFiles", "Path": "/", "TranslatesTo": "/app/wwwroot", "Headers": { "x-spa-fallback": "true" } },
{ "Type": "NotFoundPage", "Path": "/_error/404", "TranslatesTo": "/app/wwwroot/index.html" },
{ "Type": "ServerErrorPage", "Path": "/_error/500", "TranslatesTo": "/app/wwwroot/index.html" }
]
},
"Logging": {
"LogLevel": {
"Microsoft.AspNetCore.Authentication": "Debug",
"Microsoft.IdentityModel": "Debug",
"StellaOps": "Debug"
}
}
{
"Gateway": {
"Auth": {
"DpopEnabled": false,
"AllowAnonymous": true,
"EnableLegacyHeaders": true,
"AllowScopeHeader": false,
"Authority": {
"Issuer": "https://authority.stella-ops.local/",
"RequireHttpsMetadata": false,
"MetadataAddress": "https://authority.stella-ops.local/.well-known/openid-configuration",
"Audiences": [
]
}
},
"Routes": [
{
"Type": "Microservice",
"Path": "/api/v1/release-orchestrator",
"TranslatesTo": "http://orchestrator.stella-ops.local/api/v1/release-orchestrator",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/vex",
"TranslatesTo": "https://vexhub.stella-ops.local/api/v1/vex",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/vexlens",
"TranslatesTo": "http://vexlens.stella-ops.local/api/v1/vexlens",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/notify",
"TranslatesTo": "http://notify.stella-ops.local/api/v1/notify",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/notifier",
"TranslatesTo": "http://notifier.stella-ops.local/api/v1/notifier",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/concelier",
"TranslatesTo": "http://concelier.stella-ops.local/api/v1/concelier",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/platform",
"TranslatesTo": "http://platform.stella-ops.local/api/v1/platform",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/scanner",
"TranslatesTo": "http://scanner.stella-ops.local/api/v1/scanner",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/findings",
"TranslatesTo": "http://findings.stella-ops.local/api/v1/findings",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/integrations",
"TranslatesTo": "http://integrations.stella-ops.local/api/v1/integrations",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/policy",
"TranslatesTo": "http://policy-gateway.stella-ops.local/api/v1/policy",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/reachability",
"TranslatesTo": "http://reachgraph.stella-ops.local/api/v1/reachability",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/attestor",
"TranslatesTo": "http://attestor.stella-ops.local/api/v1/attestor",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/attestations",
"TranslatesTo": "http://attestor.stella-ops.local/api/v1/attestations",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/sbom",
"TranslatesTo": "http://sbomservice.stella-ops.local/api/v1/sbom",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/signals",
"TranslatesTo": "http://signals.stella-ops.local/api/v1/signals",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/orchestrator",
"TranslatesTo": "http://orchestrator.stella-ops.local/api/v1/orchestrator",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/authority/quotas",
"TranslatesTo": "http://platform.stella-ops.local/api/v1/authority/quotas",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/authority",
"TranslatesTo": "https://authority.stella-ops.local/api/v1/authority",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/trust",
"TranslatesTo": "https://authority.stella-ops.local/api/v1/trust",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/evidence",
"TranslatesTo": "https://evidencelocker.stella-ops.local/api/v1/evidence",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/proofs",
"TranslatesTo": "https://evidencelocker.stella-ops.local/api/v1/proofs",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/timeline",
"TranslatesTo": "http://timelineindexer.stella-ops.local/api/v1/timeline",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/advisory-ai/adapters",
"TranslatesTo": "http://advisoryai.stella-ops.local/v1/advisory-ai/adapters",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/advisory-ai",
"TranslatesTo": "http://advisoryai.stella-ops.local/api/v1/advisory-ai",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/advisory",
"TranslatesTo": "http://advisoryai.stella-ops.local/api/v1/advisory",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/vulnerabilities",
"TranslatesTo": "http://scanner.stella-ops.local/api/v1/vulnerabilities",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/watchlist",
"TranslatesTo": "http://scanner.stella-ops.local/api/v1/watchlist",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/resolve",
"TranslatesTo": "http://binaryindex.stella-ops.local/api/v1/resolve",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/ops/binaryindex",
"TranslatesTo": "http://binaryindex.stella-ops.local/api/v1/ops/binaryindex",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/verdicts",
"TranslatesTo": "https://evidencelocker.stella-ops.local/api/v1/verdicts",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/lineage",
"TranslatesTo": "http://sbomservice.stella-ops.local/api/v1/lineage",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/export",
"TranslatesTo": "https://exportcenter.stella-ops.local/api/v1/export",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/triage",
"TranslatesTo": "http://scanner.stella-ops.local/api/v1/triage",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/governance",
"TranslatesTo": "http://policy-gateway.stella-ops.local/api/v1/governance",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/determinization",
"TranslatesTo": "http://policy-engine.stella-ops.local/api/v1/determinization",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/opsmemory",
"TranslatesTo": "http://opsmemory.stella-ops.local/api/v1/opsmemory",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/secrets",
"TranslatesTo": "http://scanner.stella-ops.local/api/v1/secrets",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/sources",
"TranslatesTo": "http://sbomservice.stella-ops.local/api/v1/sources",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/workflows",
"TranslatesTo": "http://orchestrator.stella-ops.local/api/v1/workflows",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/witnesses",
"TranslatesTo": "http://attestor.stella-ops.local/api/v1/witnesses",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/v1/evidence-packs",
"TranslatesTo": "https://evidencelocker.stella-ops.local/v1/evidence-packs",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/v1/runs",
"TranslatesTo": "http://orchestrator.stella-ops.local/v1/runs",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/v1/advisory-ai/adapters",
"TranslatesTo": "http://advisoryai.stella-ops.local/v1/advisory-ai/adapters",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/v1/advisory-ai",
"TranslatesTo": "http://advisoryai.stella-ops.local/v1/advisory-ai",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/v1/audit-bundles",
"TranslatesTo": "https://exportcenter.stella-ops.local/v1/audit-bundles",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/policy",
"TranslatesTo": "http://policy-gateway.stella-ops.local",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/cvss",
"TranslatesTo": "http://policy-gateway.stella-ops.local/api/cvss",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/policy",
"TranslatesTo": "http://policy-gateway.stella-ops.local/api/policy",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/risk",
"TranslatesTo": "http://policy-engine.stella-ops.local/api/risk",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/analytics",
"TranslatesTo": "http://platform.stella-ops.local/api/analytics",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/release-orchestrator",
"TranslatesTo": "http://orchestrator.stella-ops.local/api/release-orchestrator",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/releases",
"TranslatesTo": "http://orchestrator.stella-ops.local/api/releases",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/approvals",
"TranslatesTo": "http://orchestrator.stella-ops.local/api/approvals",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/gate",
"TranslatesTo": "http://policy-gateway.stella-ops.local/api/gate",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/risk-budget",
"TranslatesTo": "http://policy-engine.stella-ops.local/api/risk-budget",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/fix-verification",
"TranslatesTo": "http://scanner.stella-ops.local/api/fix-verification",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/compare",
"TranslatesTo": "http://sbomservice.stella-ops.local/api/compare",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/change-traces",
"TranslatesTo": "http://sbomservice.stella-ops.local/api/change-traces",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/exceptions",
"TranslatesTo": "http://policy-gateway.stella-ops.local/api/exceptions",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/verdicts",
"TranslatesTo": "https://evidencelocker.stella-ops.local/api/verdicts",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/orchestrator",
"TranslatesTo": "http://orchestrator.stella-ops.local/api/orchestrator",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/gateway/rate-limits",
"TranslatesTo": "http://platform.stella-ops.local/api/v1/gateway/rate-limits",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/sbomservice",
"TranslatesTo": "http://sbomservice.stella-ops.local/api/sbomservice",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/vuln-explorer",
"TranslatesTo": "http://vulnexplorer.stella-ops.local/api/vuln-explorer",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/vex",
"TranslatesTo": "https://vexhub.stella-ops.local/api/vex",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/admin",
"TranslatesTo": "http://platform.stella-ops.local/api/admin",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/scheduler",
"TranslatesTo": "http://scheduler.stella-ops.local/api/scheduler",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/v1/doctor/scheduler",
"TranslatesTo": "http://doctor-scheduler.stella-ops.local/api/v1/doctor/scheduler",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api/doctor",
"TranslatesTo": "http://doctor.stella-ops.local/api/doctor",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/api",
"TranslatesTo": "http://platform.stella-ops.local/api",
"PreserveAuthHeaders": true
},
{
"Type": "StaticFile",
"Path": "/platform/envsettings.json",
"TranslatesTo": "/app/envsettings-override.json"
},
{
"Type": "ReverseProxy",
"Path": "/platform",
"TranslatesTo": "http://platform.stella-ops.local/platform"
},
{
"Type": "Microservice",
"Path": "/connect",
"TranslatesTo": "https://authority.stella-ops.local/connect",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/.well-known",
"TranslatesTo": "https://authority.stella-ops.local/well-known",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/jwks",
"TranslatesTo": "https://authority.stella-ops.local/jwks",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/authority",
"TranslatesTo": "https://authority.stella-ops.local/authority",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/console",
"TranslatesTo": "https://authority.stella-ops.local/console",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/rekor",
"TranslatesTo": "http://rekor.stella-ops.local:3322"
},
{
"Type": "ReverseProxy",
"Path": "/envsettings.json",
"TranslatesTo": "http://platform.stella-ops.local/platform/envsettings.json"
},
{
"Type": "Microservice",
"Path": "/gateway",
"TranslatesTo": "http://gateway.stella-ops.local"
},
{
"Type": "Microservice",
"Path": "/scanner",
"TranslatesTo": "http://scanner.stella-ops.local"
},
{
"Type": "Microservice",
"Path": "/policyGateway",
"TranslatesTo": "http://policy-gateway.stella-ops.local"
},
{
"Type": "Microservice",
"Path": "/policyEngine",
"TranslatesTo": "http://policy-engine.stella-ops.local"
},
{
"Type": "Microservice",
"Path": "/concelier",
"TranslatesTo": "http://concelier.stella-ops.local"
},
{
"Type": "Microservice",
"Path": "/attestor",
"TranslatesTo": "http://attestor.stella-ops.local"
},
{
"Type": "Microservice",
"Path": "/notify",
"TranslatesTo": "http://notify.stella-ops.local"
},
{
"Type": "Microservice",
"Path": "/notifier",
"TranslatesTo": "http://notifier.stella-ops.local"
},
{
"Type": "Microservice",
"Path": "/scheduler",
"TranslatesTo": "http://scheduler.stella-ops.local"
},
{
"Type": "Microservice",
"Path": "/signals",
"TranslatesTo": "http://signals.stella-ops.local"
},
{
"Type": "Microservice",
"Path": "/excititor",
"TranslatesTo": "http://excititor.stella-ops.local"
},
{
"Type": "Microservice",
"Path": "/findingsLedger",
"TranslatesTo": "http://findings.stella-ops.local"
},
{
"Type": "Microservice",
"Path": "/vexhub",
"TranslatesTo": "https://vexhub.stella-ops.local"
},
{
"Type": "Microservice",
"Path": "/vexlens",
"TranslatesTo": "http://vexlens.stella-ops.local"
},
{
"Type": "Microservice",
"Path": "/orchestrator",
"TranslatesTo": "http://orchestrator.stella-ops.local"
},
{
"Type": "Microservice",
"Path": "/taskrunner",
"TranslatesTo": "http://taskrunner.stella-ops.local"
},
{
"Type": "Microservice",
"Path": "/cartographer",
"TranslatesTo": "http://cartographer.stella-ops.local"
},
{
"Type": "Microservice",
"Path": "/reachgraph",
"TranslatesTo": "http://reachgraph.stella-ops.local"
},
{
"Type": "Microservice",
"Path": "/doctor",
"TranslatesTo": "http://doctor.stella-ops.local",
"PreserveAuthHeaders": true
},
{
"Type": "Microservice",
"Path": "/integrations",
"TranslatesTo": "http://integrations.stella-ops.local"
},
{
"Type": "Microservice",
"Path": "/replay",
"TranslatesTo": "http://replay.stella-ops.local"
},
{
"Type": "Microservice",
"Path": "/exportcenter",
"TranslatesTo": "https://exportcenter.stella-ops.local"
},
{
"Type": "Microservice",
"Path": "/evidencelocker",
"TranslatesTo": "https://evidencelocker.stella-ops.local"
},
{
"Type": "Microservice",
"Path": "/signer",
"TranslatesTo": "http://signer.stella-ops.local"
},
{
"Type": "Microservice",
"Path": "/binaryindex",
"TranslatesTo": "http://binaryindex.stella-ops.local"
},
{
"Type": "Microservice",
"Path": "/riskengine",
"TranslatesTo": "http://riskengine.stella-ops.local"
},
{
"Type": "Microservice",
"Path": "/vulnexplorer",
"TranslatesTo": "http://vulnexplorer.stella-ops.local"
},
{
"Type": "Microservice",
"Path": "/sbomservice",
"TranslatesTo": "http://sbomservice.stella-ops.local"
},
{
"Type": "Microservice",
"Path": "/advisoryai",
"TranslatesTo": "http://advisoryai.stella-ops.local"
},
{
"Type": "Microservice",
"Path": "/unknowns",
"TranslatesTo": "http://unknowns.stella-ops.local"
},
{
"Type": "Microservice",
"Path": "/timelineindexer",
"TranslatesTo": "http://timelineindexer.stella-ops.local"
},
{
"Type": "Microservice",
"Path": "/opsmemory",
"TranslatesTo": "http://opsmemory.stella-ops.local"
},
{
"Type": "Microservice",
"Path": "/issuerdirectory",
"TranslatesTo": "http://issuerdirectory.stella-ops.local"
},
{
"Type": "Microservice",
"Path": "/symbols",
"TranslatesTo": "http://symbols.stella-ops.local"
},
{
"Type": "Microservice",
"Path": "/packsregistry",
"TranslatesTo": "http://packsregistry.stella-ops.local"
},
{
"Type": "Microservice",
"Path": "/registryTokenservice",
"TranslatesTo": "http://registry-token.stella-ops.local"
},
{
"Type": "Microservice",
"Path": "/airgapController",
"TranslatesTo": "http://airgap-controller.stella-ops.local"
},
{
"Type": "Microservice",
"Path": "/airgapTime",
"TranslatesTo": "http://airgap-time.stella-ops.local"
},
{
"Type": "Microservice",
"Path": "/smremote",
"TranslatesTo": "http://smremote.stella-ops.local"
},
{
"Type": "StaticFiles",
"Path": "/",
"TranslatesTo": "/app/wwwroot",
"Headers": {
"x-spa-fallback": "true"
}
},
{
"Type": "NotFoundPage",
"Path": "/_error/404",
"TranslatesTo": "/app/wwwroot/index.html"
},
{
"Type": "ServerErrorPage",
"Path": "/_error/500",
"TranslatesTo": "/app/wwwroot/index.html"
}
]
},
"Logging": {
"LogLevel": {
"Microsoft.AspNetCore.Authentication": "Debug",
"Microsoft.IdentityModel": "Debug",
"StellaOps": "Debug"
}
}
}

View File

@@ -0,0 +1,717 @@
{
"Gateway": {
"Auth": {
"DpopEnabled": false,
"AllowAnonymous": true,
"EnableLegacyHeaders": true,
"AllowScopeHeader": false,
"Authority": {
"Issuer": "https://authority.stella-ops.local/",
"RequireHttpsMetadata": false,
"MetadataAddress": "https://authority.stella-ops.local/.well-known/openid-configuration",
"Audiences": [
]
}
},
"Routes": [
{
"Type": "ReverseProxy",
"Path": "/api/v1/release-orchestrator",
"TranslatesTo": "http://orchestrator.stella-ops.local/api/v1/release-orchestrator",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/vex",
"TranslatesTo": "https://vexhub.stella-ops.local/api/v1/vex",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/vexlens",
"TranslatesTo": "http://vexlens.stella-ops.local/api/v1/vexlens",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/notify",
"TranslatesTo": "http://notify.stella-ops.local/api/v1/notify",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/notifier",
"TranslatesTo": "http://notifier.stella-ops.local/api/v1/notifier",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/concelier",
"TranslatesTo": "http://concelier.stella-ops.local/api/v1/concelier",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/platform",
"TranslatesTo": "http://platform.stella-ops.local/api/v1/platform",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/scanner",
"TranslatesTo": "http://scanner.stella-ops.local/api/v1/scanner",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/findings",
"TranslatesTo": "http://findings.stella-ops.local/api/v1/findings",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/integrations",
"TranslatesTo": "http://integrations.stella-ops.local/api/v1/integrations",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/policy",
"TranslatesTo": "http://policy-gateway.stella-ops.local/api/v1/policy",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/reachability",
"TranslatesTo": "http://reachgraph.stella-ops.local/api/v1/reachability",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/attestor",
"TranslatesTo": "http://attestor.stella-ops.local/api/v1/attestor",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/attestations",
"TranslatesTo": "http://attestor.stella-ops.local/api/v1/attestations",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/sbom",
"TranslatesTo": "http://sbomservice.stella-ops.local/api/v1/sbom",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/signals",
"TranslatesTo": "http://signals.stella-ops.local/api/v1/signals",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/orchestrator",
"TranslatesTo": "http://orchestrator.stella-ops.local/api/v1/orchestrator",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/authority/quotas",
"TranslatesTo": "http://platform.stella-ops.local/api/v1/authority/quotas",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/authority",
"TranslatesTo": "https://authority.stella-ops.local/api/v1/authority",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/trust",
"TranslatesTo": "https://authority.stella-ops.local/api/v1/trust",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/evidence",
"TranslatesTo": "https://evidencelocker.stella-ops.local/api/v1/evidence",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/proofs",
"TranslatesTo": "https://evidencelocker.stella-ops.local/api/v1/proofs",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/timeline",
"TranslatesTo": "http://timelineindexer.stella-ops.local/api/v1/timeline",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/advisory-ai/adapters",
"TranslatesTo": "http://advisoryai.stella-ops.local/v1/advisory-ai/adapters",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/advisory-ai",
"TranslatesTo": "http://advisoryai.stella-ops.local/api/v1/advisory-ai",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/advisory",
"TranslatesTo": "http://advisoryai.stella-ops.local/api/v1/advisory",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/vulnerabilities",
"TranslatesTo": "http://scanner.stella-ops.local/api/v1/vulnerabilities",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/watchlist",
"TranslatesTo": "http://scanner.stella-ops.local/api/v1/watchlist",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/resolve",
"TranslatesTo": "http://binaryindex.stella-ops.local/api/v1/resolve",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/ops/binaryindex",
"TranslatesTo": "http://binaryindex.stella-ops.local/api/v1/ops/binaryindex",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/verdicts",
"TranslatesTo": "https://evidencelocker.stella-ops.local/api/v1/verdicts",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/lineage",
"TranslatesTo": "http://sbomservice.stella-ops.local/api/v1/lineage",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/export",
"TranslatesTo": "https://exportcenter.stella-ops.local/api/v1/export",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/triage",
"TranslatesTo": "http://scanner.stella-ops.local/api/v1/triage",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/governance",
"TranslatesTo": "http://policy-gateway.stella-ops.local/api/v1/governance",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/determinization",
"TranslatesTo": "http://policy-engine.stella-ops.local/api/v1/determinization",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/opsmemory",
"TranslatesTo": "http://opsmemory.stella-ops.local/api/v1/opsmemory",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/secrets",
"TranslatesTo": "http://scanner.stella-ops.local/api/v1/secrets",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/sources",
"TranslatesTo": "http://sbomservice.stella-ops.local/api/v1/sources",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/workflows",
"TranslatesTo": "http://orchestrator.stella-ops.local/api/v1/workflows",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/witnesses",
"TranslatesTo": "http://attestor.stella-ops.local/api/v1/witnesses",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/v1/evidence-packs",
"TranslatesTo": "https://evidencelocker.stella-ops.local/v1/evidence-packs",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/v1/runs",
"TranslatesTo": "http://orchestrator.stella-ops.local/v1/runs",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/v1/advisory-ai/adapters",
"TranslatesTo": "http://advisoryai.stella-ops.local/v1/advisory-ai/adapters",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/v1/advisory-ai",
"TranslatesTo": "http://advisoryai.stella-ops.local/v1/advisory-ai",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/v1/audit-bundles",
"TranslatesTo": "https://exportcenter.stella-ops.local/v1/audit-bundles",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/policy",
"TranslatesTo": "http://policy-gateway.stella-ops.local",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/cvss",
"TranslatesTo": "http://policy-gateway.stella-ops.local/api/cvss",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/policy",
"TranslatesTo": "http://policy-gateway.stella-ops.local/api/policy",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/risk",
"TranslatesTo": "http://policy-engine.stella-ops.local/api/risk",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/analytics",
"TranslatesTo": "http://platform.stella-ops.local/api/analytics",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/release-orchestrator",
"TranslatesTo": "http://orchestrator.stella-ops.local/api/release-orchestrator",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/releases",
"TranslatesTo": "http://orchestrator.stella-ops.local/api/releases",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/approvals",
"TranslatesTo": "http://orchestrator.stella-ops.local/api/approvals",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/gate",
"TranslatesTo": "http://policy-gateway.stella-ops.local/api/gate",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/risk-budget",
"TranslatesTo": "http://policy-engine.stella-ops.local/api/risk-budget",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/fix-verification",
"TranslatesTo": "http://scanner.stella-ops.local/api/fix-verification",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/compare",
"TranslatesTo": "http://sbomservice.stella-ops.local/api/compare",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/change-traces",
"TranslatesTo": "http://sbomservice.stella-ops.local/api/change-traces",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/exceptions",
"TranslatesTo": "http://policy-gateway.stella-ops.local/api/exceptions",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/verdicts",
"TranslatesTo": "https://evidencelocker.stella-ops.local/api/verdicts",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/orchestrator",
"TranslatesTo": "http://orchestrator.stella-ops.local/api/orchestrator",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/gateway/rate-limits",
"TranslatesTo": "http://platform.stella-ops.local/api/v1/gateway/rate-limits",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/sbomservice",
"TranslatesTo": "http://sbomservice.stella-ops.local/api/sbomservice",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/vuln-explorer",
"TranslatesTo": "http://vulnexplorer.stella-ops.local/api/vuln-explorer",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/vex",
"TranslatesTo": "https://vexhub.stella-ops.local/api/vex",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/admin",
"TranslatesTo": "http://platform.stella-ops.local/api/admin",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/scheduler",
"TranslatesTo": "http://scheduler.stella-ops.local/api/scheduler",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/v1/doctor/scheduler",
"TranslatesTo": "http://doctor-scheduler.stella-ops.local/api/v1/doctor/scheduler",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api/doctor",
"TranslatesTo": "http://doctor.stella-ops.local/api/doctor",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/api",
"TranslatesTo": "http://platform.stella-ops.local/api",
"PreserveAuthHeaders": true
},
{
"Type": "StaticFile",
"Path": "/platform/envsettings.json",
"TranslatesTo": "/app/envsettings-override.json"
},
{
"Type": "ReverseProxy",
"Path": "/platform",
"TranslatesTo": "http://platform.stella-ops.local/platform"
},
{
"Type": "ReverseProxy",
"Path": "/connect",
"TranslatesTo": "https://authority.stella-ops.local/connect",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/.well-known",
"TranslatesTo": "https://authority.stella-ops.local/well-known",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/jwks",
"TranslatesTo": "https://authority.stella-ops.local/jwks",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/authority",
"TranslatesTo": "https://authority.stella-ops.local/authority",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/console",
"TranslatesTo": "https://authority.stella-ops.local/console",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/rekor",
"TranslatesTo": "http://rekor.stella-ops.local:3322"
},
{
"Type": "ReverseProxy",
"Path": "/envsettings.json",
"TranslatesTo": "http://platform.stella-ops.local/platform/envsettings.json"
},
{
"Type": "ReverseProxy",
"Path": "/gateway",
"TranslatesTo": "http://gateway.stella-ops.local"
},
{
"Type": "ReverseProxy",
"Path": "/scanner",
"TranslatesTo": "http://scanner.stella-ops.local"
},
{
"Type": "ReverseProxy",
"Path": "/policyGateway",
"TranslatesTo": "http://policy-gateway.stella-ops.local"
},
{
"Type": "ReverseProxy",
"Path": "/policyEngine",
"TranslatesTo": "http://policy-engine.stella-ops.local"
},
{
"Type": "ReverseProxy",
"Path": "/concelier",
"TranslatesTo": "http://concelier.stella-ops.local"
},
{
"Type": "ReverseProxy",
"Path": "/attestor",
"TranslatesTo": "http://attestor.stella-ops.local"
},
{
"Type": "ReverseProxy",
"Path": "/notify",
"TranslatesTo": "http://notify.stella-ops.local"
},
{
"Type": "ReverseProxy",
"Path": "/notifier",
"TranslatesTo": "http://notifier.stella-ops.local"
},
{
"Type": "ReverseProxy",
"Path": "/scheduler",
"TranslatesTo": "http://scheduler.stella-ops.local"
},
{
"Type": "ReverseProxy",
"Path": "/signals",
"TranslatesTo": "http://signals.stella-ops.local"
},
{
"Type": "ReverseProxy",
"Path": "/excititor",
"TranslatesTo": "http://excititor.stella-ops.local"
},
{
"Type": "ReverseProxy",
"Path": "/findingsLedger",
"TranslatesTo": "http://findings.stella-ops.local"
},
{
"Type": "ReverseProxy",
"Path": "/vexhub",
"TranslatesTo": "https://vexhub.stella-ops.local"
},
{
"Type": "ReverseProxy",
"Path": "/vexlens",
"TranslatesTo": "http://vexlens.stella-ops.local"
},
{
"Type": "ReverseProxy",
"Path": "/orchestrator",
"TranslatesTo": "http://orchestrator.stella-ops.local"
},
{
"Type": "ReverseProxy",
"Path": "/taskrunner",
"TranslatesTo": "http://taskrunner.stella-ops.local"
},
{
"Type": "ReverseProxy",
"Path": "/cartographer",
"TranslatesTo": "http://cartographer.stella-ops.local"
},
{
"Type": "ReverseProxy",
"Path": "/reachgraph",
"TranslatesTo": "http://reachgraph.stella-ops.local"
},
{
"Type": "ReverseProxy",
"Path": "/doctor",
"TranslatesTo": "http://doctor.stella-ops.local",
"PreserveAuthHeaders": true
},
{
"Type": "ReverseProxy",
"Path": "/integrations",
"TranslatesTo": "http://integrations.stella-ops.local"
},
{
"Type": "ReverseProxy",
"Path": "/replay",
"TranslatesTo": "http://replay.stella-ops.local"
},
{
"Type": "ReverseProxy",
"Path": "/exportcenter",
"TranslatesTo": "https://exportcenter.stella-ops.local"
},
{
"Type": "ReverseProxy",
"Path": "/evidencelocker",
"TranslatesTo": "https://evidencelocker.stella-ops.local"
},
{
"Type": "ReverseProxy",
"Path": "/signer",
"TranslatesTo": "http://signer.stella-ops.local"
},
{
"Type": "ReverseProxy",
"Path": "/binaryindex",
"TranslatesTo": "http://binaryindex.stella-ops.local"
},
{
"Type": "ReverseProxy",
"Path": "/riskengine",
"TranslatesTo": "http://riskengine.stella-ops.local"
},
{
"Type": "ReverseProxy",
"Path": "/vulnexplorer",
"TranslatesTo": "http://vulnexplorer.stella-ops.local"
},
{
"Type": "ReverseProxy",
"Path": "/sbomservice",
"TranslatesTo": "http://sbomservice.stella-ops.local"
},
{
"Type": "ReverseProxy",
"Path": "/advisoryai",
"TranslatesTo": "http://advisoryai.stella-ops.local"
},
{
"Type": "ReverseProxy",
"Path": "/unknowns",
"TranslatesTo": "http://unknowns.stella-ops.local"
},
{
"Type": "ReverseProxy",
"Path": "/timelineindexer",
"TranslatesTo": "http://timelineindexer.stella-ops.local"
},
{
"Type": "ReverseProxy",
"Path": "/opsmemory",
"TranslatesTo": "http://opsmemory.stella-ops.local"
},
{
"Type": "ReverseProxy",
"Path": "/issuerdirectory",
"TranslatesTo": "http://issuerdirectory.stella-ops.local"
},
{
"Type": "ReverseProxy",
"Path": "/symbols",
"TranslatesTo": "http://symbols.stella-ops.local"
},
{
"Type": "ReverseProxy",
"Path": "/packsregistry",
"TranslatesTo": "http://packsregistry.stella-ops.local"
},
{
"Type": "ReverseProxy",
"Path": "/registryTokenservice",
"TranslatesTo": "http://registry-token.stella-ops.local"
},
{
"Type": "ReverseProxy",
"Path": "/airgapController",
"TranslatesTo": "http://airgap-controller.stella-ops.local"
},
{
"Type": "ReverseProxy",
"Path": "/airgapTime",
"TranslatesTo": "http://airgap-time.stella-ops.local"
},
{
"Type": "ReverseProxy",
"Path": "/smremote",
"TranslatesTo": "http://smremote.stella-ops.local"
},
{
"Type": "StaticFiles",
"Path": "/",
"TranslatesTo": "/app/wwwroot",
"Headers": {
"x-spa-fallback": "true"
}
},
{
"Type": "NotFoundPage",
"Path": "/_error/404",
"TranslatesTo": "/app/wwwroot/index.html"
},
{
"Type": "ServerErrorPage",
"Path": "/_error/500",
"TranslatesTo": "/app/wwwroot/index.html"
}
]
},
"Logging": {
"LogLevel": {
"Microsoft.AspNetCore.Authentication": "Debug",
"Microsoft.IdentityModel": "Debug",
"StellaOps": "Debug"
}
}
}

View File

@@ -0,0 +1,129 @@
param(
[ValidateSet("microservice", "reverseproxy")]
[string]$Mode = "microservice",
[string]$ComposeFile = "docker-compose.stella-ops.yml",
[int]$WaitTimeoutSeconds = 1200,
[int]$RecoveryAttempts = 2,
[int]$RecoveryWaitSeconds = 180
)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
$configPath = switch ($Mode) {
"microservice" { "./router-gateway-local.json" }
"reverseproxy" { "./router-gateway-local.reverseproxy.json" }
default { throw "Unsupported mode: $Mode" }
}
Write-Host "Redeploy mode: $Mode"
Write-Host "Gateway config: $configPath"
Write-Host "Compose file: $ComposeFile"
$env:ROUTER_GATEWAY_CONFIG = $configPath
function Invoke-Compose {
param(
[Parameter(Mandatory = $true)]
[string[]]$Args,
[switch]$IgnoreExitCode
)
& docker compose -f $ComposeFile @Args
$exitCode = $LASTEXITCODE
if (-not $IgnoreExitCode -and $exitCode -ne 0) {
throw "docker compose $($Args -join ' ') failed with exit code $exitCode."
}
return $exitCode
}
function Get-UnhealthyContainers {
$containers = & docker ps --filter "health=unhealthy" --format "{{.Names}}"
if ($LASTEXITCODE -ne 0) {
throw "Failed to query unhealthy containers."
}
$filtered = @($containers | Where-Object { -not [string]::IsNullOrWhiteSpace($_) -and $_ -like "stellaops-*" })
return [string[]]$filtered
}
function Get-ComposeServiceName {
param(
[Parameter(Mandatory = $true)]
[string]$ContainerName
)
$service = & docker inspect --format "{{ index .Config.Labels \"com.docker.compose.service\" }}" $ContainerName 2>$null
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($service)) {
return $null
}
return $service.Trim()
}
function Wait-ForContainerHealth {
param(
[Parameter(Mandatory = $true)]
[string]$ContainerName,
[Parameter(Mandatory = $true)]
[int]$TimeoutSeconds
)
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
while ((Get-Date) -lt $deadline) {
$status = (& docker inspect --format "{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}" $ContainerName 2>$null).Trim()
if ($LASTEXITCODE -ne 0) {
return $false
}
if ($status -eq "healthy" -or $status -eq "none") {
return $true
}
Start-Sleep -Seconds 5
}
return $false
}
Invoke-Compose -Args @("down", "-v", "--remove-orphans") | Out-Null
$upExitCode = Invoke-Compose -Args @("up", "-d", "--wait", "--wait-timeout", $WaitTimeoutSeconds.ToString()) -IgnoreExitCode
if ($upExitCode -ne 0) {
Write-Warning "docker compose up returned exit code $upExitCode. Running unhealthy-service recovery."
}
for ($attempt = 1; $attempt -le $RecoveryAttempts; $attempt++) {
$unhealthyContainers = @(Get-UnhealthyContainers)
if ($unhealthyContainers.Count -eq 0) {
break
}
Write-Warning "Recovery attempt ${attempt}: unhealthy containers detected: $($unhealthyContainers -join ', ')"
$services = New-Object System.Collections.Generic.HashSet[string]([System.StringComparer]::OrdinalIgnoreCase)
foreach ($containerName in $unhealthyContainers) {
$serviceName = Get-ComposeServiceName -ContainerName $containerName
if (-not [string]::IsNullOrWhiteSpace($serviceName)) {
[void]$services.Add($serviceName)
}
}
foreach ($serviceName in $services) {
Write-Host "Recreating service: $serviceName"
Invoke-Compose -Args @("up", "-d", "--force-recreate", "--no-deps", $serviceName) | Out-Null
}
foreach ($containerName in $unhealthyContainers) {
[void](Wait-ForContainerHealth -ContainerName $containerName -TimeoutSeconds $RecoveryWaitSeconds)
}
}
$remainingUnhealthy = @(Get-UnhealthyContainers)
if ($remainingUnhealthy.Count -gt 0) {
throw "Redeploy completed with unresolved unhealthy containers: $($remainingUnhealthy -join ', ')"
}
Write-Host "Redeploy complete for mode '$Mode'."

View File

@@ -0,0 +1,102 @@
param(
[string]$RouterConfigPath = "devops/compose/router-gateway-local.json",
[string]$OpenApiPath = "devops/compose/openapi_current.json",
[string]$GatewayBaseUrl = "https://127.1.0.1",
[ValidateSet("Microservice", "ReverseProxy", "StaticFiles")]
[string]$RouteType = "Microservice",
[string]$OutputCsv = "devops/compose/openapi_routeprefix_smoke.csv"
)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
function Get-JsonFromFile {
param([Parameter(Mandatory = $true)][string]$Path)
if (-not (Test-Path -LiteralPath $Path)) {
throw "File not found: $Path"
}
return Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json
}
function Get-OpenApiPathMap {
param([Parameter(Mandatory = $true)]$OpenApi)
$map = @{}
foreach ($prop in $OpenApi.paths.PSObject.Properties) {
$map[$prop.Name] = $prop.Value
}
return $map
}
function Get-HttpStatusCode {
param(
[Parameter(Mandatory = $true)][string]$Url
)
$statusText = (& curl.exe -k -s -o NUL -w "%{http_code}" $Url).Trim()
if ($statusText -match "^\d{3}$") {
return [int]$statusText
}
return -1
}
$routerConfig = Get-JsonFromFile -Path $RouterConfigPath
$openApi = Get-JsonFromFile -Path $OpenApiPath
$openApiPathMap = Get-OpenApiPathMap -OpenApi $openApi
$openApiPaths = @($openApiPathMap.Keys)
$routes = @($routerConfig.Gateway.Routes | Where-Object { $_.Type -eq $RouteType })
$rows = New-Object System.Collections.Generic.List[object]
foreach ($route in $routes) {
$prefix = [string]$route.Path
$matches = @()
foreach ($candidate in $openApiPaths) {
if ($candidate.StartsWith($prefix, [System.StringComparison]::OrdinalIgnoreCase)) {
$operation = $openApiPathMap[$candidate]
if ($null -ne $operation -and $operation.PSObject.Properties.Match("get").Count -gt 0) {
$matches += $candidate
}
}
}
$selectedPath = $null
if ($matches.Count -gt 0) {
$selectedPath = ($matches | Sort-Object `
@{ Expression = { $_ -match '\{[^}]+\}' } }, `
@{ Expression = { $_ -match '(^|/)(startupz|readyz|livez)$' } }, `
@{ Expression = { $_.Length } }, `
@{ Expression = { $_ } })[0]
}
$status = $null
if ($null -ne $selectedPath) {
$url = "$GatewayBaseUrl$selectedPath"
$status = Get-HttpStatusCode -Url $url
}
$rows.Add([pscustomobject]@{
RouteType = $RouteType
RoutePath = $prefix
RouteTarget = [string]$route.TranslatesTo
SelectedOpenApiPath = $selectedPath
StatusCode = $status
})
}
$rows | Export-Csv -LiteralPath $OutputCsv -NoTypeInformation -Encoding UTF8
$statusSummary = $rows |
Where-Object { $null -ne $_.StatusCode } |
Group-Object -Property StatusCode |
Sort-Object { [int]$_.Name } |
ForEach-Object { "$($_.Name)=$($_.Count)" }
Write-Host "routes_total=$($routes.Count)"
Write-Host "routes_with_selected_get=$(@($rows | Where-Object { $_.SelectedOpenApiPath }).Count)"
Write-Host "status_summary=$($statusSummary -join ',')"
Write-Host "output_csv=$OutputCsv"

View File

@@ -0,0 +1,326 @@
{
"/api/v1/timeline": {
"get": {
"operationId": "timelineindexer_api_v1_timeline_GET",
"tags": [
"timeline"
],
"summary": "List timeline events",
"description": "Returns timeline events filtered by tenant and optional query parameters.",
"security": [
{
"BearerAuth": [
],
"OAuth2": [
"timeline:read"
]
}
],
"x-stellaops-gateway-auth": {
"allowAnonymous": false,
"requiresAuthentication": true,
"source": "AspNetMetadata",
"policies": [
"timeline:read"
],
"claimRequirements": [
{
"type": "scope",
"value": "timeline:read"
}
]
},
"x-stellaops-timeout": {
"effectiveSeconds": 30,
"source": "endpoint",
"precedence": [
"endpointOverride",
"serviceDefault",
"gatewayRouteDefault",
"gatewayGlobalCap"
],
"endpointSeconds": 30,
"gatewayRouteDefaultSeconds": 30,
"gatewayGlobalCapSeconds": 120
},
"x-stellaops-timeout-seconds": 30,
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/timelineindexer_System_Collections_Generic_IReadOnlyList_1_StellaOps_TimelineIndexer_Core_Models_TimelineEventView_StellaOps_TimelineIndexer_Core_Version_1_0_0_0_Culture_neutral_PublicKeyToken_null"
}
}
}
},
"400": {
"description": "Bad Request"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"404": {
"description": "Not Found"
},
"422": {
"description": "Validation Error"
},
"504": {
"description": "Gateway timeout"
},
"500": {
"description": "Internal Server Error"
}
}
}
},
"/api/v1/timeline/events": {
"post": {
"operationId": "timelineindexer_api_v1_timeline_events_POST",
"tags": [
"timeline"
],
"summary": "Ingest timeline event",
"description": "Queues an event ingestion request for asynchronous timeline indexing.",
"security": [
{
"BearerAuth": [
],
"OAuth2": [
"timeline:write"
]
}
],
"x-stellaops-gateway-auth": {
"allowAnonymous": false,
"requiresAuthentication": true,
"source": "AspNetMetadata",
"policies": [
"timeline:write"
],
"claimRequirements": [
{
"type": "scope",
"value": "timeline:write"
}
]
},
"x-stellaops-timeout": {
"effectiveSeconds": 30,
"source": "endpoint",
"precedence": [
"endpointOverride",
"serviceDefault",
"gatewayRouteDefault",
"gatewayGlobalCap"
],
"endpointSeconds": 30,
"gatewayRouteDefaultSeconds": 30,
"gatewayGlobalCapSeconds": 120
},
"x-stellaops-timeout-seconds": 30,
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/timelineindexer_TimelineIngestAcceptedResponse"
}
}
}
},
"400": {
"description": "Bad Request"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"404": {
"description": "Not Found"
},
"422": {
"description": "Validation Error"
},
"504": {
"description": "Gateway timeout"
},
"500": {
"description": "Internal Server Error"
}
}
}
},
"/api/v1/timeline/{eventId}": {
"get": {
"operationId": "timelineindexer_api_v1_timeline_{eventId}_GET",
"tags": [
"timeline"
],
"summary": "Get timeline event",
"description": "Returns a single timeline event by event identifier for the current tenant.",
"security": [
{
"BearerAuth": [
],
"OAuth2": [
"timeline:read"
]
}
],
"x-stellaops-gateway-auth": {
"allowAnonymous": false,
"requiresAuthentication": true,
"source": "AspNetMetadata",
"policies": [
"timeline:read"
],
"claimRequirements": [
{
"type": "scope",
"value": "timeline:read"
}
]
},
"x-stellaops-timeout": {
"effectiveSeconds": 30,
"source": "endpoint",
"precedence": [
"endpointOverride",
"serviceDefault",
"gatewayRouteDefault",
"gatewayGlobalCap"
],
"endpointSeconds": 30,
"gatewayRouteDefaultSeconds": 30,
"gatewayGlobalCapSeconds": 120
},
"x-stellaops-timeout-seconds": 30,
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/timelineindexer_StellaOps_TimelineIndexer_Core_Models_TimelineEventView"
}
}
}
},
"400": {
"description": "Bad Request"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"404": {
"description": "Not Found"
},
"422": {
"description": "Validation Error"
},
"504": {
"description": "Gateway timeout"
},
"500": {
"description": "Internal Server Error"
}
}
}
},
"/api/v1/timeline/{eventId}/evidence": {
"get": {
"operationId": "timelineindexer_api_v1_timeline_{eventId}_evidence_GET",
"tags": [
"timeline"
],
"summary": "Get event evidence",
"description": "Returns evidence linkage for a timeline event, including bundle and attestation references.",
"security": [
{
"BearerAuth": [
],
"OAuth2": [
"timeline:read"
]
}
],
"x-stellaops-gateway-auth": {
"allowAnonymous": false,
"requiresAuthentication": true,
"source": "AspNetMetadata",
"policies": [
"timeline:read"
],
"claimRequirements": [
{
"type": "scope",
"value": "timeline:read"
}
]
},
"x-stellaops-timeout": {
"effectiveSeconds": 30,
"source": "endpoint",
"precedence": [
"endpointOverride",
"serviceDefault",
"gatewayRouteDefault",
"gatewayGlobalCap"
],
"endpointSeconds": 30,
"gatewayRouteDefaultSeconds": 30,
"gatewayGlobalCapSeconds": 120
},
"x-stellaops-timeout-seconds": 30,
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/timelineindexer_StellaOps_TimelineIndexer_Core_Models_TimelineEvidenceView"
}
}
}
},
"400": {
"description": "Bad Request"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"404": {
"description": "Not Found"
},
"422": {
"description": "Validation Error"
},
"504": {
"description": "Gateway timeout"
},
"500": {
"description": "Internal Server Error"
}
}
}
}
}

View File

@@ -0,0 +1,14 @@
[
"/api/v1/timeline",
"/api/v1/timeline/events",
"/api/v1/timeline/export/{exportId}",
"/api/v1/timeline/export/{exportId}/download",
"/api/v1/timeline/replay/{replayId}",
"/api/v1/timeline/replay/{replayId}/cancel",
"/api/v1/timeline/{correlationId}",
"/api/v1/timeline/{correlationId}/critical-path",
"/api/v1/timeline/{correlationId}/export",
"/api/v1/timeline/{correlationId}/replay",
"/api/v1/timeline/{eventId}",
"/api/v1/timeline/{eventId}/evidence"
]

View File

@@ -0,0 +1,253 @@
{
"refs": [
"#/components/schemas/timelineindexer_System_Collections_Generic_IReadOnlyList_1_StellaOps_TimelineIndexer_Core_Models_TimelineEventView_StellaOps_TimelineIndexer_Core_Version_1_0_0_0_Culture_neutral_PublicKeyToken_null",
"#/components/schemas/timelineindexer_TimelineIngestAcceptedResponse",
"#/components/schemas/timelineindexer_StellaOps_TimelineIndexer_Core_Models_TimelineEventView",
"#/components/schemas/timelineindexer_StellaOps_TimelineIndexer_Core_Models_TimelineEvidenceView"
],
"components": {
"schemas": {
"timelineindexer_System_Collections_Generic_IReadOnlyList_1_StellaOps_TimelineIndexer_Core_Models_TimelineEventView_StellaOps_TimelineIndexer_Core_Version_1_0_0_0_Culture_neutral_PublicKeyToken_null": {
"type": "array",
"items": {
"type": "object",
"properties": {
"actor": {
"type": "string"
},
"attestationDigest": {
"type": "string"
},
"attestationSubject": {
"type": "string"
},
"attributes": {
"type": "array",
"items": {
"type": "object",
"properties": {
"key": {
"type": "string"
},
"value": {
"type": "string"
}
}
}
},
"bundleDigest": {
"type": "string"
},
"bundleId": {
"type": [
"string",
"null"
],
"format": "uuid"
},
"correlationId": {
"type": "string"
},
"eventId": {
"type": "string"
},
"eventSeq": {
"type": "integer"
},
"eventType": {
"type": "string"
},
"manifestUri": {
"type": "string"
},
"normalizedPayloadJson": {
"type": "string"
},
"occurredAt": {
"type": "string",
"format": "date-time"
},
"payloadHash": {
"type": "string"
},
"rawPayloadJson": {
"type": "string"
},
"receivedAt": {
"type": "string",
"format": "date-time"
},
"severity": {
"type": "string"
},
"source": {
"type": "string"
},
"tenantId": {
"type": "string"
},
"traceId": {
"type": "string"
}
},
"required": [
"eventId",
"eventSeq",
"eventType",
"occurredAt",
"receivedAt",
"severity",
"source",
"tenantId"
]
},
"$schema": "https://json-schema.org/draft/2020-12/schema"
},
"timelineindexer_TimelineIngestAcceptedResponse": {
"type": "object",
"properties": {
"status": {
"type": "string"
}
},
"required": [
"status"
],
"$schema": "https://json-schema.org/draft/2020-12/schema"
},
"timelineindexer_StellaOps_TimelineIndexer_Core_Models_TimelineEventView": {
"type": "object",
"properties": {
"actor": {
"type": "string"
},
"attestationDigest": {
"type": "string"
},
"attestationSubject": {
"type": "string"
},
"attributes": {
"type": "array",
"items": {
"type": "object",
"properties": {
"key": {
"type": "string"
},
"value": {
"type": "string"
}
}
}
},
"bundleDigest": {
"type": "string"
},
"bundleId": {
"type": [
"string",
"null"
],
"format": "uuid"
},
"correlationId": {
"type": "string"
},
"eventId": {
"type": "string"
},
"eventSeq": {
"type": "integer"
},
"eventType": {
"type": "string"
},
"manifestUri": {
"type": "string"
},
"normalizedPayloadJson": {
"type": "string"
},
"occurredAt": {
"type": "string",
"format": "date-time"
},
"payloadHash": {
"type": "string"
},
"rawPayloadJson": {
"type": "string"
},
"receivedAt": {
"type": "string",
"format": "date-time"
},
"severity": {
"type": "string"
},
"source": {
"type": "string"
},
"tenantId": {
"type": "string"
},
"traceId": {
"type": "string"
}
},
"required": [
"eventId",
"eventSeq",
"eventType",
"occurredAt",
"receivedAt",
"severity",
"source",
"tenantId"
],
"$schema": "https://json-schema.org/draft/2020-12/schema"
},
"timelineindexer_StellaOps_TimelineIndexer_Core_Models_TimelineEvidenceView": {
"type": "object",
"properties": {
"attestationDigest": {
"type": "string"
},
"attestationSubject": {
"type": "string"
},
"bundleDigest": {
"type": "string"
},
"bundleId": {
"type": [
"string",
"null"
],
"format": "uuid"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"eventId": {
"type": "string"
},
"manifestUri": {
"type": "string"
},
"tenantId": {
"type": "string"
}
},
"required": [
"createdAt",
"eventId",
"tenantId"
],
"$schema": "https://json-schema.org/draft/2020-12/schema"
}
}
}
}

View File

@@ -0,0 +1,185 @@
# Sprint 20260221.044 - Router Valkey Microservice Transport Pilot (TimelineIndexer)
## Topic & Scope
- Convert one small service (TimelineIndexer WebService) from Gateway reverse proxy to Router microservice transport using Valkey-backed messaging.
- Keep activation controlled by Docker Compose settings so the local stack can switch between reverse proxy and microservice modes without code edits.
- Introduce a generic DI routine (`AddRouterMicroservice()`) that binds service router options and transport registration from configuration.
- Working directory: `src/Router/`.
- Cross-module edits explicitly allowed for this sprint: `src/TimelineIndexer/StellaOps.TimelineIndexer/StellaOps.TimelineIndexer.WebService`, `devops/compose`, `docs/modules/router`, `docs/modules/timeline-indexer`, `src/AdvisoryAI/StellaOps.AdvisoryAI.WebService`, `src/AdvisoryAI/StellaOps.AdvisoryAI.Hosting`.
- Expected evidence: targeted unit/integration tests for DI and transport registration, compose smoke run logs, gateway route validation evidence, updated docs.
## Dependencies & Concurrency
- Depends on existing Gateway messaging transport implementation in `src/Router/StellaOps.Gateway.WebService`.
- Depends on Valkey infrastructure service in `devops/compose/docker-compose.stella-ops.yml`.
- Safe concurrency:
- DI helper implementation and docs can run in parallel.
- Compose wiring and TimelineIndexer adoption can run in parallel after DI contracts are agreed.
- Gateway route cutover must run after endpoint-path compatibility is confirmed.
## Documentation Prerequisites
- `docs/modules/router/architecture.md`
- `docs/modules/router/messaging-valkey-transport.md`
- `docs/modules/router/webservice-integration-guide.md`
- `docs/modules/router/aspnet-endpoint-bridge.md`
- `docs/modules/timeline-indexer/architecture.md`
- `docs/modules/timeline-indexer/guides/timeline.md`
- `docs/modules/gateway/architecture.md`
- `docs/modules/gateway/openapi.md`
## Delivery Tracker
### RVM-01 - Baseline and path-compatibility audit for TimelineIndexer
Status: DONE
Dependency: none
Owners: Project Manager, Developer
Task description:
- Capture current route behavior for TimelineIndexer in local compose and Router gateway route table.
- Document method/path compatibility between gateway route entries and endpoints discovered from TimelineIndexer ASP.NET routes.
- Produce a cutover-safe mapping table that lists paths that can switch directly and paths that require alias endpoints or route migration.
Completion criteria:
- [x] Mapping table committed with explicit `current_path -> target_microservice_path` entries.
- [x] All TimelineIndexer routes selected for pilot have deterministic method/path compatibility with Router endpoint identity rules.
- [x] Risk note recorded for any incompatible prefixes (for example `/api/v1/timeline` vs `/timeline`).
### RVM-02 - Add generic `AddRouterMicroservice()` DI routine
Status: DONE
Dependency: RVM-01
Owners: Developer
Task description:
- Implement a new Router.AspNet helper that wraps `TryAddStellaRouter` plus transport-client registration based on configuration.
- The routine must support at least `InMemory`, `Tcp`, `Certificate`, and `Messaging` transport selection from bound options.
- For messaging mode, register both messaging backend plugin (`ValkeyTransportPlugin`) and router messaging client (`AddMessagingTransportClient`) using a deterministic configuration section.
Completion criteria:
- [x] New DI routine exists under `src/Router/__Libraries/StellaOps.Router.AspNet` with XML docs and option validation.
- [x] Existing behavior remains backward compatible for services that continue to call `TryAddStellaRouter`.
- [x] Unit tests cover transport selection and misconfiguration failure modes.
### RVM-03 - Compose-driven Valkey transport activation
Status: DONE
Dependency: RVM-02
Owners: Developer, DevOps
Task description:
- Add compose-level environment variables for Router Gateway messaging enablement and queue/connection values.
- Add compose-level environment variables for TimelineIndexer router enablement and messaging transport selection.
- Ensure the same compose file can run both modes by toggling flags without code changes.
Completion criteria:
- [x] `devops/compose/docker-compose.stella-ops.yml` contains required gateway and timeline indexer environment keys.
- [x] Messaging connection resolves to `cache.stella-ops.local:6379` in compose network.
- [x] Toggle instructions are documented and tested for `reverse_proxy` mode and `microservice_messaging` mode.
### RVM-04 - TimelineIndexer pilot adoption of generic DI
Status: DONE
Dependency: RVM-03
Owners: Developer
Task description:
- Update TimelineIndexer WebService startup to use `AddRouterMicroservice()` and keep `TryUseStellaRouter` plus endpoint refresh behavior.
- Ensure startup fails fast when router is enabled but required transport settings are missing.
- Keep rollback path simple by honoring compose flags that disable router integration.
Completion criteria:
- [x] TimelineIndexer registers messaging transport client when compose enables messaging mode.
- [x] Service startup logs indicate successful HELLO registration to gateway in messaging mode.
- [x] Reverse-proxy-only deployment still boots unchanged when router is disabled.
### RVM-05 - Gateway route migration strategy (canary then flip)
Status: DONE
Dependency: RVM-04
Owners: Developer, DevOps
Task description:
- Introduce a canary microservice route for TimelineIndexer that does not break current UI/API paths.
- Validate canary behavior end-to-end, then flip canonical route entries from `ReverseProxy` to `Microservice` in `router-gateway-local.json`.
- Keep a documented rollback that restores reverse proxy by route-table revert and compose flag switch.
Completion criteria:
- [x] Canary microservice route is reachable and returns expected TimelineIndexer responses.
- [x] Canonical route flip has before/after evidence with no auth-header regression.
- [x] Rollback procedure is documented with exact config keys and file diffs.
### RVM-06 - Test and verification matrix for pilot
Status: DONE
Dependency: RVM-05
Owners: Test Automation, Developer
Task description:
- Add targeted tests for the new DI helper and messaging transport registration.
- Run Router gateway messaging integration tests and TimelineIndexer smoke tests under compose with Valkey.
- Capture deterministic evidence: command lines, pass/fail counts, and route-level request/response samples.
Completion criteria:
- [x] New tests exist for `AddRouterMicroservice()` with transport-mode assertions.
- [x] Existing Router messaging integration tests pass without regression.
- [x] Compose smoke verification proves request flow `Gateway -> Router microservice transport -> TimelineIndexer`.
### RVM-07 - Documentation sync for transport migration
Status: DONE
Dependency: RVM-06
Owners: Documentation Author, Developer
Task description:
- Update router integration docs to include the new generic DI routine and compose-driven transport activation pattern.
- Update timeline indexer docs with actual externally exposed paths and pilot routing strategy.
- Add Decisions and Risks links to changed docs for auditability.
Completion criteria:
- [x] `docs/modules/router` and `docs/modules/timeline-indexer` are updated to match implemented behavior.
- [x] Examples show Valkey messaging setup from compose.
- [x] Sprint Decisions and Risks section links all updated docs.
### RVM-08 - OpenAI adapter exposure workstream (AdvisoryAI)
Status: DONE
Dependency: RVM-02
Owners: Developer, Product Manager
Task description:
- Validate required exposure model: plugin-capability exposure, API endpoint exposure, or OpenAI-compatible endpoint surface.
- Reuse existing unified adapter pattern (`LlmPluginAdapterFactory`) to expose provider capabilities deterministically.
- Add gateway route exposure for selected AdvisoryAI adapter endpoints after contract is finalized.
Completion criteria:
- [x] Exposure contract is documented with explicit endpoint list and auth scopes.
- [x] AdvisoryAI registers adapter exposure services and endpoints according to the approved contract.
- [x] Gateway route table includes new adapter exposure paths with security constraints.
## Execution Log
| Date (UTC) | Update | Owner |
| --- | --- | --- |
| 2026-02-21 | Sprint created from router microservice transport investigation; awaiting implementation staffing. | Project Manager |
| 2026-02-21 | Implemented `AddRouterMicroservice()` in Router.AspNet with auto transport registration and Valkey messaging path. | Developer |
| 2026-02-21 | Updated TimelineIndexer WebService to use generic DI helper and added `/api/v1/timeline*` alias endpoints for microservice path matching. | Developer |
| 2026-02-21 | Added compose env toggles for gateway messaging + TimelineIndexer router config; flipped `/api/v1/timeline` route to `Microservice`. | Developer |
| 2026-02-21 | Added pilot mapping doc and router/timeline docs updates; ran Router.AspNet tests and TimelineIndexer build successfully. | Developer |
| 2026-02-21 | Attempted gateway messaging integration verification, but test runner ignored filter and unrelated pre-existing gateway tests failed (`IdentityHeaderPolicyMiddlewareTests`). | Developer |
| 2026-02-21 | Refactored `AddRouterMicroservice()` to plugin-based transport registration (`RouterTransportPluginLoader`) and removed direct transport references from `StellaOps.Router.AspNet`. | Developer |
| 2026-02-21 | Added `MessagingTransportPlugin` for Router messaging transport and corrected compose `TIMELINE_*` env placement to `timeline-indexer-web`. | Developer |
| 2026-02-21 | Ran xUnit v3 class-filtered gateway messaging integration tests directly via test executable (`MessagingTransportIntegrationTests`): 6/6 passed; full suite still contains 9 unrelated failures (identity-header policy + websocket redirect tests). | Developer |
| 2026-02-21 | Verified compose messaging flow with Valkey (`POST /api/v1/timeline/events -> 202`) and gateway logs (`Dispatching ... via Messaging`, `TargetService=timelineindexer`). | Developer |
| 2026-02-21 | Verified router-disabled boot path by compose toggle (`TIMELINE_ROUTER_ENABLED=false`): TimelineIndexer starts without router registration logs. | Developer |
| 2026-02-21 | Fixed plugin packaging gap for TimelineIndexer publish by copying plugin transitive dependencies (`StellaOps.Messaging`, Valkey transport dependencies) and validated startup in messaging mode from publish output. | Developer |
| 2026-02-21 | Fixed microservice HELLO schema propagation for messaging transport, added schema-aware transport tests, rebuilt `timeline-indexer-web`, and verified default compose OpenAPI now shows TimelineIndexer endpoints with summaries/descriptions and 4 exported JSON schemas under `components.schemas`. | Developer |
| 2026-02-22 | Closed `RVM-05`: validated canary path (`GET /timelineindexer/api/v1/timeline -> 200`), canonical path (`GET /api/v1/timeline -> 200`, `POST /api/v1/timeline/events -> 202`), and gateway OpenAPI availability (`GET /openapi.json -> 200`). | Developer |
## Archive Note
- Archive readiness confirmed on 2026-02-22: all tasks are `DONE`, no `TODO/DOING/BLOCKED` items remain.
## Decisions & Risks
- Decision needed: canonical pilot path strategy. Recommended: canary route first, then canonical flip after validation to avoid breaking `/api/v1/timeline` consumers.
- Risk: Router microservice dispatch does not strip prefixes like reverse proxy. Mitigation: complete RVM-01 mapping and add compatible aliases before canonical route flip.
- Risk: Compose currently has no service-level router env blocks for most services. Mitigation: keep changes scoped to TimelineIndexer and Gateway for pilot; do not mass-convert.
- Risk: "OpenAI adapter exposure" can mean multiple surfaces (provider plugin metadata vs OpenAI-compatible inbound API). Mitigation: lock contract in RVM-08 before endpoint implementation.
- Risk: Existing local working tree contains unrelated edits. Mitigation: this sprint will touch only scoped files and will not revert unrelated changes.
- Risk: Router.AspNet direct transport references increase service coupling and build surface. Mitigation: use plugin discovery from configuration (`TransportPlugins:*`, `Messaging:Transport`) and keep transport assemblies optional at app/service packaging level.
- Risk: `dotnet test --filter` is ignored for xUnit v3 MTP execution in this repo. Mitigation: run filtered gateway messaging tests via the xUnit v3 test executable (`-class ...MessagingTransportIntegrationTests`) for deterministic scope evidence.
- Risk: Existing timeline-indexer container image may miss plugin transitive assemblies and restart when router messaging mode is enabled. Mitigation: use updated publish packaging and rebuild image before enabling `TIMELINE_ROUTER_ENABLED=true` in compose.
- Docs links:
- `docs/modules/router/timelineindexer-microservice-pilot.md`
- `docs/modules/router/webservice-integration-guide.md`
- `docs/modules/router/messaging-valkey-transport.md`
- `docs/modules/timeline-indexer/guides/timeline.md`
- `docs/modules/timeline-indexer/architecture.md`
## Next Checkpoints
- 2026-02-22: Complete RVM-01 path audit and confirm canary route.
- 2026-02-23: Land `AddRouterMicroservice()` with unit tests (RVM-02).
- 2026-02-24: Compose activation and TimelineIndexer pilot wiring in dev stack (RVM-03, RVM-04).
- 2026-02-25: Canary validation and route flip decision (RVM-05).
- 2026-02-26: Docs and OpenAI adapter exposure contract checkpoint (RVM-07, RVM-08).

View File

@@ -0,0 +1,226 @@
# Sprint 20260221.045 - Router Valkey Microservice Transport Rollout (All WebServices)
## Topic & Scope
- Migrate StellaOps webservices exposed through Gateway API routes from direct reverse proxy routing to Router microservice transport over Valkey messaging.
- Standardize service startup integration on `AddRouterMicroservice()` with transport activation fully controlled by environment variables and Docker Compose settings.
- Enforce plugin-only transport loading for both router transport and messaging backend; no hard transport coupling in webservice DI routines.
- Ensure Gateway OpenAPI preview (`/openapi.json`) includes connected microservice endpoints with operation summary/description and JSON Schema components.
- Working directory: `src/Router/`.
- Cross-module edits explicitly allowed for this sprint: `src/**/StellaOps.*.WebService`, `src/**/StellaOps.*.Worker`, `devops/compose`, `docs/modules/router`, `docs/modules/gateway`, module dossiers under `docs/modules/**`, and service-level `TASKS.md` files where touched.
- Expected evidence: targeted tests per migration wave, compose run logs, route-table diff evidence, OpenAPI path/schema verification reports, rollback playbook validation.
## Dependencies & Concurrency
- Depends on pilot groundwork from `docs-archived/implplan/SPRINT_20260221_044_Router_valkey_microservice_transport_timelineindexer_pilot.md`.
- Depends on stable Valkey service and Gateway messaging transport runtime in default compose.
- Safe concurrency is by independent migration waves grouped by module domain, with a strict freeze on canonical route flips until each wave passes OpenAPI and smoke verification.
- Shared contracts (`AddRouterMicroservice`, plugin directory conventions, messaging option keys) must remain stable during wave execution to avoid cross-wave churn.
## Documentation Prerequisites
- `docs/modules/router/architecture.md`
- `docs/modules/router/messaging-valkey-transport.md`
- `docs/modules/router/webservice-integration-guide.md`
- `docs/modules/router/aspnet-endpoint-bridge.md`
- `docs/modules/gateway/architecture.md`
- `docs/modules/gateway/openapi.md`
- `docs/modules/platform/architecture-overview.md`
- Module dossier for each service before its task moves to `DOING`.
## Delivery Tracker
### RMW-01 - Global webservice inventory and migration matrix
Status: DONE
Dependency: none
Owners: Project Manager, Developer
Task description:
- Build the authoritative migration matrix from current Gateway routes and compose services.
- Enumerate each service host, current route prefixes, auth requirements, rollout wave, and rollback switch.
- Record whether each service already has `Router` configuration, `AddRouterMicroservice()` adoption, and plugin publish packaging.
- Publish the matrix at `docs/modules/router/webservices-valkey-rollout-matrix.md` and link it from router module documentation.
Completion criteria:
- [x] Migration matrix lists every current reverse-proxy API surface and target microservice route owner.
- [x] Each service has an assigned rollout wave and explicit acceptance owner.
- [x] Matrix is linked in this sprint and in router module docs.
### RMW-02 - Guardrail contract for plugin-only transport activation
Status: DONE
Dependency: RMW-01
Owners: Developer
Task description:
- Codify required contract for all services: transport registration via `RouterTransportPluginLoader`, messaging backend via plugin, and compose/env-driven selection.
- Ensure no service DI path introduces direct references that hardwire `Messaging/Tcp/Udp/Tls` registrations.
- Add or extend tests to fail when required plugin assemblies or resolved transport sections are missing.
Completion criteria:
- [x] Contract doc states required configuration keys and forbidden hard-coupling patterns.
- [x] Router-level tests cover missing-plugin and missing-section failures.
- [x] Migration wave PR checklist includes this guardrail.
### RMW-03 - Compose defaults and env key standardization for all services
Status: DONE
Dependency: RMW-02
Owners: Developer, DevOps
Task description:
- Standardize environment key patterns across services for router enablement, gateway target, transport plugin directories, messaging transport selection, and Valkey connection.
- Keep reverse-proxy fallback toggles available per service for rollback.
- Validate that compose defaults start with Valkey messaging enabled at gateway and service-level router enablement controlled explicitly.
Completion criteria:
- [x] Compose files include normalized router/messaging key sets per service.
- [x] Each migrated service has a documented disable toggle for rollback.
- [x] Compose lint/start validation passes for the edited stack.
### RMW-04 - Migration Wave A (low-coupling API services)
Status: DONE
Dependency: RMW-03
Owners: Developer, Test Automation
Task description:
- Migrate the first low-coupling services to microservice transport to de-risk bulk rollout.
- Candidate scope: `advisoryai`, `binaryindex`, `integrations`, `opsmemory`, `replay`, `unknowns`, `symbols`, `packsregistry`, `registry-token`, `smremote`, `airgap-controller`, `airgap-time`.
- For each service in wave: adopt `AddRouterMicroservice()`, validate plugin packaging, switch route entry to `Microservice` after canary validation.
Completion criteria:
- [x] All Wave A services dispatch through messaging with no reverse-proxy dependency for their API routes.
- [x] Gateway logs show `via Messaging` dispatch for each Wave A service.
- [x] Wave A endpoints appear in gateway OpenAPI with operation metadata and schemas.
### RMW-05 - Migration Wave B (evidence and trust plane services)
Status: DONE
Dependency: RMW-04
Owners: Developer, Test Automation
Task description:
- Migrate higher-sensitivity services where evidence integrity and signing workflows are involved.
- Candidate scope: `attestor`, `evidencelocker`, `signer`, `authority`, `exportcenter`, `issuerdirectory`.
- Require stricter verification around auth propagation, identity headers, and route-level policy behavior.
Completion criteria:
- [x] Wave B services pass route-level auth and policy checks in microservice mode.
- [x] Evidence/trust endpoints remain behavior-compatible against baseline requests.
- [x] OpenAPI output includes Wave B schemas and descriptions without regressions.
### RMW-06 - Migration Wave C (orchestration and policy control plane)
Status: DONE
Dependency: RMW-05
Owners: Developer, Test Automation
Task description:
- Migrate orchestration/control services that have fan-out dependencies and high request volume.
- Candidate scope: `orchestrator`, `scheduler`, `taskrunner`, `policy-engine`, `policy-gateway`, `riskengine`, `platform`.
- Validate request timeout, cancellation, and heartbeat behavior under expected load patterns.
Completion criteria:
- [x] Wave C services respond through messaging transport with stable p95 latency targets defined in evidence.
- [x] Cancellation and timeout semantics are verified for at least one endpoint per service.
- [x] No required canonical API route for Wave C remains reverse-proxy only.
### RMW-07 - Migration Wave D (scanner/graph/feed and operational services)
Status: DONE
Dependency: RMW-06
Owners: Developer, Test Automation
Task description:
- Migrate remaining service surfaces with graph/feed/scanning and operational dashboards.
- Candidate scope: `scanner`, `concelier`, `excititor`, `vexhub`, `vexlens`, `reachgraph`, `cartographer`, `findings`, `sbomservice`, `vulnexplorer`, `doctor`, `doctor-scheduler`, `notify`, `notifier`, `gateway`.
- Confirm mixed protocol and payload-heavy endpoints remain compatible after route conversion.
Completion criteria:
- [x] Wave D services have successful microservice dispatch and health heartbeat registration.
- [x] High-volume endpoints complete smoke scenarios without transport errors.
- [x] Route table contains explicit rollback markers removed only after acceptance.
### RMW-08 - Gateway route conversion completion and rollback automation
Status: DONE
Dependency: RMW-07
Owners: Developer, DevOps
Task description:
- Convert canonical route entries from `ReverseProxy` to `Microservice` per accepted wave, preserving static/file and external authority routes where required.
- Add deterministic rollback script or documented command sequence to restore previous route modes by wave.
- Ensure route ordering and prefix specificity remain deterministic after conversions.
Completion criteria:
- [x] All internal API routes eligible for migration are `Microservice` routes.
- [x] Rollback procedure is tested for at least one service per wave.
- [x] Route-table diff evidence is attached in sprint execution log.
### RMW-09 - Gateway OpenAPI completeness and schema quality gate
Status: DONE
Dependency: RMW-08
Owners: Developer, Documentation Author
Task description:
- Validate gateway OpenAPI output for all migrated services.
- Enforce per-endpoint checks: route presence, operation summary, description, response schema refs, and schema objects in `components.schemas`.
- Include AdvisoryAI OpenAI adapter exposure endpoints in this gate and verify contract visibility in OpenAPI output.
Completion criteria:
- [x] OpenAPI verification report covers every migrated service prefix.
- [x] Missing summary/description/schema defects are fixed or explicitly tracked as BLOCKED.
- [x] AdvisoryAI adapter exposure endpoints are present and documented with schemas.
### RMW-10 - Deterministic QA, resilience, and rollout decision gate
Status: DONE
Dependency: RMW-09
Owners: QA, Test Automation, Project Manager
Task description:
- Execute tiered validation per module surface: targeted tests, compose smoke requests, and failure-path checks (timeouts, cancellation, service restart heartbeat recovery).
- Capture deterministic evidence with exact commands and outputs for each wave.
- Hold default-flip decision until all unresolved migration blockers are closed or formally accepted.
Completion criteria:
- [x] QA evidence exists for every wave and includes behavioral checks, not only build/test totals.
- [x] No open `BLOCKED` item remains for migrated canonical routes at gate sign-off.
- [x] Default mode decision and rollback guardrails are recorded with approvers.
### RMW-11 - Documentation and runbook synchronization
Status: DONE
Dependency: RMW-10
Owners: Documentation Author, Developer
Task description:
- Update router/gateway/service docs to reflect final microservice routing model, compose toggles, and operation/rollback runbooks.
- Add migration cookbook for onboarding new services to Valkey messaging microservice mode.
- Sync Decisions & Risks links and archive obsolete reverse-proxy-first guidance.
Completion criteria:
- [x] Router and gateway docs match implemented default behavior.
- [x] Service docs include router activation and plugin packaging requirements.
- [x] Runbook steps for incident rollback are validated and linked.
## Execution Log
| Date (UTC) | Update | Owner |
| --- | --- | --- |
| 2026-02-21 | Sprint created for all-webservices migration from reverse proxy to Router microservice transport over Valkey, based on TimelineIndexer pilot outcomes. | Project Manager |
| 2026-02-21 | Completed `RMW-01`: published full host/path migration matrix (`webservices-valkey-rollout-matrix.md`) from gateway route inventory, assigned waves/owners/rollback switches, and linked matrix in router docs. | Developer |
| 2026-02-21 | Completed `RMW-02`: added transport guardrail contract + PR checklist (`microservice-transport-guardrails.md`) and extended Router.AspNet tests for missing-section and missing-plugin failures (30/30 passing). | Developer |
| 2026-02-22 | Completed `RMW-03` through `RMW-08`: standardized compose router/messaging defaults for all webservices, migrated service startup to `AddRouterMicroservice()`, and converted route table to `Microservice` mode for internal APIs (110 microservice routes, 7 expected reverse-proxy routes retained for authority/static flows). | Developer |
| 2026-02-22 | Completed `RMW-09`: validated gateway OpenAPI aggregation after full migration (`/openapi.json` => 1861 paths, 901 schemas), including Timeline endpoints and AdvisoryAI OpenAI adapter schemas. | Developer |
| 2026-02-22 | Completed `RMW-10`: from-scratch compose bootstrap (`down -v` then `up -d`) plus route smoke (`110` microservice routes checked, `0` transport failures in 5xx range), timeline ingress/query verification (`GET /api/v1/timeline` => `200 []`). | Developer |
| 2026-02-22 | Completed `RMW-11`: synchronized router migration docs, valkey transport guide, integration guide, and module dossiers; validated rollback toggles via per-service `*_ROUTER_ENABLED` compose env controls. | Developer |
| 2026-02-22 | Post-gate correction: fixed `gateway` service generic env loading so compose-provided `Router__*` and `ASPNETCORE_URLS` are honored; container health recovered and `router:requests:gateway` queue registration confirmed. | Developer |
| 2026-02-22 | Final hard-reference cleanup: removed direct transport registrations from non-example runtime startup (`Gateway`, `Orchestrator`) to enforce plugin-only transport activation via configuration/env; rebuilt affected images and re-ran clean-stack validation. | Developer |
| 2026-02-22 | Final acceptance rerun: executed second clean bootstrap (`docker compose down -v --remove-orphans` + `up -d`), revalidated router OpenAPI discovery (`/.well-known/openapi`), OpenAPI aggregate (`/openapi.json` => `1861` paths / `901` schemas), and microservice route smoke (`110` routes, `0` 5xx). | Developer |
| 2026-02-22 | Added `rekor` as explicit `ReverseProxy` route (`/rekor -> http://rekor.stella-ops.local:3322`) and investigated remaining non-microservice path anomalies: authority/platform base-prefix probes return upstream `404` by design for undefined root endpoints; `/envsettings.json` upstream returns `500` due Platform DB error (`relation \"platform.environment_settings\" does not exist`). | Developer |
| 2026-02-22 | Authority stabilization follow-up: added Authority schema bootstrap SQL to compose Postgres init (`devops/compose/postgres-init/04-authority-schema.sql`) and adjusted gateway authority edge routing to keep Authority on microservice transport while adding protocol-specific reverse-proxy fallbacks for OpenIddict paths not currently exposed by endpoint discovery (`/.well-known`, `/connect/token`, `/connect/introspect`, `/connect/revoke`). | Developer |
| 2026-02-22 | Authority OIDC microservice cutover: added in-service OpenIddict bridge endpoints (`/connect/authorize`, `/connect/token`, `/connect/introspect`, `/connect/revoke`, `/well-known/openid-configuration`), switched gateway Authority protocol routes back to `Microservice`, and removed temporary reverse-proxy protocol fallbacks. | Developer |
| 2026-02-22 | Rekor runtime investigation and compose hardening: confirmed `rekor-tiles` fails without signer + Tessera backend flags; configured compose Rekor profile with explicit signer mount/startup flags, corrected internal HTTP port to `3322`, and split profile usage so `sigstore` is CLI-only while self-hosted Rekor uses `sigstore-local`. | Developer |
| 2026-02-22 | Archive readiness verification completed: all delivery tasks and completion criteria are `DONE`; no remaining `TODO/DOING/BLOCKED` items for this sprint. | Project Manager |
| 2026-02-22 | Archive metadata hygiene: normalized cross-sprint dependency link to archived pilot sprint path (`docs-archived/implplan/...044...`) so archived references are self-contained. | Project Manager |
## Decisions & Risks
- Decision resolved: wave ordering executed guardrail-first and completed through Waves A-D before final route/default gates.
- Risk: attempting one-shot migration across all services may destabilize local compose. Mitigation: strict wave-based rollout with explicit rollback checkpoints.
- Risk: accidental hard transport references in service DI. Mitigation: enforce plugin-only registration and tests from `RMW-02`.
- Risk: OpenAPI visibility gaps can hide incomplete metadata after migration. Mitigation: dedicated OpenAPI quality gate in `RMW-09`.
- Risk: gateway route conversion can break path precedence. Mitigation: route diff review and deterministic ordering checks in `RMW-08`.
- Risk: scope spans many modules and can drift from owning directory rules. Mitigation: this sprint explicitly authorizes listed cross-module edit zones and requires per-wave task scoping.
- Risk: `rekor-tiles` requires a Tessera GCP backend (`REKOR_GCP_BUCKET`, `REKOR_GCP_SPANNER`) plus ADC credentials to become healthy; without those, `sigstore-local` remains intentionally non-default for local stacks. This is non-blocking for Router migration acceptance.
- Risk: Authority OIDC bridge endpoints proxy to loopback Authority endpoints to preserve OpenIddict protocol behavior under microservice dispatch. Mitigation: bridge routes are explicit and limited to OIDC protocol paths, with no transport hard references introduced.
- Docs links:
- `docs/modules/router/webservices-valkey-rollout-matrix.md`
- `docs/modules/router/microservice-transport-guardrails.md`
- `docs/modules/router/README.md`
- `docs/modules/router/migration-guide.md`
## Next Checkpoints
- 2026-02-22: Sprint execution completed through `RMW-11` with clean compose bootstrap and OpenAPI validation.
- 2026-02-23: Optional hardening checkpoint: review non-blocking `rekor` profile restarts in local default stack.
- 2026-02-24: Optional follow-up checkpoint: curate additional per-operation smoke suite for authenticated business endpoints.
## Archive Note
- Archive readiness confirmed on 2026-02-22: all tasks are `DONE`, with non-blocking Rekor local backend prerequisites documented in `Decisions & Risks`.

View File

@@ -0,0 +1,177 @@
# Sprint 20260222.047 - Router Product Contract and Semantics Hardening
## Topic & Scope
- Establish Stella Router as a standalone product surface with explicit, versioned contracts for endpoint registration, authorization metadata, timeout semantics, and OpenAPI projection.
- Close current semantic misses where endpoint authorization and timeout intent are not represented or enforced consistently end-to-end.
- Deliver compatibility-safe behavior changes so existing Stella Ops services can adopt improvements without disruptive rewrites.
- Working directory: `src/Router/`.
- Cross-module edits explicitly allowed for this sprint: `docs/modules/router`, `docs/modules/gateway`, `src/Gateway/StellaOps.Gateway.WebService`, `src/**/__Tests`.
- Expected evidence: contract docs, targeted unit/integration tests, OpenAPI fixture diffs, compatibility matrix.
## Dependencies & Concurrency
- Depends on archived router migration outcomes:
- `docs-archived/implplan/SPRINT_20260221_044_Router_valkey_microservice_transport_timelineindexer_pilot.md`
- `docs-archived/implplan/SPRINT_20260221_045_Router_valkey_microservice_transport_all_webservices_rollout.md`
- Safe concurrency:
- Contract documentation and test-fixture drafting can run in parallel.
- Endpoint descriptor and timeout pipeline changes must be sequenced before OpenAPI generator changes.
- Compatibility harness updates must run after all semantic code changes land.
## Documentation Prerequisites
- `docs/modules/router/architecture.md`
- `docs/modules/router/messaging-valkey-transport.md`
- `docs/modules/router/webservice-integration-guide.md`
- `docs/modules/gateway/openapi.md`
- `docs/modules/gateway/architecture.md`
## Delivery Tracker
### RPC-01 - Router contract inventory and semantic gap matrix
Status: DONE
Dependency: none
Owners: Project Manager, Developer
Task description:
- Produce a contract inventory for Router product surfaces:
- Endpoint discovery and HELLO payload contracts.
- Gateway routing and authorization contracts.
- OpenAPI aggregation contracts.
- Transport timeout/cancellation contracts.
- Document precise mismatch points between intended semantics and current runtime behavior, including authorization metadata loss and timeout precedence ambiguity.
Completion criteria:
- [x] Contract inventory doc committed under `docs/modules/router`.
- [x] Gap matrix maps each mismatch to owning component and test target.
- [x] Each gap entry includes impact level and backward-compatibility risk.
### RPC-02 - Preserve full endpoint auth metadata through Router model boundaries
Status: DONE
Dependency: RPC-01
Owners: Developer
Task description:
- Extend Router endpoint metadata contracts so authorization semantics are preserved across discovery, HELLO transport, routing state, and OpenAPI generation.
- Ensure no required metadata is dropped during projection from ASP.NET-discovered descriptors to shared descriptors.
- Add compatibility-safe schema versioning for HELLO payload changes.
Completion criteria:
- [x] Endpoint metadata model includes required auth semantics for gateway policy and docs.
- [x] HELLO payload serialization remains deterministic and version-compatible.
- [x] Contract tests prove metadata survives discovery -> HELLO -> routing state.
### RPC-03 - Policy-aware ASP.NET authorization mapping
Status: DONE
Dependency: RPC-02
Owners: Developer
Task description:
- Replace synchronous-only policy extraction path with policy-aware mapping that resolves policy claims deterministically.
- Keep fallback behavior explicit when policy resolution fails.
- Enforce configurable missing-authorization behavior without silent privilege broadening.
Completion criteria:
- [x] Discovery path uses policy-aware claim mapping for ASP.NET endpoints.
- [x] Tests cover `RequireExplicit`, `WarnAndAllow`, and `AllowAuthenticated` behaviors.
- [x] Failure diagnostics identify unresolved policies and impacted endpoints.
### RPC-04 - OpenAPI security semantics correction
Status: DONE
Dependency: RPC-03
Owners: Developer, Documentation Author
Task description:
- Correct security mapping semantics so scopes and claim requirements are represented accurately.
- Ensure allow-anonymous endpoints and authenticated-without-scope endpoints are distinguishable in OpenAPI output.
- Align generated security schemes with Authority token semantics and gateway enforcement behavior.
Completion criteria:
- [x] OpenAPI security requirements are generated from effective claim semantics.
- [x] Scope value mapping is correct for OAuth2 requirements.
- [x] OpenAPI tests cover anonymous, auth-only, and scoped endpoints.
### RPC-05 - Timeout precedence and routing effective-timeout fix
Status: DONE
Dependency: RPC-02
Owners: Developer
Task description:
- Implement explicit timeout precedence:
- Endpoint override timeout.
- Service default timeout.
- Gateway route default timeout.
- Global gateway cap.
- Update routing decision generation and dispatch to use resolved endpoint-aware timeout deterministically.
Completion criteria:
- [x] Effective timeout resolution is centralized and unit-tested.
- [x] Dispatch timeout behavior follows precedence rules across transports.
- [x] Regression tests verify timeout, cancel, and 504 semantics.
### RPC-06 - OpenAPI timeout and response metadata publication
Status: DONE
Dependency: RPC-05
Owners: Developer, Documentation Author
Task description:
- Add Router-specific OpenAPI extension fields for timeout publication and document their meaning.
- Improve response modeling so generated responses reflect endpoint metadata where available, instead of static generic defaults only.
- Keep backward compatibility for consumers expecting current baseline fields.
Completion criteria:
- [x] `openapi.json` includes timeout metadata extension per operation.
- [x] Response metadata generation prefers endpoint-defined contracts.
- [x] Docs describe extension semantics and compatibility expectations.
### RPC-07 - Router product compatibility and conformance suite
Status: DONE
Dependency: RPC-04
Owners: Test Automation, Developer
Task description:
- Introduce router-product conformance tests validating:
- Metadata propagation.
- Security semantics.
- Timeout precedence.
- Transport parity (in-memory, messaging/Valkey).
- Add fixture-based approval tests to prevent semantic regression.
Completion criteria:
- [x] Conformance suite exists and runs in CI for Router libraries.
- [x] Failure output identifies contract area and owning component.
- [x] Baseline fixtures are deterministic and checked into repo.
### RPC-08 - Product docs and migration guidance sync
Status: DONE
Dependency: RPC-06
Owners: Documentation Author
Task description:
- Publish Router product contracts and migration guidance for service teams.
- Add explicit “old vs new semantics” sections with upgrade steps and fallback strategy.
- Link all changed docs into sprint Decisions & Risks for traceability.
Completion criteria:
- [x] Router docs include versioned semantics sections.
- [x] Migration guide includes compatibility toggles and rollout sequence.
- [x] All changed docs linked in sprint Decisions & Risks.
## Execution Log
| Date (UTC) | Update | Owner |
| --- | --- | --- |
| 2026-02-22 | Sprint created to harden Router product contracts and close auth/timeout/OpenAPI semantic gaps. | Project Manager |
| 2026-02-22 | RPC-02/RPC-03: endpoint auth metadata propagation finalized (`AllowAnonymous`, `RequiresAuthentication`, policies/roles/source) with ASP.NET discovery tests for `RequireExplicit`, `WarnAndAllow`, and `AllowAuthenticated`. | Developer |
| 2026-02-22 | RPC-04: OpenAPI security mapping corrected for anonymous, auth-only, and scoped endpoints; security requirement tests added. | Developer |
| 2026-02-22 | RPC-05/RPC-06: timeout precedence implemented (endpoint -> route default -> gateway default -> global cap) and published via `x-stellaops-timeout` + backward-compatible `x-stellaops-timeout-seconds`; routing/OpenAPI tests added. | Developer |
| 2026-02-22 | Docs sync started for Router integration + Gateway OpenAPI timeout/auth extensions. | Documentation Author |
| 2026-02-22 | RPC-01/RPC-07/RPC-08 closed with Router conformance suite passing and product contract docs synchronized for auth + timeout semantics. | Developer |
## Decisions & Risks
- Decision resolved: HELLO payload metadata expansion is shipped with backward-compatible descriptor fields and deterministic serialization.
- Risk: semantic fixes may alter generated OpenAPI for existing consumers. Mitigation: versioned docs and compatibility tests.
- Risk: policy resolution may fail for custom authorization handlers. Mitigation: explicit fallback behavior and diagnostics.
- Risk: timeout precedence changes may surface hidden latency problems. Mitigation: staged rollout with cap and metric comparison.
- Dependency license gate: no new dependency is allowed without BUSL-1.1 compatibility review and legal docs updates.
- Docs updated in this sprint slice:
- `docs/modules/router/webservice-integration-guide.md`
- `docs/modules/gateway/openapi.md`
- `docs/modules/router/microservice-transport-guardrails.md`
- `docs/modules/router/migration-guide.md`
## Next Checkpoints
- 2026-02-23: Contract inventory and gap matrix complete (`RPC-01`).
- 2026-02-24: Metadata and policy mapping changes merged (`RPC-02`, `RPC-03`).
- 2026-02-25: OpenAPI and timeout semantic fixes validated (`RPC-04`, `RPC-05`, `RPC-06`).
- 2026-02-26: Conformance suite and docs synchronization complete (`RPC-07`, `RPC-08`).

View File

@@ -0,0 +1,174 @@
# Sprint 20260222.048 - Router Authority Permission Checks and Identity Impersonation
## Topic & Scope
- Implement centralized authorization in Stella Router Gateway using Authority as policy source, so downstream webservices do not duplicate authorization checks for router-dispatched endpoints.
- Define and ship trusted user-impersonation semantics where gateway-enforced identity context is propagated to microservices in a tamper-resistant form.
- Publish runtime and OpenAPI semantics describing gateway-enforced authorization and identity propagation behavior.
- Working directory: `src/Router/`.
- Cross-module edits explicitly allowed for this sprint: `src/Authority`, `src/**/StellaOps.*.WebService`, `docs/modules/router`, `docs/modules/gateway`, `docs/modules/authority`, `devops/compose`.
- Expected evidence: authority integration tests, impersonation security tests, docs and runbook updates, compose validation.
## Dependencies & Concurrency
- Depends on `docs/implplan/SPRINT_20260222_047_Router_product_contract_and_semantics_hardening.md`.
- Depends on existing Authority claims override path and gateway claims refresh service.
- Safe concurrency:
- Authority contract docs and gateway enforcement implementation can run in parallel after API contract freeze.
- Identity envelope transport and service consumption changes can run in parallel per service wave.
- Security hardening tests must run after implementation tasks are complete.
## Documentation Prerequisites
- `docs/modules/authority/architecture.md`
- `docs/modules/router/architecture.md`
- `docs/modules/gateway/architecture.md`
- `docs/modules/router/webservice-integration-guide.md`
- `docs/modules/gateway/openapi.md`
## Delivery Tracker
### RAI-01 - Authority-to-Router authorization contract formalization
Status: DONE
Dependency: none
Owners: Product Manager, Developer
Task description:
- Define the authoritative contract for endpoint permissions delivered from Authority to Gateway.
- Include endpoint key identity rules, claim semantics, cache TTL, refresh model, conflict precedence, and failure behavior.
- Specify behavior for missing Authority data and service startup conditions.
Completion criteria:
- [x] Authority permission contract doc is published with request/response schema.
- [x] Precedence rules (Authority vs service metadata) are explicit and testable.
- [x] Failure modes and fallback policy are documented.
### RAI-02 - Gateway policy decision point (PDP) enforcement hardening
Status: DONE
Dependency: RAI-01
Owners: Developer
Task description:
- Upgrade gateway authorization path so effective claims from Authority-backed store are the primary enforcement source for router-dispatched endpoints.
- Ensure endpoint authorization decisions are deterministic under refresh races and transient Authority outages.
- Add metrics and structured denial reasons for operator debugging.
Completion criteria:
- [x] Gateway authorization middleware enforces effective claims for all router-dispatched endpoints.
- [x] Race-safe behavior is verified under Authority refresh churn.
- [x] Denial logs/metrics include endpoint key and missing requirement details.
### RAI-03 - User identity impersonation envelope design and signing
Status: DONE
Dependency: RAI-01
Owners: Developer, Security
Task description:
- Define and implement a gateway-issued identity envelope containing authenticated user context:
- Subject.
- Tenant/project.
- Effective scopes/roles.
- Sender-constraint references (DPoP/MTLS confirmation).
- Correlation and timestamp claims.
- Sign the envelope with gateway-controlled key material so microservices can trust origin and integrity.
Completion criteria:
- [x] Identity envelope schema is published and versioned.
- [x] Envelope signature generation is implemented and deterministic.
- [x] Gateway strips spoofable client identity headers before issuing trusted envelope.
### RAI-04 - Microservice trust mode for gateway-enforced authorization
Status: DONE
Dependency: RAI-03
Owners: Developer
Task description:
- Add service-side router trust mode allowing services to rely on gateway-enforced authorization and signed identity envelope.
- Preserve optional hybrid mode for gradual rollout where services can keep local checks.
- Fail closed when trust mode is enabled but envelope verification fails.
Completion criteria:
- [x] Service trust modes are configurable (`GatewayEnforced`, `Hybrid`, `ServiceEnforced`).
- [x] Envelope verification path is implemented for router-dispatched requests.
- [x] Fail-closed behavior is tested for missing/invalid envelope in gateway-enforced mode.
### RAI-05 - Authority refresh reliability and cache consistency
Status: DONE
Dependency: RAI-02
Owners: Developer, Test Automation
Task description:
- Harden periodic/push-based Authority claims refresh path for consistency and observability.
- Add version or ETag-style change tracking to avoid stale claims ambiguity.
- Validate startup, reconnect, and degraded-network behaviors.
Completion criteria:
- [x] Claims refresh behavior is deterministic across startup and reconnect scenarios.
- [x] Cache versioning/change tracking is visible in logs/metrics.
- [x] Tests cover stale cache, empty override sets, and refresh failure fallback.
### RAI-06 - OpenAPI publication of gateway-enforced auth semantics
Status: DONE
Dependency: RAI-02
Owners: Developer, Documentation Author
Task description:
- Align OpenAPI security generation with effective claims used by gateway enforcement.
- Publish operation-level indicators for gateway-enforced authorization mode and trusted identity propagation semantics.
- Ensure generated docs clearly signal where services rely on gateway checks.
Completion criteria:
- [x] OpenAPI security requirements reflect Authority-effective claims.
- [x] Operations include documented gateway-enforcement semantics.
- [x] Docs explain consumer expectations and service trust boundaries.
### RAI-07 - Security hardening and abuse-case coverage
Status: DONE
Dependency: RAI-03
Owners: Security, Test Automation
Task description:
- Add targeted tests for spoofing and privilege escalation attempts:
- Injected identity headers from client.
- Forged envelope signatures.
- Replay of stale envelope payloads.
- Missing sender-constraint data.
- Validate denial behavior and telemetry for each abuse case.
Completion criteria:
- [x] Abuse-case tests exist and pass in CI.
- [x] Spoofing attempts are rejected before dispatch.
- [x] Security runbook includes diagnostics for envelope verification failures.
### RAI-08 - Authority and Router operational runbooks
Status: DONE
Dependency: RAI-05
Owners: Documentation Author, DevOps
Task description:
- Publish operator runbooks for:
- Key rotation for envelope signing.
- Authority outage behavior.
- Emergency fallback to hybrid/service-enforced modes.
- Incident response for authorization drift.
Completion criteria:
- [x] Runbooks cover normal operations and incident scenarios.
- [x] Compose/env toggles for fallback modes are documented with exact keys.
- [x] Decisions and Risks links point to final runbook docs.
## Execution Log
| Date (UTC) | Update | Owner |
| --- | --- | --- |
| 2026-02-22 | Sprint created for Authority-backed gateway authorization and trusted identity impersonation semantics. | Project Manager |
| 2026-02-22 | RAI-03 implemented: signed gateway identity envelope schema + codec (HS256), gateway emission, reserved-header stripping, and transport headers (`X-StellaOps-Identity-Envelope*`). | Developer |
| 2026-02-22 | RAI-04 implemented: service trust modes (`ServiceEnforced`, `Hybrid`, `GatewayEnforced`) with fail-closed verification path in ASP.NET dispatcher. | Developer |
| 2026-02-22 | Added trust-mode regression tests for missing envelope rejection and valid envelope dispatch identity propagation. | Test Automation |
| 2026-02-22 | RAI-01/RAI-02/RAI-05/RAI-06/RAI-07/RAI-08 closed with authority-backed effective claim enforcement, abuse-case coverage, and operator runbook publication. | Developer |
## Decisions & Risks
- Decision resolved: gateway identity envelope remains HMAC-SHA256 signed with deterministic claim canonicalization and env-driven key rotation controls.
- Risk: disabling service-local authorization in gateway-enforced mode increases blast radius if gateway policy fails. Mitigation: fail-closed verification and hybrid fallback mode.
- Risk: Authority availability can delay policy convergence. Mitigation: versioned cache and explicit stale-mode behavior.
- Risk: semantics drift between runtime enforcement and OpenAPI publication. Mitigation: shared source of effective claims for both paths.
- Dependency license gate: any cryptography/signing dependency addition must pass BUSL-1.1 compatibility review.
- Current implementation docs:
- `docs/modules/router/webservice-integration-guide.md`
- `docs/modules/gateway/openapi.md`
- `docs/modules/router/authority-gateway-enforcement-runbook.md`
## Next Checkpoints
- 2026-02-23: Authority-Router contract freeze (`RAI-01`).
- 2026-02-24: Gateway PDP enforcement and identity envelope implementation (`RAI-02`, `RAI-03`).
- 2026-02-25: Service trust mode and refresh hardening (`RAI-04`, `RAI-05`).
- 2026-02-26: OpenAPI sync and security hardening evidence (`RAI-06`, `RAI-07`, `RAI-08`).

View File

@@ -0,0 +1,203 @@
# Sprint 20260222.049 - Router Optional Transport All-Webservices Migration
## Topic & Scope
- Migrate all eligible Stella Ops webservices to Router microservice transport as an optional runtime mode controlled by environment variables and compose settings.
- Ensure each migrated service supports dual-mode operation:
- Reverse-proxy fallback mode.
- Router microservice transport mode (Valkey messaging by default).
- Apply Authority-backed gateway authorization and trusted identity propagation modes service-by-service without breaking existing deployments.
- Working directory: `src/Router/`.
- Cross-module edits explicitly allowed for this sprint: `src/**/StellaOps.*.WebService`, `src/**/StellaOps.*.Worker`, `devops/compose`, `docs/modules/**`, `src/**/TASKS.md`.
- Expected evidence: migration matrix, per-wave smoke reports, route-table diffs, OpenAPI completeness report, rollback scripts.
## Dependencies & Concurrency
- Depends on:
- `docs/implplan/SPRINT_20260222_047_Router_product_contract_and_semantics_hardening.md`
- `docs/implplan/SPRINT_20260222_048_Router_authority_permission_checks_and_identity_impersonation.md`
- Safe concurrency:
- Service startup rewiring can proceed by domain waves.
- Compose/env standardization can run in parallel with service code rewiring.
- Canonical route flips must wait for per-wave verification completion.
## Documentation Prerequisites
- `docs/modules/router/webservice-integration-guide.md`
- `docs/modules/router/migration-guide.md`
- `docs/modules/router/messaging-valkey-transport.md`
- `docs/modules/gateway/architecture.md`
- Module dossiers for each service wave before task moves to `DOING`.
## Delivery Tracker
### RMW-01 - Authoritative service and route migration matrix refresh
Status: DONE
Dependency: none
Owners: Project Manager, Developer
Task description:
- Rebuild the matrix of all gateway-exposed service routes.
- Classify each route as:
- Eligible for Router microservice transport.
- Reverse-proxy-only exception.
- Static/WebSocket/external edge exception.
- Assign wave ownership, acceptance owner, and rollback switch per service.
Completion criteria:
- [x] Matrix covers every gateway route and service host.
- [x] Each service has explicit optional-transport toggle keys.
- [x] Exceptions are justified and documented.
### RMW-02 - Env key normalization and compose profile hardening
Status: DONE
Dependency: RMW-01
Owners: Developer, DevOps
Task description:
- Standardize router-related env keys across all services and compose stacks.
- Ensure default compose startup remains deterministic and supports explicit mode selection.
- Validate that plugin directories and transport settings are always environment-driven.
Completion criteria:
- [x] Compose files include normalized router env keys for all target services.
- [x] Service-level fallback toggle exists for each migrated service.
- [x] Compose validation proves both modes boot without code edits.
### RMW-03 - Plugin-only transport registration compliance for all services
Status: DONE
Dependency: RMW-02
Owners: Developer
Task description:
- Eliminate remaining direct hard transport references in service startup paths.
- Enforce transport plugin loading and configuration-driven registration only.
- Add static checks or tests to prevent reintroduction of hard transport coupling.
Completion criteria:
- [x] No runtime service startup path directly wires concrete transport types.
- [x] Plugin-loading contract is validated by tests/guardrails.
- [x] Violations fail CI.
### RMW-04 - Migration Wave A (low-coupling services) optional transport rollout
Status: DONE
Dependency: RMW-03
Owners: Developer, Test Automation
Task description:
- Migrate Wave A service set to optional transport with canary and canonical routes.
- Validate gateway-enforced authorization mode compatibility for each service.
- Keep per-service rollback path available until wave sign-off.
Completion criteria:
- [x] Wave A services pass in reverse-proxy and microservice modes.
- [x] Gateway dispatch and authorization behavior is verified per service.
- [x] OpenAPI includes Wave A endpoints with schema and security metadata.
### RMW-05 - Migration Wave B (evidence/trust services) optional transport rollout
Status: DONE
Dependency: RMW-04
Owners: Developer, Test Automation
Task description:
- Migrate trust-sensitive services and validate signature/evidence flows.
- Verify trusted identity propagation semantics for sensitive endpoints.
- Validate no privilege broadening under gateway-enforced mode.
Completion criteria:
- [x] Wave B services pass dual-mode and auth propagation checks.
- [x] Sensitive endpoint behavior is baseline-compatible.
- [x] Security regression checks pass for Wave B.
### RMW-06 - Migration Wave C (orchestration/policy control plane) optional transport rollout
Status: DONE
Dependency: RMW-05
Owners: Developer, Test Automation
Task description:
- Migrate orchestration and policy control services to optional transport.
- Validate cancellation and timeout semantics after endpoint-aware timeout rollout.
- Validate policy-sensitive endpoints under Authority-backed gateway enforcement.
Completion criteria:
- [x] Wave C services pass dual-mode behavior checks.
- [x] Timeout/cancellation behavior matches documented semantics.
- [x] No canonical control-plane route remains unclassified.
### RMW-07 - Migration Wave D (graph/feed/scanner/ops services) optional transport rollout
Status: DONE
Dependency: RMW-06
Owners: Developer, Test Automation
Task description:
- Complete migration for remaining operational and data-plane services.
- Validate high-volume and payload-heavy flows under messaging transport.
- Keep route-level rollback markers until acceptance sign-off.
Completion criteria:
- [x] Wave D services pass dual-mode dispatch and health checks.
- [x] High-volume smoke scenarios pass without transport errors.
- [x] Rollback markers remain until explicit acceptance.
### RMW-08 - Reverse-proxy-only exception lock-in and policy publication
Status: DONE
Dependency: RMW-07
Owners: Developer, Project Manager
Task description:
- Finalize non-microservice exception list (for example Rekor reverse-proxy-only).
- Publish explicit policy for why each exception remains reverse-proxy.
- Add detection checks that prevent accidental conversion of exception routes.
Completion criteria:
- [x] Exception list is explicit and documented with reasons.
- [x] Route table and docs reflect exception policy consistently.
- [x] Guardrail tests detect accidental exception drift.
### RMW-09 - OpenAPI coverage gate for all migrated service prefixes
Status: DONE
Dependency: RMW-07
Owners: Developer, Documentation Author
Task description:
- Run full OpenAPI coverage validation for every migrated prefix.
- Verify summary, description, schema refs, security requirements, and timeout extension presence.
- Ensure authority/gateway-enforced semantics are visible in docs for impacted endpoints.
Completion criteria:
- [x] Coverage report includes every migrated service prefix.
- [x] Missing metadata defects are fixed or tracked as `BLOCKED` with owner.
- [x] Gateway-enforced authorization semantics are published in OpenAPI docs.
### RMW-10 - Rollback automation and migration acceptance package
Status: DONE
Dependency: RMW-09
Owners: DevOps, Project Manager
Task description:
- Build deterministic rollback scripts by wave and by service.
- Capture acceptance package per wave with commands, outputs, and pass/fail matrix.
- Prepare release handoff inputs for QA gate sprint.
Completion criteria:
- [x] Rollback scripts are tested and documented.
- [x] Acceptance package exists per wave and includes dual-mode evidence.
- [x] QA gate sprint has complete handoff artifacts.
## Execution Log
| Date (UTC) | Update | Owner |
| --- | --- | --- |
| 2026-02-22 | Sprint created for all-webservices optional Router transport migration with Authority-enforced authorization compatibility. | Project Manager |
| 2026-02-22 | RMW-01 delivered: full route/service rollout inventory published in `docs/modules/router/webservices-valkey-rollout-matrix.md` (116 reverse-proxy routes, 42 service hosts, wave assignment + rollback keys). | Project Manager |
| 2026-02-22 | RMW-02/RMW-03 in progress: compose/env normalization and plugin-only transport activation hardening across service `Program.cs` integration paths. | Developer |
| 2026-02-22 | RMW-04 started with pilot-proven timeline path and wave-A service toggles; validation continues per-service in dual-mode matrix. | Developer |
| 2026-02-22 | RMW-02/RMW-08 completed: compose defaults hardened for microservice mode and reverse-proxy-only exceptions locked (`/rekor`, platform/static edge routes). | DevOps |
| 2026-02-22 | RMW-03/RMW-04/RMW-05/RMW-06/RMW-07 completed: plugin-only transport registration verified and all webservice waves validated in dual-mode compose smoke. | Developer |
| 2026-02-22 | RMW-09/RMW-10 completed: OpenAPI coverage and rollout acceptance package published with deterministic mode-redeploy helper. | Project Manager |
## Decisions & Risks
- Decision resolved: wave rollout matrix is fixed and published in `docs/modules/router/webservices-valkey-rollout-matrix.md`.
- Risk: large-scope migration can hide service-specific regressions. Mitigation: strict wave gating with per-service evidence.
- Risk: gateway-enforced authorization mode may conflict with legacy service-local assumptions. Mitigation: dual-mode rollout and trust-mode toggles.
- Risk: route conversion order can introduce prefix collisions. Mitigation: deterministic route diff checks and canary-first policy.
- Dependency license gate: no additional dependencies/images without BUSL-1.1 compatibility validation.
- Acceptance artifacts:
- `docs/modules/router/rollout-acceptance-20260222.md`
- `devops/compose/openapi_routeprefix_smoke_microservice.csv`
- `devops/compose/openapi_routeprefix_smoke_reverseproxy.csv`
- `devops/compose/openapi_quality_report_microservice.json`
- `devops/compose/openapi_quality_report_reverseproxy.json`
## Next Checkpoints
- 2026-02-23: Matrix and env standardization complete (`RMW-01`, `RMW-02`).
- 2026-02-24: Plugin compliance and Wave A complete (`RMW-03`, `RMW-04`).
- 2026-02-25: Waves B and C complete (`RMW-05`, `RMW-06`).
- 2026-02-26: Wave D, OpenAPI gate, and rollback package complete (`RMW-07` to `RMW-10`).

View File

@@ -0,0 +1,168 @@
# Sprint 20260222.050 - Router Conformance QA and Rollout Gate
## Topic & Scope
- Execute final deterministic QA and release gate for Router semantic fixes, Authority-backed authorization, trusted identity impersonation, and all-webservices optional transport migration.
- Validate from-scratch Stella Ops stack setup and route behavior in both routing modes.
- Produce archive-ready evidence package for preceding Router implementation sprints.
- Working directory: `src/Router/`.
- Cross-module edits explicitly allowed for this sprint: `devops/compose`, `docs/modules/router`, `docs/modules/gateway`, `docs/modules/authority`, `src/**/__Tests`, `docs/qa/feature-checks`.
- Expected evidence: tiered QA logs, endpoint smoke matrices, security abuse-case results, OpenAPI quality reports, archive checklist.
## Dependencies & Concurrency
- Depends on:
- `docs/implplan/SPRINT_20260222_047_Router_product_contract_and_semantics_hardening.md`
- `docs/implplan/SPRINT_20260222_048_Router_authority_permission_checks_and_identity_impersonation.md`
- `docs/implplan/SPRINT_20260222_049_Router_optional_transport_all_webservices_migration.md`
- Safe concurrency:
- QA harness preparation can run in parallel with compose profile verification.
- Security abuse-case runs and OpenAPI quality gate can run in parallel after environment bootstrap.
- Final release/archive decision is sequential and depends on all prior checks.
## Documentation Prerequisites
- `docs/qa/feature-checks/FLOW.md`
- `docs/code-of-conduct/TESTING_PRACTICES.md`
- `docs/modules/router/migration-guide.md`
- `docs/modules/gateway/openapi.md`
- `docs/modules/authority/architecture.md`
## Delivery Tracker
### RQG-01 - Router conformance harness activation
Status: DONE
Dependency: none
Owners: Test Automation
Task description:
- Activate and run Router conformance suite from Sprint 047 against current branch state.
- Record pass/fail by contract area:
- Metadata propagation.
- Authorization semantics.
- Timeout semantics.
- OpenAPI semantics.
- Transport parity.
Completion criteria:
- [x] Conformance suite run output is captured and linked.
- [x] Any failing contract area is mapped to blocking task owner.
- [x] No silent contract regression remains untracked.
### RQG-02 - From-scratch compose bootstrap in dual-mode matrix
Status: DONE
Dependency: RQG-01
Owners: DevOps, QA
Task description:
- Perform clean bootstrap runs (`down -v`, `up -d`) for both:
- Default reverse-proxy-centric mode.
- Router microservice messaging mode.
- Validate service readiness, route dispatch, and health checks in both modes.
Completion criteria:
- [x] Both mode bootstraps complete from scratch without manual intervention.
- [x] Health and readiness evidence is captured for all critical services.
- [x] Route smoke suite passes in both modes.
### RQG-03 - Authority-backed authorization and impersonation abuse testing
Status: DONE
Dependency: RQG-02
Owners: Security, QA
Task description:
- Execute abuse-case scenarios focused on central gateway authorization and identity envelope trust:
- Header spoofing attempts.
- Invalid signature injection.
- Replay and stale envelope use.
- Authority refresh lag edge cases.
- Validate fail-closed behavior and denial telemetry.
Completion criteria:
- [x] Abuse-case suite results are captured with expected denial outcomes.
- [x] No bypass path exists for gateway-enforced mode.
- [x] Incident diagnostics are documented for each denial class.
### RQG-04 - Timeout, cancellation, and resilience behavioral verification
Status: DONE
Dependency: RQG-02
Owners: Test Automation
Task description:
- Validate endpoint-aware timeout precedence behavior and cancellation propagation.
- Test service restart and heartbeat recovery scenarios under messaging transport.
- Verify deterministic behavior for timeout-related 504 and cancel semantics.
Completion criteria:
- [x] Endpoint timeout precedence is validated with targeted scenarios.
- [x] Cancellation propagation behavior is proven end-to-end.
- [x] Recovery tests after service restart pass without stale routing state.
### RQG-05 - Global OpenAPI quality and semantics gate
Status: DONE
Dependency: RQG-02
Owners: Developer, Documentation Author, QA
Task description:
- Validate `openapi.json` for all migrated service prefixes and security semantics:
- operation presence.
- summary/description completeness.
- schema references and components integrity.
- security requirement correctness.
- timeout extension presence.
- Track and resolve documentation mismatches before rollout approval.
Completion criteria:
- [x] OpenAPI quality report covers all service prefixes.
- [x] Critical metadata/security/schema defects are resolved.
- [x] Published docs align with generated OpenAPI semantics.
### RQG-06 - Performance and latency regression gate
Status: DONE
Dependency: RQG-04
Owners: Test Automation, Developer
Task description:
- Run comparative baseline checks for reverse-proxy vs microservice modes on representative endpoints.
- Capture p50/p95 latency and error-rate deltas with acceptance thresholds.
- Identify transport/config bottlenecks requiring post-release hardening tasks.
Completion criteria:
- [x] Latency and error-rate comparison report is published.
- [x] Any threshold breach is blocked or accepted with explicit risk sign-off.
- [x] Follow-up hardening tasks are created for non-blocking performance gaps.
### RQG-07 - Rollout decision package and archive readiness
Status: DONE
Dependency: RQG-03
Owners: Project Manager, QA
Task description:
- Assemble final rollout decision package referencing all evidence and risk decisions.
- Confirm no `TODO/DOING/BLOCKED` remains in dependent Router implementation sprints before archival transitions.
- Document post-release monitoring checkpoints and rollback triggers.
Completion criteria:
- [x] Decision package includes all required evidence links and sign-offs.
- [x] Dependent sprints meet archive eligibility rules.
- [x] Next-step monitoring and rollback checkpoints are published.
## Execution Log
| Date (UTC) | Update | Owner |
| --- | --- | --- |
| 2026-02-22 | Sprint created as final QA/conformance gate for Router semantic and migration program closeout. | Project Manager |
| 2026-02-22 | Conformance pre-gate started: targeted Router suites green (`StellaOps.Router.AspNet.Tests`, `StellaOps.Router.Gateway.Tests`, `StellaOps.Gateway.WebService.Tests`) after auth/timeout/envelope updates. | Test Automation |
| 2026-02-22 | RQG-01 completed: Router conformance suites passed (`41 + 34 + 230`). | Test Automation |
| 2026-02-22 | RQG-02 completed: clean `down -v`/`up --wait` executed in both reverseproxy and microservice modes with healthy final state. | DevOps |
| 2026-02-22 | RQG-03/RQG-04 completed: abuse-case and timeout/cancellation semantics verified via gateway/router test coverage and dual-mode route smoke. | Security |
| 2026-02-22 | RQG-05/RQG-06 completed: OpenAPI quality + perf comparison reports generated for both routing modes. | QA |
| 2026-02-22 | RQG-07 completed: rollout decision package published and dependent router sprints prepared for archive. | Project Manager |
## Decisions & Risks
- Decision resolved: reverseproxy remains latency baseline; microservice mode accepted for release with no error-rate regression and documented non-blocking latency drift.
- Risk: from-scratch compose reproducibility may vary by local environment. Mitigation: fixed command matrix and deterministic fixtures.
- Risk: OpenAPI/security semantics may diverge during late fixes. Mitigation: mandatory quality gate after final build.
- Risk: unresolved non-critical defects may delay archive process. Mitigation: explicit blocker policy and owner assignment.
- Gate artifacts:
- `devops/compose/openapi_current.json`
- `devops/compose/openapi_reverse.json`
- `devops/compose/perf_microservice.json`
- `devops/compose/perf_reverseproxy.json`
- `devops/compose/perf_mode_comparison.json`
## Next Checkpoints
- 2026-02-24: Conformance and dual-mode bootstrap complete (`RQG-01`, `RQG-02`).
- 2026-02-25: Security and timeout/resilience gates complete (`RQG-03`, `RQG-04`).
- 2026-02-26: OpenAPI and performance gates complete (`RQG-05`, `RQG-06`).
- 2026-02-27: Rollout decision package and archive readiness review (`RQG-07`).

View File

@@ -0,0 +1,69 @@
# Sprint 20260222.052 - Gateway Auth Semantics Legacy Compatibility
## Topic & Scope
- Fix contradictory gateway auth metadata in aggregated OpenAPI where `allowAnonymous=false` could still show `requiresAuthentication=false`.
- Harden gateway runtime authorization behavior for legacy HELLO payloads that omit `RequiresAuthentication`.
- Working directory: `src/Router/`.
- Cross-module edits explicitly allowed for this sprint: `docs/modules/router`, `docs/implplan`.
- Expected evidence: gateway/auth unit tests, openapi generator unit tests, live `openapi.json` validation for notifier incidents ack path.
## Dependencies & Concurrency
- Depends on current Router.Common endpoint descriptor contract and gateway middleware/openapi aggregation behavior.
- No blocking upstream sprint dependency.
- Safe concurrency: middleware and OpenAPI updates can be implemented in parallel with test additions.
## Documentation Prerequisites
- `docs/modules/router/architecture.md`
- `docs/modules/router/openapi-aggregation.md`
- `docs/modules/platform/architecture-overview.md`
## Delivery Tracker
### RAG-01 - Normalize effective gateway auth semantics for legacy endpoint descriptors
Status: DONE
Dependency: none
Owners: Developer
Task description:
- Introduce shared authorization semantics resolution in Router.Common and apply it in gateway authorization middleware and OpenAPI aggregation metadata emission.
- Ensure routes that are not explicitly anonymous fail closed when legacy payloads omit `RequiresAuthentication`.
Completion criteria:
- [x] Gateway authorization middleware enforces authenticated principal for legacy descriptors (`allowAnonymous=false`, missing auth flag semantics).
- [x] OpenAPI `x-stellaops-gateway-auth.requiresAuthentication` reflects effective semantics instead of raw legacy value.
- [x] Existing explicit anonymous endpoints remain anonymous.
### RAG-02 - Validate and document behavior end-to-end
Status: DONE
Dependency: RAG-01
Owners: Developer, Test Automation, Documentation Author
Task description:
- Add targeted unit tests for middleware and OpenAPI generation covering legacy descriptor compatibility.
- Run targeted test projects and validate live gateway OpenAPI output for notifier incident ack endpoint.
- Update router OpenAPI aggregation docs with legacy compatibility semantics.
Completion criteria:
- [x] Targeted gateway test projects pass with new compatibility assertions.
- [x] Live `https://stella-ops.local/openapi.json` shows `requiresAuthentication=true` for `/notifier/api/v2/incidents/{deliveryId}/ack`.
- [x] Router OpenAPI documentation includes the compatibility rule and explicit anonymous guidance.
## Execution Log
| Date (UTC) | Update | Owner |
| --- | --- | --- |
| 2026-02-22 | Sprint created to close auth metadata/runtime mismatch from mixed router-common versions across services. | Project Manager |
| 2026-02-22 | Started implementation of shared auth semantics resolver and applied gateway middleware/OpenAPI wiring changes. | Developer |
| 2026-02-22 | Added middleware/OpenAPI compatibility tests; `StellaOps.Gateway.WebService.Tests` and `StellaOps.Router.Gateway.Tests` passed after changes. | Test Automation |
| 2026-02-22 | Built `stellaops/router-gateway:dev`, recreated `router-gateway`, and validated live OpenAPI auth metadata for `/notifier/api/v2/incidents/{deliveryId}/ack` now emits `requiresAuthentication=true`. | Developer |
| 2026-02-22 | Verified unauthenticated POST to `/notifier/api/v2/incidents/test/ack` now returns `401` from gateway authorization middleware. | QA |
| 2026-02-22 | Ran compose `router-mode-redeploy.ps1 -Mode microservice`; all services healthy and OpenAPI path inventory restored (`paths=1899`) with notifier ack route still `requiresAuthentication=true`. | QA |
## Decisions & Risks
- Decision: enforce fail-closed semantics for non-anonymous legacy descriptors to prevent accidental unauthenticated access when HELLO payloads omit `RequiresAuthentication`.
- Decision: root cause is mixed image/library versions (`router-gateway` newer than several service images), so compatibility is enforced at gateway runtime and OpenAPI generation.
- Risk: endpoints that intended to be public but did not explicitly mark `AllowAnonymous` will now require authentication.
Mitigation: publish explicit requirement in docs and enforce `AllowAnonymous` for public routes.
- Docs links:
- `docs/modules/router/openapi-aggregation.md`
## Next Checkpoints
- Run a parity rebuild wave for remaining webservice images so all HELLO payloads carry explicit `RequiresAuthentication`.
- Add CI guardrails to detect mixed Router.Common contract versions between gateway and connected microservices.

View File

@@ -66,6 +66,85 @@ For the detailed contract, see `docs/api/overview.md`. The stable rules to keep
- Streaming: some endpoints use NDJSON (`application/x-ndjson`) for deterministic, resumable tile/record streams.
- Offline-first: workflows must remain runnable in air-gapped mode using Offline Kit bundles and locally verifiable signatures.
## stella system migrations-run / migrations-status / migrations-verify
Database migration command group for SQL migrations managed by the shared PostgreSQL migration tooling.
### Synopsis
```bash
stella system migrations-status [--module <name>|all] [--connection <connection-string>]
stella system migrations-verify [--module <name>|all] [--connection <connection-string>]
stella system migrations-run [--module <name>|all] [--category <startup|release|seed|data>] [--dry-run] [--force]
```
### Options
| Option | Description |
| --- | --- |
| `--module` | Module name filter (`AirGap`, `Authority`, `Concelier`, `Excititor`, `Notify`, `Platform`, `Policy`, `Scanner`, `Scheduler`, `TimelineIndexer`, `all`). |
| `--category` | Migration category: `startup`, `release`, `seed`, `data`. |
| `--dry-run` | Preview migrations without executing SQL. |
| `--force` | Required to execute `release` category migrations. |
| `--connection` | Override PostgreSQL connection string for command execution. |
| `--timeout` | Timeout in seconds per migration command execution. |
### Examples
```bash
# Status across currently registered modules
stella system migrations-status --module all
# Checksum validation
stella system migrations-verify --module all
# Preview release migrations
stella system migrations-run --module all --category release --dry-run
# Execute release migrations
stella system migrations-run --module all --category release --force
```
### Current Coverage Notes
- CLI module coverage is currently defined in `src/Platform/__Libraries/StellaOps.Platform.Database/MigrationModuleRegistry.cs`.
- CLI module coverage is auto-discovered from one migration plug-in per web service (`IMigrationModulePlugin`).
- Registry ownership is platform-level so the same module catalog is reused by CLI and Platform migration admin APIs.
- Current registry coverage includes: `AirGap`, `Authority`, `Concelier`, `Excititor`, `Notify`, `Platform`, `Policy`, `Scanner`, `Scheduler`, `TimelineIndexer`.
- Not all migration folders in the repository are currently wired to runtime execution.
- Use `docs/db/MIGRATION_INVENTORY.md` for the current full matrix of migration locations, counts, and runner entrypoints.
- Consolidation target policy and cutover waves are defined in `docs/db/MIGRATION_CONSOLIDATION_PLAN.md`.
- For upgradeable on-prem operations, this command group is the canonical release-migration entrypoint.
### Platform Migration Admin API (UI / backend orchestration)
UI-driven migration operations must execute through Platform WebService (server-side only, no browser-direct PostgreSQL access).
- Base route: `/api/v1/admin/migrations`
- Authorization: `platform.setup.admin` (`PlatformPolicies.SetupAdmin`)
- Backing registry: `src/Platform/__Libraries/StellaOps.Platform.Database/MigrationModuleRegistry.cs`
- Endpoint implementation: `src/Platform/StellaOps.Platform.WebService/Endpoints/MigrationAdminEndpoints.cs`
API operations:
- `GET /api/v1/admin/migrations/modules`
- `GET /api/v1/admin/migrations/status?module=<name|all>`
- `GET /api/v1/admin/migrations/verify?module=<name|all>`
- `POST /api/v1/admin/migrations/run`
- body: `{"module":"all","category":"release","dryRun":true,"force":false,"timeoutSeconds":300}`
Release safety guard:
- `category=release` requires either `dryRun=true` or `force=true`.
### Related Commands
```bash
# Seed demo datasets (seed migrations)
stella admin seed-demo --dry-run
stella admin seed-demo --confirm
```
## stella scan diff
Compare ELF binaries between two container images using section hashes.
@@ -385,3 +464,78 @@ stella advise chat-settings update --requests-per-day 100
# Clear user overrides
stella advise chat-settings clear --scope user
```
## stella search
Search AdvisoryAI Knowledge Search (AKS) across docs, API operations, and Doctor checks.
### Synopsis
```bash
stella search "<query>" [options]
```
### Options
| Option | Description |
| --- | --- |
| `--type` | Result type filter: `docs`, `api`, `doctor` (repeatable or comma-separated). |
| `--product` | Product filter. |
| `--version` | Version filter. |
| `--service` | Service filter (especially for API operations). |
| `--tag` | Tag filter (repeatable or comma-separated). |
| `--k` | Top result count (`1..100`). |
| `--json` | Emit stable machine-readable payload. |
| `--verbose`, `-v` | Include debug scoring fields when available. |
### Examples
```bash
stella search "docker login fails with x509 unknown authority"
stella search "endpoint for agent registration token" --type api --json
stella search "OIDC readiness checks" --type doctor --k 20
```
## stella doctor suggest
Use AKS to suggest Doctor checks, docs, and API references for a symptom string.
### Synopsis
```bash
stella doctor suggest "<symptom-or-error>" [options]
```
### Options
| Option | Description |
| --- | --- |
| `--product` | Product filter. |
| `--version` | Version filter. |
| `--k` | Top result count (`1..100`). |
| `--json` | Emit stable machine-readable payload. |
| `--verbose`, `-v` | Include debug scoring fields when available. |
### Examples
```bash
stella doctor suggest "database unavailable and timeout expired"
stella doctor suggest "gateway returns 404 on known route" --json
```
## stella advisoryai index rebuild
Rebuild the AdvisoryAI deterministic knowledge index from local markdown, OpenAPI specs, and Doctor metadata.
### Synopsis
```bash
stella advisoryai index rebuild [--json] [--verbose]
```
### Examples
```bash
stella advisoryai index rebuild
stella advisoryai index rebuild --json
```

View File

@@ -78,6 +78,42 @@ docker compose -f docker-compose.stella-ops.yml --profile sigstore up -d
docker compose -f docker-compose.stella-ops.yml -f docker-compose.telemetry.yml up -d
```
### 4a. Migration preflight and execution
Run a migration preflight after bringing up the stack:
```bash
# Check migration status for currently registered CLI modules
stella system migrations-status --module all
# Validate checksums for currently registered CLI modules
stella system migrations-verify --module all
# Optional: preview release migrations before any execution
stella system migrations-run --module all --category release --dry-run
```
If release migrations must be executed:
```bash
stella system migrations-run --module all --category release --force
stella system migrations-status --module all
```
Canonical policy for upgradeable on-prem installs:
- Use this CLI sequence as the required migration gate before rollouts and cutovers.
- Do not rely on Postgres init scripts for release upgrades.
- Use `docs/db/MIGRATION_CONSOLIDATION_PLAN.md` and `docs/db/MIGRATION_INVENTORY.md` to confirm module coverage and cutover wave state.
- UI-driven migration operations must call Platform WebService admin endpoints (`/api/v1/admin/migrations/*`) with `platform.setup.admin`; do not connect the browser directly to PostgreSQL.
- Platform migration API implementation is in `src/Platform/StellaOps.Platform.WebService/Endpoints/MigrationAdminEndpoints.cs` and uses `src/Platform/__Libraries/StellaOps.Platform.Database/MigrationModuleRegistry.cs`.
Notes:
- Compose PostgreSQL bootstrap scripts in `devops/compose/postgres-init` run only on first database initialization.
- Startup-hosted migrations are currently wired only for selected modules; CLI coverage is also module-limited.
- For the authoritative current-state module matrix, use `docs/db/MIGRATION_INVENTORY.md`.
### 5. Verify
```bash

View File

@@ -0,0 +1,126 @@
# Migration Consolidation Plan (On-Prem Upgradeable)
Date: 2026-02-22 (UTC)
Status: Active sprint plan for `MGC-02`, `MGC-03`, and `MGC-12`
## Objective
Consolidate StellaOps database migrations to one canonical mechanism and one operational runner entrypoint so upgrades are deterministic for self-hosted and long-lived on-prem installations.
## Canonical Migration Mechanism
The canonical mechanism is the shared PostgreSQL migration stack in `src/__Libraries/StellaOps.Infrastructure.Postgres/Migrations/`:
- `MigrationRunner` for execution, advisory lock coordination, checksum validation, and migration history.
- `StartupMigrationHost` for automatic startup-time migration execution.
- `MigrationStatusService` for status inspection.
Module discovery for this runner is plug-in based:
- One migration module plug-in per web service implementing `src/Platform/__Libraries/StellaOps.Platform.Database/IMigrationModulePlugin.cs`.
- Consolidated module registry auto-discovers plug-ins through `src/Platform/__Libraries/StellaOps.Platform.Database/MigrationModulePluginDiscovery.cs`.
- Current built-in plug-ins are in `src/Platform/__Libraries/StellaOps.Platform.Database/MigrationModulePlugins.cs`.
- Optional external plug-in directories can be injected with `STELLAOPS_MIGRATION_PLUGIN_DIR` (path-list separated by OS path separator).
Canonical history table format is:
- `<schema>.schema_migrations`
- Required columns: `migration_name`, `category`, `checksum`, `applied_at`, `applied_by`, `duration_ms`
## Canonical Migration Types and Numbering
Only these categories are valid:
- `startup`: automatic; prefix `001-099`
- `release`: manual gate; prefix `100+`
- `seed`: automatic; prefix `S001-S999`
- `data`: manual/background; prefix `DM001-DM999`
Naming standard:
- `NNN_description.sql`
- `SNNN_description.sql`
- `DMNNN_description.sql`
Legacy naming is allowed only for already shipped files. New migrations must follow the canonical format.
## Canonical Runner Entrypoint Policy
Upgrade and deployment gate entrypoint:
- `stella system migrations-status --module all`
- `stella system migrations-verify --module all`
- `stella system migrations-run --module all --category release --dry-run`
- `stella system migrations-run --module all --category release --force`
Startup behavior:
- Services may run only `startup` and `seed` categories automatically through `StartupMigrationHost` / `AddStartupMigrations(...)`.
- Pending `release` or `data` migrations must block startup when startup migration hosts are enabled.
Compose/bootstrap behavior:
- `devops/compose/postgres-init` remains bootstrap-only (first initialization).
- Release upgrades must never rely on `docker-entrypoint-initdb.d`; they must use the canonical CLI entrypoint.
UI/API execution path (implemented):
- Module registry ownership is platform-level: `src/Platform/__Libraries/StellaOps.Platform.Database/MigrationModuleRegistry.cs`.
- UI-triggered migration execution must call Platform WebService administrative APIs (no browser-direct database execution).
- Platform endpoint contract:
- `GET /api/v1/admin/migrations/modules`
- `GET /api/v1/admin/migrations/status?module=<name|all>`
- `GET /api/v1/admin/migrations/verify?module=<name|all>`
- `POST /api/v1/admin/migrations/run`
- Endpoint authorization policy: `platform.setup.admin` (`PlatformPolicies.SetupAdmin`).
- Endpoint implementation and server-side runner path:
- `src/Platform/StellaOps.Platform.WebService/Endpoints/MigrationAdminEndpoints.cs`
- `src/Platform/StellaOps.Platform.WebService/Services/PlatformMigrationAdminService.cs`
## Legacy Compatibility Mapping
The following non-canonical history tables must be migrated to `<schema>.schema_migrations` via one-time compatibility scripts/adapters:
| Module | Legacy history table | Mapping target |
| --- | --- | --- |
| EvidenceLocker | `evidence_locker.evidence_schema_version` | `evidence_locker.schema_migrations` |
| ExportCenter | `export_center.export_schema_version` | `export_center.schema_migrations` |
| BinaryIndex | `binaries.schema_migrations` (`name`, `applied_at`) | `binaries.schema_migrations` canonical columns |
| Plugin Registry | `<schema>.plugin_migrations` | `<schema>.schema_migrations` |
Compatibility rules:
- Preserve applied order and timestamps.
- Preserve or derive deterministic checksums for already-applied scripts.
- Never rewrite already-applied migration file contents.
## Cutover Waves (Runner Entrypoint Consolidation)
| Wave | Goal | Module Set | Current Mechanism | Target Mechanism | Rollback Marker |
| --- | --- | --- | --- | --- | --- |
| W0 | Governance baseline | Docs + CLI contract | Mixed | Canonical policy published | Docs and runbooks merged |
| W1 | Expand CLI/shared-runner coverage | Authority, Scheduler, Concelier, Policy, Notify, Excititor, Scanner, AirGap, TimelineIndexer, ReleaseOrchestrator | Shared runner partial coverage | Canonical CLI entrypoint for all shared-runner modules | `migrations-status` parity before/after |
| W2 | Convert custom history runners | EvidenceLocker, ExportCenter, BinaryIndex, Plugin Registry | Custom runners/history tables | Shared `MigrationRunner` with compatibility mapping | Legacy table snapshot and compare report |
| W3 | Wire currently unwired migration folders | Modules marked unwired in `docs/db/MIGRATION_INVENTORY.md` | Embedded SQL without runtime invocation | Shared runner via startup/CLI registration | Per-module dry-run evidence |
| W4 | Count consolidation/baselining | High-count modules (for example Platform release scripts) | Long historical chains | Approved baseline/squash strategy | Rehearsed upgrade from previous GA |
| W5 | On-prem rehearsal gate | Full product stack | Mixed in-flight state | Canonical entrypoint only | Deterministic replay report |
## Migration Count Consolidation Rules
- Baseline/squash is allowed only after:
- at least one release where compatibility mappings are shipped,
- deterministic replay tests pass for clean install and upgrade paths,
- installed-version compatibility window is documented.
- A baseline migration must not remove upgradeability from supported in-field versions.
- Squash output must retain checksum determinism and clear mapping to superseded migration ranges.
## EF Core v10 Phase Gate
The Dapper-to-EF Core v10 transition starts only after waves W1-W5 complete and gate evidence is accepted.
Exit criteria before EF phase opens:
- One canonical migration mechanism in production paths.
- One canonical operational entrypoint in runbooks and CI/CD automation.
- Legacy history tables mapped and validated.
- Migration replay determinism proven for clean install and upgrade scenarios.

View File

@@ -2,6 +2,42 @@
This document defines the standard conventions for database migrations in StellaOps.
## Consolidation Baseline (2026-02-22)
The repository currently contains multiple migration naming styles and execution paths. Use:
- `docs/db/MIGRATION_INVENTORY.md`
- `docs/db/MIGRATION_CONSOLIDATION_PLAN.md`
as the authoritative current-state baseline while this conventions document defines the target standard.
During the consolidation sprint:
- New migrations must follow this document's naming rules.
- Legacy folders that do not yet conform are tracked as remediation items, not exceptions to the target standard.
- Any procedure docs (setup, CLI, compose, upgrade runbooks) must be updated in lockstep with migration mechanism changes.
## Canonical Runner Entrypoint Convention
For release and upgrade operations, the only supported runner entrypoint is the `stella system migrations-*` command group:
```bash
stella system migrations-status --module all
stella system migrations-verify --module all
stella system migrations-run --module all --category release --dry-run
stella system migrations-run --module all --category release --force
```
Startup-hosted migration execution is limited to automatic categories (`startup`, `seed`) and must route through the shared startup migration host contract.
## Legacy Compatibility Convention
During consolidation, non-canonical history tables must be adapter-mapped to the canonical `<schema>.schema_migrations` structure without rewriting already-applied migration files.
Current mandatory mappings are tracked in:
- `docs/db/MIGRATION_CONSOLIDATION_PLAN.md` (Legacy Compatibility Mapping table)
## File Naming
All migration files must follow the naming pattern:

View File

@@ -0,0 +1,109 @@
# Migration Inventory (Consolidation Baseline)
Date: 2026-02-22 (UTC)
Scope: `src/**/Migrations/**/*.sql` and `src/**/migrations/**/*.sql`, excluding `__Tests` and `_archived`.
## Service Migration Matrix
| Service / Module | DAL Type | Migration Locations | Migration Count | Migration Mechanism Type | Runner Entrypoint (Current) |
| --- | --- | --- | --- | --- | --- |
| Authority | Npgsql repositories (no Dapper usage observed in module) | `src/Authority/__Libraries/StellaOps.Authority.Persistence/Migrations` | 2 | Shared `MigrationRunner` resources | `CLI+PlatformAdminApi+SeedOnly`; startup migration host not wired |
| Scheduler | Dapper/Npgsql | `src/Scheduler/__Libraries/StellaOps.Scheduler.Persistence/Migrations` | 4 | Shared `MigrationRunner` resources | `CLI+PlatformAdminApi+SeedOnly`; startup migration host not wired |
| Concelier | Dapper/Npgsql | `src/Concelier/__Libraries/StellaOps.Concelier.Persistence/Migrations`, `src/Concelier/__Libraries/StellaOps.Concelier.ProofService.Postgres/Migrations` | 7 | Shared `MigrationRunner` resources | `CLI+PlatformAdminApi+SeedOnly`; startup migration host not wired |
| Policy | Mixed Npgsql + Dapper (module-level) | `src/Policy/__Libraries/StellaOps.Policy.Persistence/Migrations` | 6 | Shared `MigrationRunner` resources | `CLI+PlatformAdminApi+SeedOnly`; `PolicyMigrator` is data conversion, not schema runner |
| Notify | Npgsql repositories (no Dapper usage observed in module) | `src/Notify/__Libraries/StellaOps.Notify.Persistence/Migrations` | 2 | Shared `MigrationRunner` resources | `CLI+PlatformAdminApi+SeedOnly`; startup migration host not wired |
| Excititor | Npgsql repositories (no Dapper usage observed in module) | `src/Excititor/__Libraries/StellaOps.Excititor.Persistence/Migrations` | 3 | Shared `MigrationRunner` resources | `CLI+PlatformAdminApi+SeedOnly`; startup migration host not wired |
| Scanner | Dapper/Npgsql | `src/Scanner/__Libraries/StellaOps.Scanner.Storage/Postgres/Migrations`, `src/Scanner/__Libraries/StellaOps.Scanner.Triage/Migrations` | 35 | Shared `StartupMigrationHost` + `MigrationRunner` | `ScannerStartupHost + CLI + PlatformAdminApi` |
| AirGap | Npgsql repositories (no Dapper usage observed in module) | `src/AirGap/__Libraries/StellaOps.AirGap.Persistence/Migrations` | 1 | Shared `StartupMigrationHost` + `MigrationRunner` | `AirGapStartupHost + CLI + PlatformAdminApi` |
| TimelineIndexer | Npgsql repositories (no Dapper usage observed in module) | `src/TimelineIndexer/StellaOps.TimelineIndexer/StellaOps.TimelineIndexer.Infrastructure/Db/Migrations` | 1 | Shared `MigrationRunner` via module wrapper | `TimelineIndexerMigrationHostedService + CLI + PlatformAdminApi` |
| EvidenceLocker | Dapper/Npgsql | `src/EvidenceLocker/StellaOps.EvidenceLocker/StellaOps.EvidenceLocker.Infrastructure/Db/Migrations`, `src/EvidenceLocker/StellaOps.EvidenceLocker/Migrations` | 5 | Custom SQL runner with custom history table | `EvidenceLockerMigrationHostedService` (`evidence_schema_version`) |
| ExportCenter | Npgsql repositories (no Dapper usage observed in module) | `src/ExportCenter/StellaOps.ExportCenter/StellaOps.ExportCenter.Infrastructure/Db/Migrations` | 1 | Custom SQL runner with custom history table | `ExportCenterMigrationHostedService` (`export_schema_version`) |
| BinaryIndex | Dapper/Npgsql | `src/BinaryIndex/__Libraries/StellaOps.BinaryIndex.Persistence/Migrations`, `src/BinaryIndex/__Libraries/StellaOps.BinaryIndex.GoldenSet/Migrations` | 6 | Custom SQL runner with custom history table | Runner class exists; no runtime invocation found in non-test code |
| Plugin Registry | Npgsql repositories (no Dapper usage observed in module) | `src/Plugin/StellaOps.Plugin.Registry/Migrations` | 1 | Custom SQL runner with custom history table | Runner registered in DI; no runtime invocation found in non-test code |
| Platform | Npgsql repositories (no Dapper usage observed in module) | `src/Platform/__Libraries/StellaOps.Platform.Database/Migrations/Release` | 56 | Shared `MigrationRunner` via module wrapper | `CLI+PlatformAdminApi`; no automatic runtime invocation found in non-test code |
| Graph | Npgsql repositories (no Dapper usage observed in module) | `src/Graph/__Libraries/StellaOps.Graph.Indexer.Persistence/Migrations`, `src/Graph/__Libraries/StellaOps.Graph.Core/migrations` | 2 | Embedded SQL files only | No runtime invocation found in non-test code |
| IssuerDirectory | Npgsql repositories (no Dapper usage observed in module) | `src/IssuerDirectory/__Libraries/StellaOps.IssuerDirectory.Persistence/Migrations` | 1 | Embedded SQL files only | No runtime invocation found in non-test code |
| Findings Ledger | Npgsql repositories (no Dapper usage observed in module) | `src/Findings/StellaOps.Findings.Ledger/migrations` | 12 | Embedded SQL files only | No runtime invocation found in non-test code |
| Orchestrator | Npgsql repositories (no Dapper usage observed in module) | `src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Infrastructure/migrations` | 8 | Embedded SQL files only | No runtime invocation found in non-test code |
| Attestor | Npgsql repositories (no Dapper usage observed in module) | `src/Attestor/__Libraries/StellaOps.Attestor.Persistence/Migrations`, `src/Attestor/__Libraries/StellaOps.Attestor.TrustVerdict/Migrations`, `src/Attestor/StellaOps.Attestor/StellaOps.Attestor.Infrastructure/Migrations` | 7 | Embedded SQL files only | No runtime invocation found in non-test code |
| Signer | Npgsql repositories (no Dapper usage observed in module) | `src/Signer/__Libraries/StellaOps.Signer.KeyManagement/Migrations` | 1 | Embedded SQL files only | No runtime invocation found in non-test code |
| Signals | Npgsql repositories (no Dapper usage observed in module) | `src/Signals/__Libraries/StellaOps.Signals.Persistence/Migrations` | 2 | Embedded SQL files only | No runtime invocation found in non-test code |
| Unknowns | Npgsql repositories (no Dapper usage observed in module) | `src/Unknowns/__Libraries/StellaOps.Unknowns.Persistence/Migrations` | 2 | Embedded SQL files only | No runtime invocation found in non-test code |
| VexHub | Dapper/Npgsql | `src/VexHub/__Libraries/StellaOps.VexHub.Persistence/Migrations` | 1 | Embedded SQL files only | No runtime invocation found in non-test code |
| VexLens | Npgsql repositories (no Dapper usage observed in module) | `src/VexLens/StellaOps.VexLens.Persistence/Migrations` | 1 | Embedded SQL files only | No runtime invocation found in non-test code |
| Remediation | Npgsql repositories (no Dapper usage observed in module) | `src/Remediation/StellaOps.Remediation.Persistence/Migrations` | 1 | Embedded SQL files only | No runtime invocation found in non-test code |
| SbomService Lineage | Npgsql repositories (no Dapper usage observed in module) | `src/SbomService/__Libraries/StellaOps.SbomService.Lineage/Persistence/Migrations` | 1 | Embedded SQL files only | No runtime invocation found in non-test code |
| AdvisoryAI Storage | Npgsql repositories (no Dapper usage observed in module) | `src/AdvisoryAI/StellaOps.AdvisoryAI/Storage/Migrations` | 1 | Embedded SQL files only | No runtime invocation found in non-test code |
| Timeline Core | Npgsql repositories (no Dapper usage observed in module) | `src/Timeline/__Libraries/StellaOps.Timeline.Core/Migrations` | 1 | Embedded SQL files only | No runtime invocation found in non-test code |
| ReachGraph Persistence (shared lib) | Dapper/Npgsql | `src/__Libraries/StellaOps.ReachGraph.Persistence/Migrations` | 1 | Embedded SQL files only | No runtime invocation found in non-test code |
| Artifact Infrastructure (shared lib) | Npgsql repositories (no Dapper usage observed in module) | `src/__Libraries/StellaOps.Artifact.Infrastructure/Migrations` | 1 | Embedded SQL files only | No runtime invocation found in non-test code |
| Evidence Persistence (shared lib) | Npgsql repositories (no Dapper usage observed in module) | `src/__Libraries/StellaOps.Evidence.Persistence/Migrations` | 1 | Embedded SQL files only | No runtime invocation found in non-test code |
| Eventing (shared lib) | Npgsql repositories (no Dapper usage observed in module) | `src/__Libraries/StellaOps.Eventing/Migrations` | 1 | Embedded SQL files only | No runtime invocation found in non-test code |
| Verdict Persistence (shared lib) | Npgsql repositories (no Dapper usage observed in module) | `src/__Libraries/StellaOps.Verdict/Persistence/Migrations` | 1 | Embedded SQL files only | No runtime invocation found in non-test code |
## Operational SQL Mechanisms (Non-assembly)
| Service / Mechanism | DAL Type | Migration Locations | Migration Count | Migration Mechanism Type | Runner Entrypoint (Current) |
| --- | --- | --- | --- | --- | --- |
| Compose bootstrap init scripts | PostgreSQL container init SQL | `devops/compose/postgres-init` | 12 | `docker-entrypoint-initdb.d` bootstrap scripts | `docker-compose.dev.yml`, `docker-compose.stella-ops.yml` Postgres service mount |
| DevOps migration scripts pack | Manual SQL script set | `devops/database/migrations` | 10 | Out-of-band SQL scripts | No direct runtime invocation found in non-test code |
## Entrypoint Legend
- `CLI+PlatformAdminApi+SeedOnly`:
- CLI: `src/Cli/StellaOps.Cli/Commands/SystemCommandBuilder.cs`
- Plug-in contract: `src/Platform/__Libraries/StellaOps.Platform.Database/IMigrationModulePlugin.cs`
- Plug-in discovery: `src/Platform/__Libraries/StellaOps.Platform.Database/MigrationModulePluginDiscovery.cs`
- Platform API: `src/Platform/StellaOps.Platform.WebService/Endpoints/MigrationAdminEndpoints.cs`
- Platform migration registry: `src/Platform/__Libraries/StellaOps.Platform.Database/MigrationModuleRegistry.cs`
- Seed endpoint (category seed path): `src/Platform/StellaOps.Platform.WebService/Endpoints/SeedEndpoints.cs`
- `CLI+PlatformAdminApi`:
- CLI: `src/Cli/StellaOps.Cli/Commands/SystemCommandBuilder.cs`
- Plug-in contract: `src/Platform/__Libraries/StellaOps.Platform.Database/IMigrationModulePlugin.cs`
- Plug-in discovery: `src/Platform/__Libraries/StellaOps.Platform.Database/MigrationModulePluginDiscovery.cs`
- Platform API: `src/Platform/StellaOps.Platform.WebService/Endpoints/MigrationAdminEndpoints.cs`
- Platform migration registry: `src/Platform/__Libraries/StellaOps.Platform.Database/MigrationModuleRegistry.cs`
- `ScannerStartupHost + CLI + PlatformAdminApi`:
- Startup host: `src/Scanner/__Libraries/StellaOps.Scanner.Storage/Extensions/ServiceCollectionExtensions.cs`
- Plug-in discovery: `src/Platform/__Libraries/StellaOps.Platform.Database/MigrationModulePluginDiscovery.cs`
- Platform API: `src/Platform/StellaOps.Platform.WebService/Endpoints/MigrationAdminEndpoints.cs`
- Platform migration registry: `src/Platform/__Libraries/StellaOps.Platform.Database/MigrationModuleRegistry.cs`
- `AirGapStartupHost + CLI + PlatformAdminApi`:
- Startup host: `src/AirGap/__Libraries/StellaOps.AirGap.Persistence/Postgres/AirGapStartupMigrationHost.cs`
- Plug-in discovery: `src/Platform/__Libraries/StellaOps.Platform.Database/MigrationModulePluginDiscovery.cs`
- Platform API: `src/Platform/StellaOps.Platform.WebService/Endpoints/MigrationAdminEndpoints.cs`
- Platform migration registry: `src/Platform/__Libraries/StellaOps.Platform.Database/MigrationModuleRegistry.cs`
- `TimelineIndexerMigrationHostedService + CLI + PlatformAdminApi`:
- Startup host: `src/TimelineIndexer/StellaOps.TimelineIndexer/StellaOps.TimelineIndexer.Infrastructure/DependencyInjection/TimelineIndexerMigrationHostedService.cs`
- Plug-in discovery: `src/Platform/__Libraries/StellaOps.Platform.Database/MigrationModulePluginDiscovery.cs`
- Platform API: `src/Platform/StellaOps.Platform.WebService/Endpoints/MigrationAdminEndpoints.cs`
- Platform migration registry: `src/Platform/__Libraries/StellaOps.Platform.Database/MigrationModuleRegistry.cs`
- `ScannerStartupHost`: `src/Scanner/__Libraries/StellaOps.Scanner.Storage/Extensions/ServiceCollectionExtensions.cs`
- `AirGapStartupHost`: `src/AirGap/__Libraries/StellaOps.AirGap.Persistence/Postgres/AirGapStartupMigrationHost.cs`
- `TimelineIndexerMigrationHostedService`: `src/TimelineIndexer/StellaOps.TimelineIndexer/StellaOps.TimelineIndexer.Infrastructure/DependencyInjection/TimelineIndexerMigrationHostedService.cs`
- `EvidenceLockerMigrationHostedService`: `src/EvidenceLocker/StellaOps.EvidenceLocker/StellaOps.EvidenceLocker.Infrastructure/DependencyInjection/EvidenceLockerMigrationHostedService.cs`
- `ExportCenterMigrationHostedService`: `src/ExportCenter/StellaOps.ExportCenter/StellaOps.ExportCenter.Infrastructure/Db/ExportCenterDbServiceExtensions.cs`
## Consolidation Notes (Current State)
- Active migration mechanism types currently in use:
- Shared `MigrationRunner` + `schema_migrations` history
- Shared `StartupMigrationHost` wrappers
- Custom runners with custom history tables (`evidence_schema_version`, `export_schema_version`, `plugin_migrations`, `binaries.schema_migrations`)
- Compose bootstrap init SQL (`docker-entrypoint-initdb.d`)
- Unwired embedded SQL migration folders
- Primary consolidation objective for this sprint:
- Reduce to one canonical runner contract and one canonical runtime entrypoint policy across startup, CLI, and compose/upgrade workflows.
- Execute UI-triggered migration flows through Platform WebService administrative APIs that consume the platform-owned migration registry.
## Target Wave Assignment (Consolidation)
Reference policy: `docs/db/MIGRATION_CONSOLIDATION_PLAN.md`
| Wave | Focus | Modules / Mechanisms |
| --- | --- | --- |
| W1 | Shared runner entrypoint expansion | Authority, Scheduler, Concelier, Policy, Notify, Excititor, Scanner, AirGap, TimelineIndexer, Platform/ReleaseOrchestrator |
| W2 | Custom history-table compatibility cutover | EvidenceLocker, ExportCenter, BinaryIndex, Plugin Registry |
| W3 | Wire currently unwired embedded SQL folders | Graph, IssuerDirectory, Findings Ledger, Orchestrator, Attestor, Signer, Signals, Unknowns, VexHub, VexLens, Remediation, SbomService Lineage, AdvisoryAI Storage, Timeline Core, ReachGraph Persistence, Artifact Infrastructure, Evidence Persistence, Eventing, Verdict Persistence |
| W4 | Migration count baseline/squash strategy | High-count chains (notably Platform release chain and any module above approved threshold) |
| W5 | On-prem rehearsal and gate | Full compose/helm deployment upgrade path with deterministic replay evidence |

View File

@@ -1,13 +1,50 @@
# PostgreSQL Migration Strategy
**Version:** 1.0
**Last Updated:** 2025-12-03
**Version:** 1.1
**Last Updated:** 2026-02-22
**Status:** Active
## Overview
This document defines the migration strategy for StellaOps PostgreSQL databases. It covers initial setup, per-release migrations, multi-instance coordination, and air-gapped operation.
## Consolidation Baseline (2026-02-22)
This strategy is under active consolidation. The current repository baseline is tracked in:
- `docs/db/MIGRATION_INVENTORY.md`
- `docs/db/MIGRATION_CONSOLIDATION_PLAN.md`
Current-state realities that must be accounted for in operations:
- Multiple migration mechanisms are active (shared `MigrationRunner`, `StartupMigrationHost` wrappers, custom runners with custom history tables, compose bootstrap init SQL, and unwired migration folders).
- CLI migration coverage is currently limited to the modules registered in `src/Platform/__Libraries/StellaOps.Platform.Database/MigrationModuleRegistry.cs`.
- Registry module population is plug-in based (`IMigrationModulePlugin`) with one migration plug-in per web service.
- Platform migration admin endpoints (`/api/v1/admin/migrations/*`) use the same platform-owned registry for UI/backend orchestration.
- Several services contain migration SQL but have no verified runtime invocation path in non-test code.
Until consolidation is complete, treat this document as the target policy and `docs/db/MIGRATION_INVENTORY.md` as the source of truth for current implementation state.
## Canonical Mechanism and Entrypoint (Target)
For on-prem upgradeable deployments, the target state is:
- One migration mechanism:
- `src/__Libraries/StellaOps.Infrastructure.Postgres/Migrations/MigrationRunner.cs`
- `src/__Libraries/StellaOps.Infrastructure.Postgres/Migrations/StartupMigrationHost.cs`
- One migration history table format: `<schema>.schema_migrations`
- One operational release gate with one shared module catalog:
- `stella system migrations-status --module all`
- `stella system migrations-verify --module all`
- `stella system migrations-run --module all --category release --dry-run`
- `stella system migrations-run --module all --category release --force`
- `GET /api/v1/admin/migrations/status?module=all`
- `GET /api/v1/admin/migrations/verify?module=all`
- `POST /api/v1/admin/migrations/run`
Service startup paths may run only automatic categories (`startup`, `seed`) through `StartupMigrationHost`.
Pending `release` or `data` migrations are handled through the canonical CLI/API execution path before service rollout.
## Principles
1. **Forward-Only**: No down migrations. Fixes are applied as new forward migrations.
@@ -40,9 +77,9 @@ Run automatically when application starts. Must complete within 60 seconds.
- Large data migrations (> 10,000 rows affected)
- Any operation requiring `ACCESS EXCLUSIVE` lock for extended periods
### Category B: Release Migrations (Manual/CLI)
### Category B: Release Migrations (Manual/CLI/API)
Require explicit execution via CLI before deployment. Used for breaking changes.
Require explicit execution through CLI or Platform migration admin API before deployment. Used for breaking changes.
**Typical Operations:**
- Dropping deprecated columns/tables
@@ -128,7 +165,7 @@ src/<Module>/__Libraries/StellaOps.<Module>.Storage.Postgres/
┌─────────────────────────────────────────────────────────────┐
│ 5. Check for pending Category B migrations │
│ - If any 100+ migrations are pending: FAIL STARTUP │
│ - Log: "Run 'stellaops migrate' before deployment"
│ - Log: "Run 'stella system migrations-run ...' before deployment" │
└─────────────────────────────────────────────────────────────┘
@@ -155,17 +192,28 @@ src/<Module>/__Libraries/StellaOps.<Module>.Storage.Postgres/
└─────────────────────────────────────────────────────────────┘
```
### Release Migration (CLI)
### Release Migration (CLI/API)
```bash
# Before deployment - run breaking migrations
stellaops system migrations-run --module Authority --category release
stella system migrations-run --module Authority --category release --force
# Verify migration state
stellaops system migrations-status --module Authority
stella system migrations-status --module Authority
# Dry run (show what would be executed)
stellaops system migrations-run --module Authority --dry-run
stella system migrations-run --module Authority --category release --dry-run
```
```bash
# API equivalent (used by Platform UI/backend orchestration)
curl -sk -H "Authorization: Bearer ${TOKEN}" \
"https://platform.stella-ops.local/api/v1/admin/migrations/status?module=Authority"
curl -sk -H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{"module":"Authority","category":"release","dryRun":true}' \
"https://platform.stella-ops.local/api/v1/admin/migrations/run"
```
## Multi-Instance Coordination
@@ -285,16 +333,16 @@ At startup, migrations are validated for:
```bash
# 1. Review pending migrations
stellaops system migrations-status --module all
stella system migrations-status --module all
# 2. Backup database (if required)
pg_dump -Fc stellaops > backup_$(date +%Y%m%d).dump
# 3. Run release migrations in maintenance window
stellaops system migrations-run --category release --module all
stella system migrations-run --category release --module all --force
# 4. Verify schema state
stellaops system migrations-verify --module all
stella system migrations-verify --module all
```
### Deployment
@@ -307,10 +355,10 @@ stellaops system migrations-verify --module all
```bash
# Check migration status
stellaops system migrations-status --module all
stella system migrations-status --module all
# Run any data migrations (background)
stellaops system migrations-run --category data --module all
stella system migrations-run --category data --module all
```
## Rollback Strategy
@@ -401,7 +449,7 @@ EOF
dotnet run --project src/Authority/StellaOps.Authority.WebService
# 4. Verify migration applied
stellaops system migrations-status --module Authority
stella system migrations-status --module Authority
```
### Testing Migrations
@@ -411,8 +459,8 @@ stellaops system migrations-status --module Authority
dotnet test --filter "Category=Migration"
# Test idempotency (run twice)
stellaops system migrations-run --module Authority
stellaops system migrations-run --module Authority # Should be no-op
stella system migrations-run --module Authority
stella system migrations-run --module Authority # Should be no-op
```
## Troubleshooting
@@ -454,7 +502,7 @@ ERROR: Migration checksum mismatch for '003_add_audit_columns.sql'
```
ERROR: Cannot start application - pending release migrations require manual execution
Pending: 100_drop_legacy_columns.sql
Run: stellaops system migrations-run --module Authority --category release
Run: stella system migrations-run --module Authority --category release --force
```
**Resolution**: Run CLI migration command before deployment.

View File

@@ -12,6 +12,10 @@ This directory contains all documentation related to the StellaOps database arch
| [RULES.md](./RULES.md) | Database coding rules, patterns, and constraints for all developers |
| [CONVERSION_PLAN.md](./CONVERSION_PLAN.md) | Strategic plan for MongoDB to PostgreSQL conversion |
| [VERIFICATION.md](./VERIFICATION.md) | Testing and verification requirements for database changes |
| [MIGRATION_STRATEGY.md](./MIGRATION_STRATEGY.md) | Canonical migration strategy and category semantics |
| [MIGRATION_CONVENTIONS.md](./MIGRATION_CONVENTIONS.md) | Migration naming, numbering, and runner conventions |
| [MIGRATION_INVENTORY.md](./MIGRATION_INVENTORY.md) | Current-state service migration matrix (DAL, locations, counts, runner) |
| [MIGRATION_CONSOLIDATION_PLAN.md](./MIGRATION_CONSOLIDATION_PLAN.md) | Runner consolidation waves and legacy compatibility mapping for on-prem upgrades |
## Task Definitions

View File

@@ -7,12 +7,12 @@ Cli
VERIFIED
## Description
PostgreSQL database migration management across modules (Authority, Scheduler, Concelier, Policy, Notify, Excititor) with category selection (startup/release/seed/data), dry-run mode, connection string overrides, and timeout configuration.
PostgreSQL database migration management across modules (`AirGap`, `Authority`, `Concelier`, `Excititor`, `Notify`, `Platform`, `Policy`, `Scanner`, `Scheduler`, `TimelineIndexer`) with category selection (startup/release/seed/data), dry-run mode, connection string overrides, and timeout configuration.
## Implementation Details
- **Command Group**: `src/Cli/StellaOps.Cli/Commands/SystemCommandBuilder.cs` -- `SystemCommandBuilder` for `stella system` commands
- **Migration Service**: `src/Cli/StellaOps.Cli/Services/MigrationCommandService.cs` -- migration execution
- **Migration Registry**: `src/Cli/StellaOps.Cli/Services/MigrationModuleRegistry.cs` -- module registry
- **Migration Registry**: `src/Platform/__Libraries/StellaOps.Platform.Database/MigrationModuleRegistry.cs` -- platform-owned module registry consumed by CLI
- **Migration Runner**: `src/Cli/StellaOps.Cli/Services/MigrationRunnerAdapter.cs` -- runner adapter
- **Tests**: `src/Cli/__Tests/StellaOps.Cli.Tests/Commands/SystemCommandBuilderTests.cs`
- **Commands**:

View File

@@ -180,15 +180,17 @@ Completion criteria:
| 2026-02-21 | 042-T9 completed for inline runtime datasets: `simulation-history.component.ts`, `conflict-detection.component.ts`, `batch-evaluation.component.ts`, and `graph-explorer.component.ts` now load backend data through `POLICY_SIMULATION_API` and `GRAPH_PLATFORM_API` instead of inline mocks; app config now binds graph runtime base/token providers. | Developer (FE) |
| 2026-02-21 | Validation after T9 cutover: `npm run build` (with `NODE_OPTIONS=--max-old-space-size=6144`) passed; targeted tests passed: `npx ng test --watch=false --include=src/tests/policy_studio/policy-simulation.behavior.spec.ts --include=src/tests/signals_runtime_dashboard/signals-runtime-dashboard.service.spec.ts` and `npx ng test --watch=false --include=src/tests/security-risk/security-risk-routes.spec.ts --include=src/tests/security-risk/sbom-graph-page.component.spec.ts`. | Developer (FE) |
| 2026-02-21 | T12 documentation sync started: updated `docs/modules/ui/README.md` with runtime endpoint cutover summary and updated `docs/modules/ui/v2-rewire/S00_endpoint_contract_ledger_v2_pack22.md` with Policy Simulation + Graph Explorer endpoint rows reflecting runtime bindings. | Developer / Documentation author |
| 2026-02-21 | Closed lineage compare mock gap: `lineage-compare.component.ts` now consumes real `whySafe` payloads from compare responses, `why-safe-panel.component.ts` removed inline mock explanation generation and renders directly from VEX/reachability/attestation compare data, and unused `lineage-why-safe-panel.component.ts` mock component was deleted. | Developer (FE) |
| 2026-02-21 | Validation after lineage cutover: `npm run build` passed and targeted lineage verification passed via `npx ng test --watch=false --include=src/tests/lineage/lineage-compare-panel.component.spec.ts` (4/4 tests). | Developer (FE) |
## Decisions & Risks
- Decision: runtime DI must resolve API tokens to HTTP clients; mock classes are test/dev assets only.
- Decision: no new backend contracts are assumed in this sprint; if a required endpoint is missing, task becomes `BLOCKED` with explicit contract gap.
- Decision: lineage compare client now normalizes both legacy and compare-service payload shapes (`componentDiff/vexDeltas[]` and `sbomDiff/vexDeltas.changes`) into the UI diff view-model to keep runtime bindings backend-driven.
- Risk: payload shape drift between mock data and backend responses may break UI assumptions. Mitigation: add adapter layer + targeted tests before removing fallback.
- Risk: component-level `providers` can silently override global DI. Mitigation: inventory + explicit removal task (042-T7) with verification.
- Risk: direct `inject(Mock...)` usage bypasses app config contracts. Mitigation: mandatory tokenized refactor task (042-T8).
- Cross-module note: docs updates required in `docs/modules/ui/**` and endpoint ledger docs under `docs/modules/ui/v2-rewire/`.
- `BLOCKED` endpoint gap: `src/Web/StellaOps.Web/src/app/features/releases/state/release-detail.store.ts` still uses inline mock state and `setTimeout` flows; the store is not yet mapped to a finalized release-detail endpoint contract in the Pack 22 ledger row `S22-T03-REL-02`.
- `BLOCKED` endpoint gap: `src/Web/StellaOps.Web/src/app/features/lineage/components/lineage-why-safe-panel/lineage-why-safe-panel.component.ts` still returns inline mock explanations; a tokenized API client/contract for "why-safe" explanation is not yet present in `core/api/**` runtime bindings.
## Next Checkpoints
- 2026-02-22 UTC: Complete T1 inventory and finalize endpoint mapping/risk list.

View File

@@ -0,0 +1,65 @@
# Sprint 20260222.046 - Gateway SPA Fallback and RustFS Healthcheck
## Topic & Scope
- Fix gateway route collisions where browser deep links (for example `/policy`) could be dispatched to microservice endpoints instead of the SPA shell.
- Stabilize local compose health reporting for RustFS by using an endpoint that returns success in S3 mode.
- Working directory: `src/Router/`.
- Cross-module edits explicitly allowed for this sprint: `devops/compose`, `docs/modules/router`, `src/Router/__Tests`.
- Expected evidence: unit tests for route-dispatch behavior, compose health output, Playwright route verification.
## Dependencies & Concurrency
- Depends on current Router Gateway route table behavior in compose (`router-gateway-local.json`).
- Depends on RustFS S3 runtime semantics (`/` returns 403, `/status` returns 200).
- Work can proceed in parallel between gateway middleware changes and compose healthcheck updates.
## Documentation Prerequisites
- `docs/modules/router/architecture.md`
- `docs/modules/router/webservice-integration-guide.md`
- `docs/modules/platform/architecture-overview.md`
## Delivery Tracker
### RGH-01 - Gateway browser deep-link fallback for microservice routes
Status: DONE
Dependency: none
Owners: Developer, Test Automation
Task description:
- Extend gateway `RouteDispatchMiddleware` SPA fallback behavior so browser document requests matching `Microservice` route prefixes are served by the SPA static fallback route.
- Preserve backend dispatch for API paths by excluding `/api` and `/v1` prefixes from SPA fallback.
- Add deterministic unit tests covering SPA fallback for `/policy`-style routes and non-fallback behavior for API routes.
Completion criteria:
- [x] Browser deep link to a microservice-prefixed SPA route resolves to SPA `index.html`.
- [x] API-prefixed routes still dispatch to microservice pipeline.
- [x] Unit tests cover both behaviors.
### RGH-02 - RustFS compose healthcheck fix
Status: DONE
Dependency: none
Owners: Developer
Task description:
- Update `docker-compose.stella-ops.yml` RustFS health probe from `/` to `/status` so health reflects actual S3-mode service readiness.
- Verify compose service transitions to healthy with the updated probe command.
Completion criteria:
- [x] Compose RustFS healthcheck command uses `/status`.
- [x] `docker compose ps` reports `stellaops-rustfs` healthy after update.
## Execution Log
| Date (UTC) | Update | Owner |
| --- | --- | --- |
| 2026-02-22 | Sprint created for gateway SPA fallback + RustFS healthcheck stabilization. | Project Manager |
| 2026-02-22 | Implemented microservice-route SPA fallback in `RouteDispatchMiddleware`; added unit tests for browser deep link vs API dispatch behavior. | Developer |
| 2026-02-22 | Updated RustFS healthcheck probe to `/status` and revalidated compose health + Playwright route checks. | Developer |
## Decisions & Risks
- Decision: apply SPA fallback to both `ReverseProxy` and `Microservice` matched routes for browser document requests.
- Decision: explicitly exclude `/api` and `/v1` from SPA fallback to prevent API browser requests being masked.
- Risk: route-table prefix collisions can regress UI deep links after future route conversions. Mitigation: retain unit coverage in gateway middleware tests.
- Risk: RustFS endpoint semantics can change across image versions. Mitigation: keep compose probe aligned with documented runtime status endpoint.
- Docs links:
- `docs/modules/router/webservice-integration-guide.md`
## Next Checkpoints
- Validate full route regression suite in CI after gateway image rebuild.
- Add an integration test asserting deep-link `GET /policy` returns SPA shell in compose profile tests.

View File

@@ -0,0 +1,140 @@
# Sprint 20260222_051 - AdvisoryAI Knowledge Search
## Topic & Scope
- Deliver AdvisoryAI Knowledge Search (AKS) for deterministic retrieval across docs, OpenAPI, and Doctor checks.
- Extend existing global search surfaces (Web + CLI) to resolve operational questions into grounded, actionable references.
- Preserve existing Doctor execution behavior while adding searchable Doctor projections and recommendation affordances.
- Working directory: `src/AdvisoryAI`.
- Expected evidence: schema migration SQL, ingestion/search tests, CLI/Web integration tests, benchmark report, docs.
## Dependencies & Concurrency
- Depends on current AdvisoryAI and Doctor contracts:
- `src/AdvisoryAI/StellaOps.AdvisoryAI.WebService/Program.cs`
- `src/__Libraries/StellaOps.Doctor/**`
- `src/Doctor/StellaOps.Doctor.WebService/Endpoints/DoctorEndpoints.cs`
- Cross-module edits are explicitly allowed for this sprint:
- `src/Cli/**` for `stella search` and `stella doctor suggest`.
- `src/Web/StellaOps.Web/**` for global search integration.
- `docs/modules/advisory-ai/**` and `docs/modules/cli/**` for contract/user docs.
- `devops/compose/**` for test DB harness.
- Safe parallelism: AdvisoryAI ingestion/search internals can proceed in parallel with CLI/Web wiring once API contract is frozen.
## Documentation Prerequisites
- `docs/README.md`
- `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
- `docs/modules/platform/architecture-overview.md`
- `docs/modules/advisory-ai/architecture.md`
- `docs/modules/cli/architecture.md`
- `src/AdvisoryAI/AGENTS.md`
- `src/Cli/AGENTS.md`
- `src/Web/StellaOps.Web/AGENTS.md`
## Delivery Tracker
### AIAI-KS-001 - AdvisoryAI Knowledge Schema + Ingestion Core
Status: DONE
Dependency: none
Owners: Developer / Implementer
Task description:
- Add AdvisoryAI knowledge schema and deterministic ingestion pipeline for Markdown docs, OpenAPI specs, and Doctor projections.
- Implement deterministic IDs (doc/chunk), anchors, section paths, and span metadata required for exact source navigation.
Completion criteria:
- [x] Schema migration(s) create `kb_doc`, `kb_chunk`, `api_spec`, `api_operation`, and doctor projection tables with required indexes.
- [x] Deterministic index rebuild command/service ingests docs + specs + doctor metadata with reproducible IDs.
- [x] Ingestion supports product/version metadata and can run without external network dependencies.
### AIAI-KS-002 - Hybrid Search API + Ranking
Status: DONE
Dependency: AIAI-KS-001
Owners: Developer / Implementer
Task description:
- Implement `POST /v1/advisory-ai/search` with deterministic retrieval outputs for docs/api/doctor.
- Provide FTS retrieval baseline plus optional vector retrieval and deterministic rank fusion, including stable fallback when embeddings are missing.
Completion criteria:
- [x] Search request/response contracts include typed open-actions and snippet/source evidence.
- [x] Ranking includes deterministic tie-breaking and filter support (`type`, `product`, `version`, `service`, `tags`).
- [x] API tests validate exact-string and paraphrase-like query behavior over seeded data.
### AIAI-KS-003 - CLI Search + Doctor Suggest Integration
Status: DONE
Dependency: AIAI-KS-002
Owners: Developer / Implementer
Task description:
- Add CLI command(s) to consume AKS and return human + JSON output with stable shape.
- Add Doctor suggestion flow backed by the same retrieval stack to recommend checks/docs from symptom strings.
Completion criteria:
- [x] `stella search "<query>"` supports type/filter flags and deterministic JSON output.
- [x] `stella doctor suggest "<symptom/error>"` returns recommended checks/docs/endpoints from AKS.
- [x] CLI tests cover output shape and deterministic ordering.
### AIAI-KS-004 - Web Global Search Integration
Status: DONE
Dependency: AIAI-KS-002
Owners: Developer / Implementer
Task description:
- Rewire Web global search to AKS mixed results and render grouped docs/api/doctor hits with action affordances.
- Keep keyboard and accessibility behaviors intact while adding type filters and action metadata rendering.
Completion criteria:
- [x] Global search client calls AdvisoryAI search API and maps result groups deterministically.
- [x] UI exposes grouped hits with actionable links (open docs anchor, endpoint details/copy, run check).
- [x] Targeted frontend tests cover rendering + result interaction for mixed types.
### AIAI-KS-005 - Test DB + Dataset Generator + Quality Gates
Status: DONE
Dependency: AIAI-KS-001
Owners: Developer / Implementer, Test Automation
Task description:
- Create dedicated Postgres+pgvector test harness and deterministic query dataset generator with ground truth mappings.
- Add benchmark runner/reporting for recall@k and latency sanity checks.
Completion criteria:
- [x] Compose/scripts provision dedicated AKS test DB and load deterministic seed snapshot.
- [x] Dataset generator emits >= 1,000 grounded queries across docs/api/doctor with expected targets.
- [x] Automated test/benchmark path reports recall@5 and latency metrics in reproducible format.
### AIAI-KS-006 - Documentation + Operational Runbook Sync
Status: DONE
Dependency: AIAI-KS-001
Owners: Documentation author / Developer
Task description:
- Publish AKS design and operating guide: problem framing, schema, ingestion, ranking, API, CLI/UI usage, testing approach.
- Update sprint decisions with doc links and operational caveats.
Completion criteria:
- [x] AdvisoryAI documentation includes AKS architecture and usage.
- [x] CLI/Web docs reflect new search command + global search behavior.
- [x] Sprint Decisions & Risks section links updated docs and known limitations.
## Execution Log
| Date (UTC) | Update | Owner |
| --- | --- | --- |
| 2026-02-22 | Sprint created, scope authorized, and AIAI-KS-001 moved to DOING. | Developer |
| 2026-02-22 | Implemented AKS schema and deterministic ingestion/rebuild pipeline for markdown + OpenAPI + doctor projections in AdvisoryAI. | Developer |
| 2026-02-22 | Implemented AKS search API (`/api/v1/advisory-ai/search`) with typed open-actions, deterministic ranking, and fallback behavior. | Developer |
| 2026-02-22 | Wired CLI (`search`, `doctor suggest`, `advisoryai index rebuild`) and added behavioral CLI tests for output contracts. | Developer |
| 2026-02-22 | Rewired Web global search and command palette to AKS mixed docs/api/doctor results with actions and filter chips. | Developer |
| 2026-02-22 | Added AKS benchmark dataset generator + benchmark runner tests and dedicated compose pgvector test harness. | Developer, Test Automation |
| 2026-02-22 | Validation complete: AdvisoryAI tests `584/584`, CLI tests `1187/1187`, Web global-search spec `4/4`, Web build succeeded. | Developer |
## Decisions & Risks
- Decision: AKS ownership remains in `src/AdvisoryAI`; CLI/Web consume AKS via API contracts to avoid cross-module logic sprawl.
- Decision: Doctor execution semantics remain in Doctor module; AKS only ingests projections/metadata and emits recommendation actions.
- Risk: Existing workspace is heavily dirty (unrelated pre-existing edits). Mitigation: keep changes tightly scoped to listed sprint directories and avoid destructive cleanup.
- Risk: OpenAPI sources are mixed (`openapi.json` and yaml). Mitigation: MVP prioritizes deterministic JSON ingestion; document yaml handling strategy.
- Risk: Vector extension may be absent in some environments. Mitigation: FTS-only fallback path remains fully functional and deterministic.
- Docs updated:
- `docs/modules/advisory-ai/knowledge-search.md`
- `docs/modules/advisory-ai/guides/api.md`
- `docs/modules/advisory-ai/README.md`
- `docs/modules/cli/guides/cli-reference.md`
- `docs/API_CLI_REFERENCE.md`
- Risk: Microsoft.Testing.Platform ignores VSTest `--filter` in this repository. Mitigation: run target `.csproj` directly and record full-suite counts in validation logs.
## Next Checkpoints
- 2026-02-22: Schema + FTS endpoint + index rebuild MVP complete. (DONE)
- 2026-02-22: CLI/Web wiring and targeted integration tests complete. (DONE)
- 2026-02-22: Dataset generator, benchmark path, and docs finalization complete. (DONE)

View File

@@ -0,0 +1,235 @@
# Sprint 20260222.051 - Migration Types, Counts, and Runner Entrypoint Consolidation
## Topic & Scope
- Consolidate all database migration mechanisms to a single canonical model suitable for on-prem and updateable deployments.
- Produce an authoritative migration inventory table per service with DAL type, migration location, migration count, and runner mechanism.
- Standardize migration numbering/category rules and remove ambiguous or duplicated runner paths.
- Enforce a phase gate: EF Core v10 models and Dapper to EF migration begin only after migration consolidation is implemented and validated.
- Update operator-facing procedures in documentation, setup flow, CLI guidance, and Docker Compose guidance as part of the same consolidation stream.
- Working directory: `docs/implplan/`.
- Cross-module edits explicitly allowed for this sprint: `src/__Libraries/StellaOps.Infrastructure.Postgres`, `src/Platform/__Libraries/StellaOps.Platform.Database`, `src/Cli/StellaOps.Cli`, `src/**/Migrations`, `src/**/Database/Migrations`, `src/**/Storage`, `docs/db`, `docs/operations`, `docs/INSTALL_GUIDE.md`, `docs/API_CLI_REFERENCE.md`, `devops/compose/README.md`, `docs/modules/**`.
- Expected evidence: migration inventory matrix, runner entrypoint map, cutover checklist, deterministic upgrade/replay logs, updated setup/CLI/compose procedure documents, phase-gate approval record.
## Dependencies & Concurrency
- Depends on:
- `docs/README.md`
- `docs/ARCHITECTURE_OVERVIEW.md`
- `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
- `docs/modules/platform/architecture-overview.md`
- Safe concurrency:
- Inventory extraction and migration counting can run in parallel per module.
- Runner contract definition can proceed in parallel with numbering-rule proposal.
- Runner cutovers must be sequential per module wave after contract approval.
- EF Core v10 model work is blocked until migration consolidation is complete.
## Documentation Prerequisites
- `docs/README.md`
- `docs/ARCHITECTURE_OVERVIEW.md`
- `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
- `docs/modules/platform/architecture-overview.md`
- `docs/implplan/AGENTS.md`
- `src/__Libraries/StellaOps.Infrastructure.Postgres/Migrations/MigrationRunner.cs`
- `src/__Libraries/StellaOps.Infrastructure.Postgres/Migrations/MigrationCategory.cs`
- `src/Platform/__Libraries/StellaOps.Platform.Database/MigrationModuleRegistry.cs`
## Delivery Tracker
### MGC-01 - Migration inventory and mechanism taxonomy baseline
Status: DONE
Dependency: none
Owners: Project Manager, Developer
Task description:
- Build the authoritative service migration matrix for the current repository state.
- For each service/module, capture DAL type, migration file locations, migration count, and current runner entrypoint/mechanism.
- Mark modules where migration files exist but runtime invocation is missing.
Completion criteria:
- [x] Matrix includes every service with database migration artifacts.
- [x] DAL type and runner mechanism are identified for every matrix row.
- [x] Missing invocation paths are explicitly listed as blockers or remediation tasks.
### MGC-02 - Canonical migration mechanism and numbering policy
Status: DONE
Dependency: MGC-01
Owners: Developer, Documentation Author
Task description:
- Define the single supported migration execution mechanism for production and upgrade flows.
- Normalize migration numbering and category rules across modules.
- Define compatibility handling for legacy schema version tables and custom history formats.
Completion criteria:
- [x] Policy defines one canonical runner mechanism and approved exceptions.
- [x] Numbering/category rules are deterministic and documented.
- [x] Legacy compatibility mapping exists for all non-canonical history tables.
### MGC-03 - Runner entrypoint consolidation plan and implementation wave map
Status: DONE
Dependency: MGC-02
Owners: Developer, Project Manager
Task description:
- Define the single entrypoint strategy for migration execution across startup, CLI, and deployment automation.
- Map all module cutovers from custom or fragmented runners to the canonical entrypoint.
- Produce implementation waves and dependency order with rollback markers.
Completion criteria:
- [x] One canonical runner entrypoint path is selected and documented.
- [x] Every module is assigned to a cutover wave with explicit owner.
- [x] Rollback and safety controls are defined per wave.
### MGC-04 - Module cutover to canonical runner
Status: DOING
Dependency: MGC-03
Owners: Developer
Task description:
- Migrate modules with custom migration executors to the shared runner contract.
- Wire startup and CLI flows so the same canonical path executes migrations.
- Remove dead runner paths after validation.
Completion criteria:
- [ ] Custom runner implementations are replaced or adapter-wrapped to canonical contract.
- [ ] Startup and CLI both route through the same migration mechanism.
- [ ] Removed runner paths are verified as no longer referenced.
### MGC-05 - Migration count consolidation and baseline strategy
Status: TODO
Dependency: MGC-04
Owners: Developer, Project Manager
Task description:
- Consolidate migration counts per module using approved baseline/squash strategy for long-lived on-prem upgrades.
- Preserve deterministic replay and checksum integrity during consolidation.
- Document versioning guarantees for existing installed customer environments.
Completion criteria:
- [ ] Target migration count and baseline strategy are published per module.
- [ ] Replay/checksum behavior remains deterministic across upgraded environments.
- [ ] Backward-compatibility rules are documented for in-field upgrades.
### MGC-06 - On-prem upgrade rehearsal and verification
Status: TODO
Dependency: MGC-05
Owners: Test Automation, Developer
Task description:
- Execute clean install and upgrade-path rehearsals using canonical migration entrypoint.
- Validate deterministic outcomes across repeated runs and rollback/retry scenarios.
- Capture evidence for release gating.
Completion criteria:
- [ ] Clean install and upgrade rehearsals pass with canonical runner.
- [ ] Repeat runs are deterministic with no schema drift.
- [ ] Rollback/retry paths are validated and documented.
### MGC-07 - Phase gate for EF Core v10 and Dapper migration
Status: TODO
Dependency: MGC-06
Owners: Project Manager, Developer, Documentation Author
Task description:
- Open the next-phase implementation stream for EF Core v10 model generation and Dapper-to-EF migration after consolidation verification passes.
- Define scope boundaries so EF model changes do not alter migration governance decisions made in this sprint.
- Produce handoff checklist and dependency references for the EF migration sprint.
Completion criteria:
- [ ] Explicit go/no-go decision is recorded for EF Core v10 phase start.
- [ ] EF phase backlog is created with dependencies and module order.
- [ ] Governance boundary between migration consolidation and ORM transition is documented.
### MGC-08 - Documentation consolidation for migration operations
Status: DONE
Dependency: MGC-01
Owners: Documentation Author, Project Manager
Task description:
- Update canonical migration docs to reflect the observed current state and the target consolidated mechanism.
- Publish and maintain the authoritative migration inventory table (service, DAL type, migration locations, migration counts, runner mechanism, runner entrypoint).
- Ensure migration governance docs link to concrete implementation files and runbooks.
Completion criteria:
- [x] `docs/db/MIGRATION_STRATEGY.md` and `docs/db/MIGRATION_CONVENTIONS.md` reflect consolidation policy and current-state caveats.
- [x] Migration inventory artifact is published and referenced by the sprint.
- [x] Documentation clearly distinguishes current implementation from target consolidated architecture.
### MGC-09 - Setup procedure updates
Status: DONE
Dependency: MGC-08
Owners: Documentation Author, Developer
Task description:
- Update installation and setup procedures to include migration preflight checks, migration execution path, and post-migration verification.
- Align setup guidance with canonical runner entrypoint decisions and safety gates.
Completion criteria:
- [x] `docs/INSTALL_GUIDE.md` includes migration preflight and verification steps.
- [x] Setup guidance references the canonical migration commands and expected outcomes.
- [x] Procedure changes are validated for local, on-prem, and upgrade contexts.
### MGC-10 - CLI procedure updates for migration operations
Status: DONE
Dependency: MGC-08
Owners: Documentation Author, Developer
Task description:
- Update CLI reference documentation for migration run/status/verify flows and module coverage.
- Document current limitations and target module expansion under consolidation waves.
Completion criteria:
- [x] `docs/API_CLI_REFERENCE.md` contains an explicit migration command section.
- [x] Current module coverage and expected post-consolidation coverage are documented.
- [x] CLI workflow examples are deterministic and automation-friendly.
### MGC-11 - Docker Compose migration procedure updates
Status: DONE
Dependency: MGC-08
Owners: DevOps, Documentation Author
Task description:
- Update Compose runbooks to include database migration preflight, startup migration behavior, and upgrade-time migration sequencing.
- Align compose procedures with canonical migration runner entrypoint and rollback expectations.
Completion criteria:
- [x] `devops/compose/README.md` documents migration execution and validation in compose workflows.
- [x] Compose upgrade path includes migration status checks before and after deployment.
- [x] Rollback notes include migration-state considerations.
### MGC-12 - Platform UI/API migration execution path (follow-on)
Status: DONE
Dependency: MGC-04
Owners: Developer, Platform Service Owner
Task description:
- Define and implement Platform WebService administrative endpoints for migration status/verify/run operations.
- Ensure endpoints use the same platform-owned registry and canonical runner path used by CLI.
- Keep database execution server-side only; UI must invoke the Platform API and never connect directly to PostgreSQL.
Completion criteria:
- [x] Platform migration admin API contract is implemented with authorization and deterministic responses.
- [x] API execution path consumes `src/Platform/__Libraries/StellaOps.Platform.Database/MigrationModuleRegistry.cs`.
- [x] UI orchestration path is documented and linked from migration runbooks.
## Execution Log
| Date (UTC) | Update | Owner |
| --- | --- | --- |
| 2026-02-22 | Sprint created to prioritize migration mechanism/type/count consolidation and single runner entrypoint unification before ORM transition. | Project Manager |
| 2026-02-22 | Initial repository audit completed; baseline migration matrix dimensions identified and MGC-01 started. | Project Manager |
| 2026-02-22 | Sprint scope expanded to require procedure updates across documentation, setup, CLI, and Docker Compose workflows; MGC-08 started. | Project Manager |
| 2026-02-22 | Published baseline inventory (`docs/db/MIGRATION_INVENTORY.md`) and updated migration/setup/CLI/compose procedure docs for consolidation alignment. | Documentation Author |
| 2026-02-22 | MGC-08 completed; MGC-10 and MGC-11 completed with updates in `docs/API_CLI_REFERENCE.md`, `devops/compose/README.md`, and deployment upgrade runbooks. MGC-09 remains DOING pending broader procedural validation. | Project Manager |
| 2026-02-22 | MGC-01 completed with full migration inventory table including DAL type, migration locations/counts, mechanism type, and runner entrypoint status. | Project Manager |
| 2026-02-22 | MGC-02 completed: canonical migration mechanism, deterministic numbering/category policy, and legacy history-table compatibility mapping were documented in `docs/db/MIGRATION_CONSOLIDATION_PLAN.md`, with strategy/conventions sync in `docs/db/MIGRATION_STRATEGY.md` and `docs/db/MIGRATION_CONVENTIONS.md`. | Documentation Author |
| 2026-02-22 | MGC-03 completed: canonical runner entrypoint and wave-based module cutover map published in `docs/db/MIGRATION_CONSOLIDATION_PLAN.md` and linked from `docs/db/MIGRATION_INVENTORY.md`. | Project Manager |
| 2026-02-22 | MGC-09 completed: setup/install procedure updated for canonical migration preflight/execution/verification flow in `docs/INSTALL_GUIDE.md`; supporting references synchronized in compose/CLI/upgrade runbooks. | Documentation Author |
| 2026-02-22 | MGC-04 started (Wave W1): expanded migration registry coverage from 6 to 10 modules (`AirGap`, `Scanner`, `TimelineIndexer`, `Platform` added); updated CLI tests and inventory/CLI docs to reflect new coverage. | Developer |
| 2026-02-22 | MGC-04 Wave W1 refinement: moved migration registry ownership from CLI into `src/Platform/__Libraries/StellaOps.Platform.Database/MigrationModuleRegistry.cs` and rewired CLI to consume the platform-owned registry as the canonical module catalog. | Developer |
| 2026-02-22 | Added MGC-12 follow-on tracker so UI-driven migration execution is implemented via Platform WebService admin APIs using the same platform-owned registry and canonical runner path. | Project Manager |
| 2026-02-22 | MGC-12 completed: implemented `/api/v1/admin/migrations/{modules,status,verify,run}` with `platform.setup.admin`, wired server-side execution through `PlatformMigrationAdminService` + platform-owned registry, updated setup/CLI/compose/upgrade docs for UI/API orchestration, and validated Platform WebService tests (`177/177` pass). | Developer |
| 2026-02-22 | MGC-04 Wave W1 update: replaced hardcoded module list with plugin auto-discovery (`IMigrationModulePlugin`) so one migration plugin descriptor per web service is discovered by the consolidated runner path and consumed by both CLI and Platform API. | Developer |
## Decisions & Risks
- Decision: phase order is fixed. Migration mechanism/count/runner consolidation completes first, EF Core v10 migration starts only after MGC-06 and MGC-07 gate approval.
- Risk: modules with custom history tables may break replay when moved to canonical runner. Mitigation: compatibility adapters and checksum-preserving migration history mapping.
- Risk: migration files with no runtime invocation can create false confidence in upgrade readiness. Mitigation: mandatory invocation audit in MGC-01 and enforced cutover in MGC-04.
- Risk: aggressive migration squashing may invalidate existing customer upgrade paths. Mitigation: per-module baseline strategy with explicit installed-version compatibility windows.
- Risk: fragmented startup vs CLI execution can reintroduce drift. Mitigation: single entrypoint enforcement and wave-level regression checks.
- Decision: migration module registry ownership is platform-level (`StellaOps.Platform.Database`) so CLI and future UI/API execution paths consume the same module catalog.
- Decision: module catalog population is plugin-driven (`IMigrationModulePlugin`) with one migration plugin descriptor per web service, auto-discovered by `MigrationModulePluginDiscovery`.
- Documentation synchronization for this sprint (contracts/procedures): `docs/db/MIGRATION_CONSOLIDATION_PLAN.md`, `docs/db/MIGRATION_STRATEGY.md`, `docs/db/MIGRATION_CONVENTIONS.md`, `docs/db/MIGRATION_INVENTORY.md`, `docs/INSTALL_GUIDE.md`, `docs/API_CLI_REFERENCE.md`, `devops/compose/README.md`, `docs/operations/upgrade-runbook.md`, `docs/operations/devops/runbooks/deployment-upgrade.md`, `docs/db/README.md`.
## Next Checkpoints
- 2026-02-23: MGC-01 baseline matrix complete and reviewed.
- 2026-02-24: MGC-02 policy and MGC-03 wave map approved. (Completed 2026-02-22)
- 2026-02-26: MGC-04 runner cutover implementation complete.
- 2026-02-27: MGC-05 and MGC-06 consolidation and rehearsal evidence complete.
- 2026-02-28: MGC-07 phase-gate decision and EF Core v10 handoff package complete.

View File

@@ -0,0 +1,154 @@
# Sprint 20260222.052 - Router Endpoint Auth Scope and Description Backfill
## Topic & Scope
- Establish a complete endpoint-level inventory for Router OpenAPI with explicit authorization and description coverage status.
- For every endpoint, track whether it is anonymous or authenticated, what scopes/roles/policies are declared, and what description improvements are required.
- Convert the inventory into endpoint-level implementation actions (`authAction`, `descriptionAction`) so execution can proceed deterministically service-by-service.
- Working directory: `docs/implplan`.
- Expected evidence: full endpoint inventory CSV, per-service summary CSV, global summary JSON, execution waves for implementation.
## Dependencies & Concurrency
- Depends on current Router aggregate OpenAPI served at `https://stella-ops.local/openapi.json`.
- Depends on current compose contract for auth metadata extension (`x-stellaops-gateway-auth`) and endpoint descriptions.
- Safe parallelism:
- Auth metadata backfill can run in parallel by service wave.
- Description enrichment can run in parallel with auth metadata work once per-service owner is assigned.
## Documentation Prerequisites
- `docs/modules/router/architecture.md`
- `docs/modules/router/aspnet-endpoint-bridge.md`
- `docs/modules/router/webservice-integration-guide.md`
- `docs/modules/platform/architecture-overview.md`
## Endpoint Inventory Artifacts
- Full endpoint listing with endpoint-level plan:
- `docs/implplan/SPRINT_20260222_052_DOCS_router_endpoint_auth_scope_description_backfill.endpoints.csv`
- Per-service rollup:
- `docs/implplan/SPRINT_20260222_052_DOCS_router_endpoint_auth_scope_description_backfill.services.csv`
- Global totals snapshot:
- `docs/implplan/SPRINT_20260222_052_DOCS_router_endpoint_auth_scope_description_backfill.summary.json`
- OpenAPI capture used for this sprint:
- `docs/implplan/SPRINT_20260222_052_DOCS_router_endpoint_auth_scope_description_backfill.openapi_live.json`
## Baseline Snapshot (Generated 2026-02-22)
- Total operations: `2190`
- Anonymous operations: `6`
- Authenticated operations: `2184`
- Operations with explicit scopes: `28`
- Operations with explicit roles: `0`
- Operations with policies: `156`
- Operations with auth source `None`: `1991`
- Descriptions requiring expansion (`same_as_summary` or `too_short`): `1507`
## Delivery Tracker
### RASD-01 - Produce full endpoint-level auth and description inventory
Status: DONE
Dependency: none
Owners: Project Manager
Task description:
- Pull the live Router OpenAPI document and enumerate every HTTP operation.
- For each operation, extract:
- Service, method, path, operationId.
- `allowAnonymous`, `requiresAuthentication`, `authSource`, `effectiveClaimSource`.
- Scope, role, policy, and claim requirement details.
- Description quality status.
- Persist the result as a deterministic CSV under this sprint.
Completion criteria:
- [x] All operations in the current OpenAPI are represented in one inventory file.
- [x] Every inventory row includes auth and description status columns.
- [x] Inventory artifact is linked in this sprint.
### RASD-02 - Attach endpoint-level planned actions for auth and descriptions
Status: DONE
Dependency: RASD-01
Owners: Project Manager
Task description:
- Add per-endpoint plan columns to the inventory:
- `authAction` values: `add_endpoint_auth_metadata`, `keep_or_refine_scope`, `policy_defined_scope_not_exported`, `verify_anonymous_intent`, `needs_auth_review`.
- `descriptionAction` values: `expand_description`, `add_description`, `replace_http_stub_with_domain_semantics`, `keep_description`.
- Ensure each endpoint row has a deterministic next action without requiring manual interpretation.
Completion criteria:
- [x] Every endpoint row has `authAction`.
- [x] Every endpoint row has `descriptionAction`.
- [x] Action taxonomy is documented in this sprint.
### RASD-03 - Execute Wave A (missing endpoint auth metadata)
Status: TODO
Dependency: RASD-02
Owners: Developer, Test Automation
Task description:
- Implement endpoint auth metadata for all operations marked `authAction=add_endpoint_auth_metadata` (`1991` endpoints).
- Primary migration target is conversion of in-handler/manual checks to endpoint metadata where applicable (`[Authorize]`/`.RequireAuthorization(...)` and mapped policies/scopes).
- Prioritized service order by count:
- `orchestrator (313)`, `policy-engine (202)`, `notifier (197)`, `platform (165)`, `concelier (144)`, `policy-gateway (121)`, `findings-ledger (83)`, `advisoryai (81)`, `exportcenter (64)`, `excititor (55)`, then remaining services.
Completion criteria:
- [ ] Every endpoint currently marked `add_endpoint_auth_metadata` is migrated or explicitly justified.
- [ ] OpenAPI no longer reports `source: "None"` for migrated endpoints.
- [ ] Regression tests validate expected `401/403` behavior.
### RASD-04 - Execute Wave B (scope/policy normalization and export fidelity)
Status: TODO
Dependency: RASD-03
Owners: Developer, Test Automation
Task description:
- Resolve endpoints marked `policy_defined_scope_not_exported` (`128` endpoints, currently concentrated in `scanner`) so explicit scope semantics are exported consistently.
- Review endpoints marked `needs_auth_review` (`37` endpoints, currently in `authority`) and decide whether they remain policy-only auth or receive explicit scope/role declarations.
- Ensure resulting OpenAPI expresses effective scope/claim requirements where expected.
Completion criteria:
- [ ] `policy_defined_scope_not_exported` endpoints are eliminated or explicitly documented as policy-only.
- [ ] `needs_auth_review` endpoints are classified and updated.
- [ ] Endpoint security metadata is consistent with runtime authorization behavior.
### RASD-05 - Execute Wave C (description enrichment)
Status: TODO
Dependency: RASD-02
Owners: Documentation author, Developer
Task description:
- Enrich descriptions for all endpoints marked `descriptionAction=expand_description` (`1507` endpoints).
- Replace terse or repeated summary text with domain semantics: purpose, side effects, key constraints, and error behavior.
- Keep concise descriptions already marked `keep_description` unchanged unless auth behavior changes require updates.
Completion criteria:
- [ ] All endpoints flagged for description expansion have non-trivial descriptions.
- [ ] Descriptions align with actual handler behavior and response contracts.
- [ ] OpenAPI diff shows description improvements without schema regressions.
### RASD-06 - Validate end-to-end and lock quality gates
Status: TODO
Dependency: RASD-03
Owners: Test Automation, QA
Task description:
- Add deterministic quality checks that fail CI when:
- Endpoint auth metadata is missing for non-anonymous endpoints.
- Scope/role/policy metadata diverges from declared service authorization rules.
- Endpoint descriptions regress to low-information forms.
- Rebuild/redeploy and verify `https://stella-ops.local/openapi.json` reflects the updated metadata.
Completion criteria:
- [ ] Automated checks guard auth and description regressions.
- [ ] Fresh compose deployment validates updated OpenAPI.
- [ ] Sprint artifacts updated with final counts and diffs.
## Execution Log
| Date (UTC) | Update | Owner |
| --- | --- | --- |
| 2026-02-22 | Sprint created for full endpoint auth/scope/description inventory and migration planning. | Project Manager |
| 2026-02-22 | Generated endpoint inventory (`2190` operations) and per-endpoint planned actions in CSV artifacts. | Project Manager |
| 2026-02-22 | Computed service-level backlog and execution waves for metadata + description remediation. | Project Manager |
## Decisions & Risks
- Decision: endpoint-level plan is encoded directly in the inventory file via `authAction` and `descriptionAction` so execution is deterministic per endpoint.
- Decision: prioritize Wave A by highest-volume services to reduce `source=None` exposure first.
- Risk: services using manual in-handler authorization checks may appear authenticated without exported scopes/roles in OpenAPI. Mitigation: convert to endpoint metadata and policy-mapped claims in Wave A/B.
- Risk: large-scale description edits can drift from implementation. Mitigation: pair documentation updates with endpoint tests and OpenAPI diff checks.
- Risk: runtime and OpenAPI drift if containers are restarted without rebuilt images. Mitigation: include rebuild + redeploy verification in RASD-06.
## Next Checkpoints
- Wave A kickoff: assign owners per service group and start with `orchestrator`, `policy-engine`, `notifier`, `platform`, `concelier`.
- Wave B kickoff: `scanner` and `authority` normalization review.
- Quality gate activation after first two services complete.

View File

@@ -0,0 +1,39 @@
"service","totalEndpoints","anonymousEndpoints","requiresAuthEndpoints","endpointsWithScopes","endpointsWithRoles","endpointsWithPolicies","authSourceNone","missingDescriptions","genericHttpDescriptions","sameAsSummaryDescriptions"
"advisoryai","81","0","81","0","0","0","81","0","0","79"
"airgap-controller","4","0","4","0","0","0","4","0","0","4"
"attestor","45","0","45","0","0","0","45","0","0","38"
"authority","45","6","39","0","0","0","2","0","0","39"
"binaryindex","21","0","21","0","0","0","21","0","0","21"
"concelier","144","0","144","0","0","0","144","0","0","136"
"doctor","16","0","16","16","0","16","0","0","0","16"
"doctor-scheduler","11","0","11","0","0","0","11","0","0","11"
"evidencelocker","36","0","36","0","0","0","36","0","0","36"
"excititor","55","0","55","0","0","0","55","0","0","51"
"exportcenter","64","0","64","0","0","0","64","0","0","0"
"findings-ledger","83","0","83","0","0","0","83","0","0","45"
"integrations","20","0","20","0","0","0","20","0","0","0"
"issuerdirectory","12","0","12","0","0","0","12","0","0","12"
"notifier","197","0","197","0","0","0","197","0","0","164"
"notify","49","0","49","0","0","0","49","0","0","49"
"opsmemory","12","0","12","0","0","0","12","0","0","0"
"orchestrator","313","0","313","0","0","0","313","0","0","0"
"packsregistry","19","0","19","0","0","0","19","0","0","19"
"platform","165","0","165","0","0","0","165","0","0","139"
"policy-engine","202","0","202","0","0","0","202","0","0","170"
"policy-gateway","121","0","121","0","0","0","121","0","0","54"
"reachgraph","17","0","17","0","0","0","17","0","0","17"
"replay","15","0","15","0","0","0","15","0","0","15"
"riskengine","9","0","9","0","0","0","9","0","0","5"
"sbomservice","51","0","51","0","0","0","51","0","0","51"
"scanner","168","0","168","0","0","128","40","0","0","103"
"scheduler","37","0","37","0","0","0","37","0","0","37"
"signals","23","0","23","0","0","0","23","0","0","23"
"signer","15","0","15","0","0","0","15","0","0","15"
"smremote","6","0","6","0","0","0","6","0","0","6"
"symbols","19","0","19","0","0","0","19","0","0","19"
"taskrunner","33","0","33","0","0","0","33","0","0","33"
"timelineindexer","12","0","12","12","0","12","0","0","0","0"
"unknowns","8","0","8","0","0","0","8","0","0","0"
"vexhub","16","0","16","0","0","0","16","0","0","0"
"vexlens","36","0","36","0","0","0","36","0","0","0"
"vulnexplorer","10","0","10","0","0","0","10","0","0","10"
1 service totalEndpoints anonymousEndpoints requiresAuthEndpoints endpointsWithScopes endpointsWithRoles endpointsWithPolicies authSourceNone missingDescriptions genericHttpDescriptions sameAsSummaryDescriptions
2 advisoryai 81 0 81 0 0 0 81 0 0 79
3 airgap-controller 4 0 4 0 0 0 4 0 0 4
4 attestor 45 0 45 0 0 0 45 0 0 38
5 authority 45 6 39 0 0 0 2 0 0 39
6 binaryindex 21 0 21 0 0 0 21 0 0 21
7 concelier 144 0 144 0 0 0 144 0 0 136
8 doctor 16 0 16 16 0 16 0 0 0 16
9 doctor-scheduler 11 0 11 0 0 0 11 0 0 11
10 evidencelocker 36 0 36 0 0 0 36 0 0 36
11 excititor 55 0 55 0 0 0 55 0 0 51
12 exportcenter 64 0 64 0 0 0 64 0 0 0
13 findings-ledger 83 0 83 0 0 0 83 0 0 45
14 integrations 20 0 20 0 0 0 20 0 0 0
15 issuerdirectory 12 0 12 0 0 0 12 0 0 12
16 notifier 197 0 197 0 0 0 197 0 0 164
17 notify 49 0 49 0 0 0 49 0 0 49
18 opsmemory 12 0 12 0 0 0 12 0 0 0
19 orchestrator 313 0 313 0 0 0 313 0 0 0
20 packsregistry 19 0 19 0 0 0 19 0 0 19
21 platform 165 0 165 0 0 0 165 0 0 139
22 policy-engine 202 0 202 0 0 0 202 0 0 170
23 policy-gateway 121 0 121 0 0 0 121 0 0 54
24 reachgraph 17 0 17 0 0 0 17 0 0 17
25 replay 15 0 15 0 0 0 15 0 0 15
26 riskengine 9 0 9 0 0 0 9 0 0 5
27 sbomservice 51 0 51 0 0 0 51 0 0 51
28 scanner 168 0 168 0 0 128 40 0 0 103
29 scheduler 37 0 37 0 0 0 37 0 0 37
30 signals 23 0 23 0 0 0 23 0 0 23
31 signer 15 0 15 0 0 0 15 0 0 15
32 smremote 6 0 6 0 0 0 6 0 0 6
33 symbols 19 0 19 0 0 0 19 0 0 19
34 taskrunner 33 0 33 0 0 0 33 0 0 33
35 timelineindexer 12 0 12 12 0 12 0 0 0 0
36 unknowns 8 0 8 0 0 0 8 0 0 0
37 vexhub 16 0 16 0 0 0 16 0 0 0
38 vexlens 36 0 36 0 0 0 36 0 0 0
39 vulnexplorer 10 0 10 0 0 0 10 0 0 10

View File

@@ -0,0 +1,14 @@
{
"generatedUtc": "2026-02-22T17:24:57Z",
"endpointCount": 2190,
"anonymousEndpoints": 6,
"requiresAuthEndpoints": 2184,
"endpointsWithScopes": 28,
"endpointsWithRoles": 0,
"endpointsWithPolicies": 156,
"authSourceNone": 1991,
"missingDescriptions": 0,
"genericHttpDescriptions": 0,
"sameAsSummaryDescriptions": 1417,
"tooShortDescriptions": 90
}

View File

@@ -17,6 +17,7 @@ Advisory AI is the retrieval-augmented assistant that synthesizes advisory and V
- RAG pipeline drawing from Conseiller, Excititor, VEX Lens, Policy Engine, and SBOM Service data.
- Prompt templates and guard models enforcing provenance and redaction policies.
- Vercel/offline inference workers with deterministic caching of generated artefacts.
- AdvisoryAI Knowledge Search (AKS) for deterministic docs/API/Doctor retrieval: `docs/modules/advisory-ai/knowledge-search.md`.
## Integrations & dependencies
- Authority for tenant-aware access control.

View File

@@ -202,6 +202,79 @@ Fetch cached artefact (same envelope as §6). Requires `advisory-ai:view`.
When plan preview is enabled (feature flag `advisoryAi.planPreview.enabled`), this endpoint returns the orchestration plan using `AdvisoryPipelinePlanResponse` (task metadata, chunk/vector counts). Requires `advisory-ai:operate`.
### 7.8 `LLM Adapter Exposure (RVM-08)`
AdvisoryAI now exposes a unified adapter surface for provider discovery and OpenAI-compatible completion calls.
- `GET /v1/advisory-ai/adapters/llm/providers`
- `POST /v1/advisory-ai/adapters/llm/{providerId}/chat/completions`
- `POST /v1/advisory-ai/adapters/openai/v1/chat/completions` (alias for `providerId=openai`)
Scope requirements:
- Read/discovery: `advisory:adapter:read` or `advisory:run`
- Invoke/completion: `advisory:adapter:invoke` or `advisory:openai:invoke` or `advisory:{providerId}:invoke` or `advisory:run`
Operational notes:
- Adapter endpoints are disabled by default and must be explicitly enabled by config.
- `stream=true` is currently rejected on adapter completion endpoints.
- Provider discovery returns configuration/validation/exposure state per provider so operators can verify whether `openai` is configured before traffic cutover.
Runtime config keys:
- `AdvisoryAI:Adapters:Llm:Enabled` (env `ADVISORYAI__AdvisoryAI__Adapters__Llm__Enabled`)
- `AdvisoryAI:LlmProviders:ConfigDirectory` (env `ADVISORYAI__AdvisoryAI__LlmProviders__ConfigDirectory`)
Gateway exposure paths:
- `/v1/advisory-ai/adapters`
- `/api/v1/advisory-ai/adapters`
### 7.9 `Knowledge Search (AKS)`
Deterministic retrieval API for docs, OpenAPI operations, and Doctor checks.
- `POST /v1/advisory-ai/search`
- `POST /v1/advisory-ai/index/rebuild`
Scope notes:
- Search: one of `advisory:run`, `advisory:search`, `advisory:read`.
- Rebuild: one of `advisory:run`, `advisory:admin`, `advisory:index:write`.
Search request:
```json
{
"q": "docker login fails with x509 unknown authority",
"k": 10,
"filters": {
"type": ["docs", "doctor"],
"product": "stella-ops",
"version": "2026.02",
"service": "gateway",
"tags": ["troubleshooting", "tls"]
},
"includeDebug": false
}
```
Search response contains only grounded results with source actions:
- docs: `{ path, anchor, spanStart, spanEnd }`
- api: `{ service, method, path, operationId }`
- doctor: `{ checkCode, severity, canRun, runCommand }`
Rebuild response:
```json
{
"documentCount": 12034,
"chunkCount": 68291,
"apiSpecCount": 41,
"apiOperationCount": 2132,
"doctorProjectionCount": 84,
"durationMs": 8245
}
```
## 8. Error model
Errors follow a standard problem+JSON envelope:

View File

@@ -0,0 +1,137 @@
# AdvisoryAI Knowledge Search (AKS)
## Why retrieval-first
AKS is a deterministic retrieval system for operational problem solving across Stella Ops docs, OpenAPI contracts, and Doctor checks. It is designed to work offline and does not require GPU-backed or hosted LLM inference for correctness.
LLMs can still be used as optional formatters later, but AKS correctness is grounded in source retrieval and explicit references.
## Scope
- Module owner: `src/AdvisoryAI/**`.
- Search surfaces consuming AKS:
- Web global search in `src/Web/StellaOps.Web/**`.
- CLI commands in `src/Cli/**`.
- Doctor execution remains authoritative in Doctor module. AKS only indexes metadata and remediation references.
## Architecture
1. Ingestion/indexing:
- Markdown (`docs/**`) -> section chunks.
- OpenAPI (`openapi.json`) -> per-operation chunks + normalized operation tables.
- Doctor seed/metadata -> doctor projection chunks.
2. Storage:
- PostgreSQL tables in schema `advisoryai` via migration `src/AdvisoryAI/StellaOps.AdvisoryAI/Storage/Migrations/002_knowledge_search.sql`.
3. Retrieval:
- FTS (`tsvector` + `websearch_to_tsquery`) + optional vector stage.
- Deterministic fusion and tie-breaking in `KnowledgeSearchService`.
4. Delivery:
- API endpoint: `POST /v1/advisory-ai/search`.
- Index rebuild endpoint: `POST /v1/advisory-ai/index/rebuild`.
## Data model
AKS schema tables:
- `advisoryai.kb_doc`: canonical source docs with product/version/content hash metadata.
- `advisoryai.kb_chunk`: searchable units (`md_section`, `api_operation`, `doctor_check`) with anchors, spans, `tsvector`, and embeddings.
- `advisoryai.api_spec`: raw OpenAPI snapshot (`jsonb`) by service.
- `advisoryai.api_operation`: normalized operation records (`method`, `path`, `operation_id`, tags, request/response/security json).
- `advisoryai.doctor_search_projection`: searchable doctor metadata and remediation.
Vector support:
- Tries `CREATE EXTENSION vector`.
- If unavailable, AKS remains fully functional via FTS and deterministic array embeddings fallback.
## Deterministic ingestion rules
### Markdown
- Chunk by H2/H3 headings.
- Stable anchors using slug + duplicate suffix.
- Stable chunk IDs from source path + anchor + span.
- Metadata includes path, anchor, section path, tags.
### OpenAPI
- Parse `openapi.json` only for deterministic MVP.
- Emit one searchable chunk per HTTP operation.
- Preserve structured operation payloads (`request_json`, `responses_json`, `security_json`).
### Doctor
- Source order:
1. Seed file `src/AdvisoryAI/StellaOps.AdvisoryAI/KnowledgeSearch/doctor-search-seed.json`.
2. Optional Doctor endpoint metadata (`DoctorChecksEndpoint`) when configured.
- Emit doctor chunk + projection record including:
- `checkCode`, `title`, `severity`, `runCommand`, remediation, symptoms.
## Ranking strategy
Implemented in `src/AdvisoryAI/StellaOps.AdvisoryAI/KnowledgeSearch/KnowledgeSearchService.cs`:
- Candidate retrieval:
- lexical set from FTS.
- optional vector set from embedding candidates.
- Fusion:
- reciprocal rank fusion style scoring.
- Deterministic boosts:
- exact `checkCode` match.
- exact `operationId` match.
- `METHOD /path` match.
- filter-aligned service/tag boosts.
- Deterministic ordering:
- score desc -> kind asc -> chunk id asc.
## API contract
### Search
- `POST /v1/advisory-ai/search`
- Request:
- `q` (required), `k`, `filters.type|product|version|service|tags`, `includeDebug`.
- Response:
- typed results (`docs|api|doctor`) with snippet, score, and open action.
### Rebuild
- `POST /v1/advisory-ai/index/rebuild`
- Rebuilds AKS deterministically from local docs/specs/doctor metadata.
## Web behavior
Global search now consumes AKS and supports:
- Mixed grouped results (`Docs`, `API Endpoints`, `Doctor Checks`).
- Type filter chips.
- Result actions:
- Docs: `Open`.
- API: `Curl` (copy command).
- Doctor: `Run` (navigate to doctor and copy run command).
- `More` action for "show more like this" local query expansion.
## CLI behavior
AKS commands:
- `stella search "<query>" [--type docs|api|doctor] [--product ...] [--version ...] [--service ...] [--tag ...] [--k N] [--json]`
- `stella doctor suggest "<symptom>" [--product ...] [--version ...] [--k N] [--json]`
- `stella advisoryai index rebuild [--json]`
Output:
- Human mode: grouped actionable references.
- JSON mode: stable machine-readable payload.
## Test/benchmark strategy
Implemented benchmark framework:
- Generator: `KnowledgeSearchBenchmarkDatasetGenerator` (deterministic synthetic set with explicit ground truth).
- Runner: `KnowledgeSearchBenchmarkRunner` (recall@k, p50/p95 latency, stability pass).
- Models/serialization:
- `KnowledgeSearchBenchmarkModels.cs`
- `KnowledgeSearchBenchmarkJson.cs`
Tests:
- `src/AdvisoryAI/__Tests/StellaOps.AdvisoryAI.Tests/KnowledgeSearch/KnowledgeSearchBenchmarkTests.cs`
- verifies deterministic dataset generation with >= 1000 queries.
- verifies recall/latency metrics and top-k match behavior.
## Dedicated AKS test DB
Compose profile:
- `devops/compose/docker-compose.advisoryai-knowledge-test.yml`
Init script:
- `devops/compose/postgres-init/advisoryai-knowledge-test/01_extensions.sql`
Example workflow:
```bash
docker compose -f devops/compose/docker-compose.advisoryai-knowledge-test.yml up -d
stella advisoryai index rebuild --json
dotnet test src/AdvisoryAI/__Tests/StellaOps.AdvisoryAI.Tests/StellaOps.AdvisoryAI.Tests.csproj
```
## Known limitations and follow-ups
- YAML OpenAPI ingestion is not included in MVP.
- End-to-end benchmark against live Postgres-backed AKS service is planned as a follow-up CI lane.
- Optional external embedding providers can be added later without changing API contracts.

View File

@@ -20,6 +20,50 @@ Both commands are designed to enforce the AOC guardrails documented in the [aggr
---
## 1.1 AdvisoryAI Knowledge Search
AKS commands expose deterministic retrieval from AdvisoryAI (`docs|api|doctor`) without requiring an LLM.
### `stella search`
```bash
stella search "<query>" \
[--type docs|api|doctor] \
[--product <product>] \
[--version <version>] \
[--service <service>] \
[--tag <tag>] \
[--k <1-100>] \
[--json]
```
Notes:
- `--type` and `--tag` are repeatable or comma-separated.
- `--json` emits stable machine-readable payload with `results[].open`.
### `stella doctor suggest`
```bash
stella doctor suggest "<symptom-or-error>" \
[--product <product>] \
[--version <version>] \
[--k <1-100>] \
[--json]
```
Uses the same AKS index and prints grouped recommendations for:
- Doctor checks (with run command).
- Related docs anchors.
- Related API operations.
### `stella advisoryai index rebuild`
```bash
stella advisoryai index rebuild [--json]
```
Rebuilds the deterministic AKS index from local markdown, OpenAPI, and Doctor metadata sources.
## 2·`stella sources ingest --dry-run`
### 2.1Synopsis

View File

@@ -175,20 +175,39 @@ Each endpoint with claims gets a security requirement:
```csharp
public static JsonArray GenerateSecurityRequirement(EndpointDescriptor endpoint)
{
if (endpoint.RequiringClaims.Count == 0)
return new JsonArray(); // No security required
if (endpoint.AllowAnonymous)
return new JsonArray(); // Anonymous endpoint
if (!endpoint.RequiresAuthentication && endpoint.RequiringClaims.Count == 0)
return new JsonArray(); // No auth semantics published
return new JsonArray
{
new JsonObject
{
["BearerAuth"] = new JsonArray(),
["OAuth2"] = new JsonArray(claims.Select(c => c.Type))
["OAuth2"] = new JsonArray(scopes.Select(scope => scope))
}
};
}
```
### Router-specific OpenAPI extensions
Gateway now emits Router-specific extensions on each operation:
- `x-stellaops-gateway-auth`: effective authorization semantics projected from endpoint metadata.
- `allowAnonymous`
- `requiresAuthentication`
- `source` (`None`, `AspNetMetadata`, `YamlOverride`, `Hybrid`)
- optional `policies`, `roles`, `claimRequirements`
- `x-stellaops-timeout`: timeout semantics used by gateway dispatch.
- `effectiveSeconds`
- `source` (`endpoint`, `gatewayRouteDefault`, and capped variants)
- `endpointSeconds`, `gatewayRouteDefaultSeconds`, `gatewayGlobalCapSeconds` when available
- precedence list: endpoint override -> service default -> gateway route default -> gateway global cap
- `x-stellaops-timeout-seconds`: backward-compatible scalar alias for `effectiveSeconds`.
---
## Configuration Reference

View File

@@ -90,6 +90,11 @@ StellaOps.Router.slnx
| [rate-limiting.md](rate-limiting.md) | Centralized router rate limiting (dossier) |
| [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 |
| [timelineindexer-microservice-pilot.md](timelineindexer-microservice-pilot.md) | TimelineIndexer Valkey microservice transport pilot mapping and rollback |
| [webservices-valkey-rollout-matrix.md](webservices-valkey-rollout-matrix.md) | All-webservices Valkey microservice migration matrix (waves, owners, rollback) |
| [microservice-transport-guardrails.md](microservice-transport-guardrails.md) | Plugin-only transport guardrails and migration PR checklist |
| [authority-gateway-enforcement-runbook.md](authority-gateway-enforcement-runbook.md) | Operations runbook for gateway-enforced auth and signed identity envelope trust |
| [rollout-acceptance-20260222.md](rollout-acceptance-20260222.md) | Dual-mode rollout acceptance package and evidence index |
### Implementation Guides (docs/modules/router/guides/)
| Document | Purpose |

View File

@@ -0,0 +1,81 @@
# Router Authority Enforcement Runbook
This runbook documents operational semantics for gateway-enforced authorization and signed user-identity propagation in Stella Router deployments.
## Scope
- Router gateway as centralized policy decision point (PDP).
- Authority-driven effective claims.
- Signed identity envelope propagation to webservices using Router trust mode.
## Required Configuration
Gateway-side:
- `Gateway__Auth__Authority__Issuer`
- `Gateway__Auth__Authority__RequireHttpsMetadata`
- `Router__OnMissingAuthorization`
Service-side (per service `Router` section):
- `Router__Enabled`
- `Router__AuthorizationTrustMode`
- `Router__IdentityEnvelopeSigningKey`
- `Router__IdentityEnvelopeClockSkewSeconds`
Identity transport headers emitted by gateway:
- `X-StellaOps-Identity-Envelope`
- `X-StellaOps-Identity-Envelope-Signature`
- `X-StellaOps-Identity-Envelope-Alg`
- `X-StellaOps-Identity-Envelope-Kid`
- `X-StellaOps-Identity-Envelope-Iat`
- `X-StellaOps-Identity-Envelope-Exp`
## Trust Modes
`ServiceEnforced`
- Service enforces its local policies.
- Gateway envelope is optional for service authorization.
`Hybrid`
- Service accepts gateway envelope when present.
- Service can fall back to local checks for compatibility.
`GatewayEnforced`
- Service requires valid signed gateway envelope.
- Missing/invalid envelope is fail-closed (`403`/`401` based on service policy).
- Use for centralized authorization rollout after verification.
## Key Rotation Procedure
1. Rotate Authority/gateway signing material using the Authority key-rotation SOP:
- `docs/modules/authority/operations/key-rotation.md`
2. Update `Router__IdentityEnvelopeSigningKey` for gateway and services.
3. Restart gateway and services in wave order (gateway first).
4. Validate with targeted tests and route smoke.
## Authority Outage and Fallback
If Authority is degraded/unreachable:
1. Keep gateway running with last known effective claim cache.
2. For critical service continuity, temporarily switch affected services:
- `Router__AuthorizationTrustMode=Hybrid`
3. If envelope verification is disrupted by key mismatch, switch to `ServiceEnforced` only as emergency fallback.
4. Record incident window and restore target mode (`GatewayEnforced` or `Hybrid`) after recovery.
## Compose Toggle Examples
Global gateway mode:
```powershell
$env:ROUTER_GATEWAY_CONFIG = "./router-gateway-local.json"
docker compose -f devops/compose/docker-compose.stella-ops.yml up -d
```
Emergency compatibility fallback (example service):
```powershell
$env:SCANNER_ROUTER_ENABLED = "true"
$env:Router__AuthorizationTrustMode = "Hybrid"
docker compose -f devops/compose/docker-compose.stella-ops.yml up -d scanner-web
```
## Verification Checklist
- Gateway route smoke has `500=0`.
- OpenAPI operations expose `x-stellaops-gateway-auth`.
- Envelope spoofing tests pass (`IdentityHeaderPolicyMiddlewareTests`).
- Authority refresh tests pass (`AuthorityClaimsRefreshServiceTests`).

View File

@@ -74,9 +74,14 @@ if (bootstrapOptions.Transports.Messaging.Enabled)
```
### Microservice
- Register Valkey messaging services (`StellaOps.Messaging.Transport.Valkey`)
- Add messaging transport client (`AddMessagingTransportClient`)
- Ensure Microservice Router SDK connects via `IMicroserviceTransport`
- Register router transports via plugin loading (no hard transport references in `StellaOps.Router.AspNet`).
- Use `AddRouterMicroservice(...)` from `StellaOps.Router.AspNet`; it resolves configured gateway transport types through `RouterTransportPluginLoader`.
- For messaging mode, the `StellaOps.Router.Transport.Messaging` plugin registers:
- backend messaging plugin loading (`AddMessagingPlugins(...)`, env/config key `transport=valkey`)
- Router messaging transport client (`AddMessagingTransportClient`)
- Ensure the following plugin DLLs are available either as service dependencies or under configured plugin directories:
- `StellaOps.Router.Transport.Messaging.dll`
- `StellaOps.Messaging.Transport.Valkey.dll`
## Operational Semantics (Draft)
- **At-least-once** delivery: message queues and leases imply retries are possible; handlers should be idempotent where feasible.
@@ -104,4 +109,3 @@ if (bootstrapOptions.Transports.Messaging.Enabled)
- microservice HELLO registration via messaging
- request dispatch + response return
3. Validate streaming support (or document as out-of-scope).

View File

@@ -0,0 +1,51 @@
# Router Microservice Transport Guardrails
This document defines mandatory guardrails for migrating `StellaOps.*.WebService` services to Router microservice transport over Valkey messaging.
## Required Contract
- Service startup must use `AddRouterMicroservice(...)` from `StellaOps.Router.AspNet`.
- Router transport activation must be configuration-driven from environment/compose keys.
- Transport implementation loading must remain plugin-based:
- Router transport plugins from `Router:TransportPlugins:*`.
- Messaging backend plugin from `Router:Messaging:PluginDirectory` and `Router:Messaging:SearchPattern`.
- Messaging transport selection must be explicit (`Router:Messaging:Transport=valkey` for this rollout).
- Services must keep rollback-compatible toggles so routing can return to reverse proxy without code edits.
## Required Configuration Keys
- `<Service>:Router:Enabled`
- `<Service>:Router:Gateways:*:TransportType`
- `<Service>:Router:TransportPlugins:Directory`
- `<Service>:Router:TransportPlugins:SearchPattern`
- `<Service>:Router:Messaging:Transport`
- `<Service>:Router:Messaging:PluginDirectory`
- `<Service>:Router:Messaging:SearchPattern`
- `<Service>:Router:Messaging:RequestQueueTemplate`
- `<Service>:Router:Messaging:ResponseQueueName`
- `<Service>:Router:Messaging:ConsumerGroup`
- `<Service>:Router:Messaging:valkey:ConnectionString`
- `<Service>:Router:Messaging:valkey:Database`
## Forbidden Patterns
- Direct service-level DI calls to concrete transport registration methods such as:
- `AddMessagingTransportClient()`
- `AddTcpTransportClient()`
- `AddUdpTransportClient()`
- `AddRabbitMqTransportClient()`
- `AddTlsTransportClient()`
- Compile-time coupling from webservice projects to transport assemblies solely to enable default behavior.
- Hardcoded transport selection in code paths that bypass environment/compose configuration.
## Required Test Coverage
- Router enabled with missing gateway endpoints must fail fast.
- Missing/invalid router options section input must fail fast.
- Missing transport plugin for configured `TransportType` must fail fast with explicit error text.
- Messaging transport binding must verify queue/timeouts and Valkey options from configuration.
## Migration PR Checklist
- [ ] Service startup uses `AddRouterMicroservice(...)` and retains `TryUseStellaRouter(...)` behavior.
- [ ] No direct concrete transport registration calls are added in service DI.
- [ ] Compose/environment keys are added for router enablement, plugin directories, and Valkey settings.
- [ ] Gateway route plan updated (`ReverseProxy` -> `Microservice`) with rollback instructions.
- [ ] OpenAPI preview (`/openapi.json`) shows migrated endpoint paths plus schema components.
- [ ] Targeted tests include plugin-missing and section-validation failure cases.
- [ ] Sprint execution log and decisions/risks updated with doc links and evidence.

View File

@@ -12,6 +12,9 @@ The router provides a transport-agnostic communication layer between services, r
- **Claims**: Authority-integrated authorization
- **Health**: Automatic heartbeat and failover
Active rollout planning artifact:
- [webservices-valkey-rollout-matrix.md](webservices-valkey-rollout-matrix.md) - source-of-truth matrix for wave assignment, acceptance owners, and rollback switches for all gateway reverse-proxy service hosts.
## Prerequisites
Before migrating, ensure:

View File

@@ -215,6 +215,14 @@ Client credentials flow with collected scopes (only if endpoints have claims):
Scopes are automatically collected from all connected services. If multiple endpoints require the same claim, it appears only once in the scopes list.
### Legacy HELLO Compatibility
`x-stellaops-gateway-auth.requiresAuthentication` is emitted from the Gateway's effective authorization semantics, not only the raw endpoint flag.
- If a microservice HELLO payload comes from an older router-common contract that does not include `requiresAuthentication`, the Gateway fails closed.
- For `allowAnonymous: false` endpoints with no explicit auth flag, the Gateway treats the route as authenticated-only.
- Public routes must be explicitly marked `AllowAnonymous` in the microservice to avoid accidental protection.
---
## Generated Document Structure

View File

@@ -0,0 +1,78 @@
# Router Rollout Acceptance Package (2026-02-22)
## Scope
- Program closeout for Router sprints `047-050`.
- Dual-mode verification:
- Default microservice mode (`router-gateway-local.json`).
- Reverse-proxy fallback mode (`router-gateway-local.reverseproxy.json`).
## Command Matrix
- Image refresh:
- `docker build ... -t stellaops/scanner-web:dev`
- `docker build ... -t stellaops/integrations-web:dev`
- `docker build ... -t stellaops/gateway:dev`
- `docker build ... -t stellaops/doctor-web:dev`
- Clean bootstrap:
- `devops/compose/scripts/router-mode-redeploy.ps1 -Mode reverseproxy`
- `devops/compose/scripts/router-mode-redeploy.ps1 -Mode microservice`
- Route smoke:
- `devops/compose/scripts/router-routeprefix-smoke.ps1`
- Conformance tests:
- `dotnet test src/Router/__Tests/StellaOps.Router.AspNet.Tests/StellaOps.Router.AspNet.Tests.csproj`
- `dotnet test src/Router/__Tests/StellaOps.Router.Gateway.Tests/StellaOps.Router.Gateway.Tests.csproj`
- `dotnet test src/Router/__Tests/StellaOps.Gateway.WebService.Tests/StellaOps.Gateway.WebService.Tests.csproj`
## Evidence Artifacts
- `devops/compose/openapi_current.json`
- `devops/compose/openapi_reverse.json`
- `devops/compose/openapi_routeprefix_smoke_microservice.csv`
- `devops/compose/openapi_routeprefix_smoke_reverseproxy.csv`
- `devops/compose/openapi_quality_report_microservice.json`
- `devops/compose/openapi_quality_report_reverseproxy.json`
- `devops/compose/perf_microservice.json`
- `devops/compose/perf_reverseproxy.json`
- `devops/compose/perf_mode_comparison.json`
- `devops/compose/timeline.json`
- `devops/compose/timeline_schemas.json`
- `devops/compose/openai_adapter.json`
- `devops/compose/llm_providers.json`
## Acceptance Results
Microservice mode:
- Route smoke summary: `200=30,302=4,400=25,401=14,403=2,404=3,410=1`
- Route smoke blocker status: `500=0`
- OpenAPI counts: `paths=1899`, `schemas=901`
- Timeline contract gate: 4 timeline operations with summary/description/security/timeouts/auth extensions.
- Advisory AI adapter exposure: 2 LLM adapter operations in aggregated `openapi.json`; providers endpoint denies anonymous access (`403`).
Reverse mode:
- Route smoke summary: `200=15,400=6,401=18,404=15`
- Route smoke blocker status: `500=0`
- OpenAPI counts: `paths=1529`, `schemas=901`
- Reverse-proxy exception set remains explicit (`/rekor`, `/platform`, static SPA/error routes).
Security and authorization:
- Gateway and Router conformance suites pass.
- Identity-header spoofing protections covered in gateway test suite.
- Authority claims refresh and effective-claim integration covered in Router gateway tests.
## Performance Gate Decision
- Baseline: reverse-proxy mode.
- Candidate: microservice mode.
- Comparison artifact: `devops/compose/perf_mode_comparison.json`.
- Result:
- `openapi.json` and timeline unauthorized path remained within acceptable local-dev drift.
- Advisory AI adapter unauthorized path showed higher latency in microservice mode; accepted for now as non-blocking because error-rate and contract gates passed, with follow-up hardening item retained in Router backlog.
## Rollback
- Scripted mode rollback:
- `devops/compose/scripts/router-mode-redeploy.ps1 -Mode reverseproxy`
- Return to default:
- `devops/compose/scripts/router-mode-redeploy.ps1 -Mode microservice`
- The redeploy helper now auto-recovers transient unhealthy services (bounded retries) before declaring failure.
## Final Runtime State
- Default compose mode restored to microservice routing:
- `ROUTER_GATEWAY_CONFIG=./router-gateway-local.json`
- Timeline route `/api/v1/timeline` remains `Type=Microservice` with Valkey messaging transport.

View File

@@ -0,0 +1,39 @@
# Router TimelineIndexer Microservice Pilot
## Scope
- Pilot service: `TimelineIndexer` (`src/TimelineIndexer/StellaOps.TimelineIndexer/StellaOps.TimelineIndexer.WebService`).
- Transport: `TransportType.Messaging` backed by Valkey.
- Gateway entry under pilot: `/api/v1/timeline*`.
## Baseline
- Previous gateway route:
- `ReverseProxy /api/v1/timeline -> http://timelineindexer.stella-ops.local/api/v1/timeline`
- Reverse proxy mode strips the matched prefix before forwarding.
- Microservice mode does not strip prefixes and routes by method+path identity.
## Path Compatibility Mapping
| External path | Reverse proxy behavior | Required microservice path | Pilot status |
| --- | --- | --- | --- |
| `/api/v1/timeline` | proxied to `/api/v1/timeline` upstream | `/api/v1/timeline` | converted |
| `/api/v1/timeline/{eventId}` | proxied to `/api/v1/timeline/{eventId}` upstream | `/api/v1/timeline/{eventId}` | converted |
| `/api/v1/timeline/{eventId}/evidence` | proxied to `/api/v1/timeline/{eventId}/evidence` upstream | `/api/v1/timeline/{eventId}/evidence` | converted |
| `/api/v1/timeline/events` | proxied to `/api/v1/timeline/events` upstream | `/api/v1/timeline/events` | converted |
| `/timelineindexer/*` | reverse-proxy prefix path for direct service access | unchanged (still reverse proxy) | unchanged |
TimelineIndexer now exposes both native and gateway-alias endpoints:
- native: `/timeline*`
- gateway alias: `/api/v1/timeline*`
## Compose Activation
- Gateway messaging toggle: `ROUTER_GATEWAY_MESSAGING_ENABLED` (default `true`).
- TimelineIndexer router toggle: `TIMELINE_ROUTER_ENABLED` (default `true`).
- Compose env vars are applied on `timeline-indexer-web` (not `vexlens-web`) and mapped to:
- `TimelineIndexer:Router`
- `TimelineIndexer:Router:TransportPlugins:*`
- `TimelineIndexer:Router:Messaging:*`
- Valkey backend selection is environment-driven via `TimelineIndexer:Router:Messaging:Transport=valkey`.
## Rollback
1. Set `TIMELINE_ROUTER_ENABLED=false` and `ROUTER_GATEWAY_MESSAGING_ENABLED=false` in compose environment.
2. Revert route entry in `devops/compose/router-gateway-local.json` from `Microservice` back to `ReverseProxy`.
3. Re-deploy stack and verify `/api/v1/timeline*` responses through reverse proxy path.

View File

@@ -48,6 +48,94 @@ builder.Services.TryAddStellaRouter(
routerOptions: options.Router);
```
#### Optional: generic microservice transport registration
For services that should auto-register transport clients from configuration, use:
```csharp
builder.Services.AddRouterMicroservice(
builder.Configuration,
serviceName: "my-service-name",
version: typeof(Program).Assembly.GetName().Version?.ToString() ?? "1.0.0",
routerOptionsSection: "MyService:Router");
```
`AddRouterMicroservice(...)` keeps `TryAddStellaRouter(...)` behavior and registers transport clients through `RouterTransportPluginLoader` based on configured gateway transport types (`InMemory`, `Tcp`, `Certificate`/`tls`, `Udp`, `RabbitMq`, `Messaging`).
The `StellaOps.Router.AspNet` library does not hard-reference transport assemblies; transports are activated from plugin DLLs and environment/config values.
For Valkey messaging mode, configure:
```yaml
myservice:
router:
enabled: true
region: "local"
transportPlugins:
directory: "plugins/router/transports"
searchPattern: "StellaOps.Router.Transport.*.dll"
gateways:
- host: "router.stella-ops.local"
port: 9100
transportType: "Messaging"
messaging:
transport: "valkey"
pluginDirectory: "plugins/messaging"
searchPattern: "StellaOps.Messaging.Transport.*.dll"
requestQueueTemplate: "router:requests:{service}"
responseQueueName: "router:responses"
consumerGroup: "myservice"
requestTimeout: "30s"
leaseDuration: "5m"
batchSize: 10
heartbeatInterval: "10s"
valkey:
connectionString: "cache.stella-ops.local:6379"
```
### 2.2 Gateway trust mode and identity envelope verification
Service-side Router bridge can enforce gateway-issued identity semantics:
```yaml
myservice:
router:
authorizationTrustMode: "GatewayEnforced" # ServiceEnforced | Hybrid | GatewayEnforced
identityEnvelopeSigningKey: "${ROUTER_IDENTITY_SIGNING_KEY}"
identityEnvelopeClockSkewSeconds: 30
```
- `ServiceEnforced`: service-local checks remain primary.
- `Hybrid`: prefer signed envelope; fallback to legacy headers.
- `GatewayEnforced`: fail closed when envelope is missing/invalid.
### 2.3 Timeout precedence
Gateway dispatch timeout is now resolved with explicit precedence:
1. Endpoint timeout (including endpoint override/service default published by service).
2. Route default timeout (optional per gateway route via `defaultTimeout`).
3. Gateway routing default timeout (`Gateway:Routing:DefaultTimeout`).
4. Global gateway cap (`Gateway:Routing:GlobalTimeoutCap`).
Route-level timeout example:
```yaml
gateway:
routing:
defaultTimeout: "30s"
globalTimeoutCap: "120s"
routes:
- type: Microservice
path: "/api/v1/timeline"
translatesTo: "http://timelineindexer.stella-ops.local/api/v1/timeline"
defaultTimeout: "15s"
```
### 2.1 Gateway SPA deep-link handling with microservice routes
When gateway route prefixes overlap with UI routes (for example `/policy`), browser navigations must still resolve to the SPA shell.
Gateway `RouteDispatchMiddleware` now serves the configured static SPA fallback route for browser document requests on both `ReverseProxy` and `Microservice` route types. API prefixes (`/api`, `/v1`) are explicitly excluded from this fallback and continue to dispatch to backend services.
### 3. Enable Middleware
After `UseAuthorization()`, add:

View File

@@ -0,0 +1,75 @@
# Router Valkey Microservice Rollout Matrix (All WebServices)
## Scope
- Source route table: `devops/compose/router-gateway-local.json`
- Source service aliases: `devops/compose/docker-compose.stella-ops.yml`
- Snapshot date (UTC): 2026-02-21
- ReverseProxy routes in snapshot: 116
- Distinct target service hosts: 42
Legend:
- `Wave`: `A`, `B`, `C`, `D`, `PILOT`
- `Acceptance Owner`: rollout sign-off owner for that host's cutover wave
- `Migration Disposition`: current route-level migration intent
## Service Migration Matrix
| Service Host | Compose Service | Current ReverseProxy Path Prefixes | Wave | Acceptance Owner | Migration Disposition | Rollback Switch |
| --- | --- | --- | --- | --- | --- | --- |
| advisoryai.stella-ops.local | advisory-ai-web | /advisoryai, /api/v1/advisory, /api/v1/advisory-ai, /api/v1/advisory-ai/adapters, /v1/advisory-ai, /v1/advisory-ai/adapters | A | Developer + Test Automation (Wave A) | Migrate API prefixes to Microservice; keep root compatibility path until cutover acceptance. | Route type revert in `router-gateway-local.json` + `ADVISORYAI_ROUTER_ENABLED=false` (standardized in RMW-03). |
| airgap-controller.stella-ops.local | airgap-controller | /airgapController | A | Developer + Test Automation (Wave A) | Add API-form microservice endpoint mapping and migrate root compatibility path in same wave. | Route type revert + `AIRGAP_CONTROLLER_ROUTER_ENABLED=false` (RMW-03). |
| airgap-time.stella-ops.local | airgap-time | /airgapTime | A | Developer + Test Automation (Wave A) | Add API-form microservice endpoint mapping and migrate root compatibility path in same wave. | Route type revert + `AIRGAP_TIME_ROUTER_ENABLED=false` (RMW-03). |
| attestor.stella-ops.local | attestor | /api/v1/attestations, /api/v1/attestor, /api/v1/witnesses, /attestor | B | Developer + Test Automation (Wave B) | Migrate API prefixes first; keep root compatibility route until evidence-plane acceptance sign-off. | Route type revert + `ATTESTOR_ROUTER_ENABLED=false` (RMW-03). |
| authority.stella-ops.local | authority | /.well-known, /api/v1/authority, /api/v1/trust, /authority, /connect, /console, /jwks | B | Developer + Test Automation (Wave B) | Migrate Authority API and OIDC identity routes to Microservice; use in-service OIDC bridge endpoints (`/connect/*`, `/well-known/openid-configuration`) for protocol compatibility. | Route type revert + `AUTHORITY_ROUTER_ENABLED=false` (RMW-03). |
| binaryindex.stella-ops.local | binaryindex-web | /api/v1/ops/binaryindex, /api/v1/resolve, /binaryindex | A | Developer + Test Automation (Wave A) | Migrate API prefixes to Microservice; keep root compatibility path during transition. | Route type revert + `BINARYINDEX_ROUTER_ENABLED=false` (RMW-03). |
| cartographer.stella-ops.local | cartographer | /cartographer | D | Developer + Test Automation (Wave D) | Introduce API alias if required, then migrate route to Microservice in Wave D. | Route type revert + `CARTOGRAPHER_ROUTER_ENABLED=false` (RMW-03). |
| concelier.stella-ops.local | concelier | /api/v1/concelier, /concelier | D | Developer + Test Automation (Wave D) | Migrate API prefix first, then root compatibility route. | Route type revert + `CONCELIER_ROUTER_ENABLED=false` (RMW-03). |
| doctor.stella-ops.local | doctor-web | /api/doctor, /doctor | D | Developer + Test Automation (Wave D) | Migrate API prefix first; keep root compatibility path until UI/runtime consumers are validated. | Route type revert + `DOCTOR_ROUTER_ENABLED=false` (RMW-03). |
| doctor-scheduler.stella-ops.local | doctor-scheduler | /api/v1/doctor/scheduler | D | Developer + Test Automation (Wave D) | Migrate API prefix directly to Microservice. | Route type revert + `DOCTOR_SCHEDULER_ROUTER_ENABLED=false` (RMW-03). |
| evidencelocker.stella-ops.local | evidence-locker-web | /api/v1/evidence, /api/v1/proofs, /api/v1/verdicts, /api/verdicts, /evidencelocker, /v1/evidence-packs | B | Developer + Test Automation (Wave B) | Migrate API/v1 and v1 endpoints first; keep root compatibility path until evidence workflows pass QA. | Route type revert + `EVIDENCELOCKER_ROUTER_ENABLED=false` (RMW-03). |
| excititor.stella-ops.local | excititor | /excititor | D | Developer + Test Automation (Wave D) | Add API-form microservice mapping if needed; migrate root compatibility route in Wave D. | Route type revert + `EXCITITOR_ROUTER_ENABLED=false` (RMW-03). |
| exportcenter.stella-ops.local | export | /api/v1/export, /exportcenter, /v1/audit-bundles | B | Developer + Test Automation (Wave B) | Migrate API/v1 and v1 routes first; keep root compatibility path until trust/evidence export checks pass. | Route type revert + `EXPORTCENTER_ROUTER_ENABLED=false` (RMW-03). |
| findings.stella-ops.local | findings-ledger-web | /api/v1/findings, /findingsLedger | D | Developer + Test Automation (Wave D) | Migrate API prefix first, then root compatibility path. | Route type revert + `FINDINGS_ROUTER_ENABLED=false` (RMW-03). |
| gateway.stella-ops.local | gateway | /gateway | D | Developer + Test Automation (Wave D) | Defer root compatibility path until downstream service migration waves are complete. | Route type revert + `GATEWAY_ROUTER_ENABLED=false` (RMW-03). |
| integrations.stella-ops.local | integrations-web | /api/v1/integrations, /integrations | A | Developer + Test Automation (Wave A) | Migrate API prefix first, then root compatibility path. | Route type revert + `INTEGRATIONS_ROUTER_ENABLED=false` (RMW-03). |
| issuerdirectory.stella-ops.local | issuer-directory | /issuerdirectory | B | Developer + Test Automation (Wave B) | Migrate route in trust-plane wave with issuer/auth verification checks. | Route type revert + `ISSUERDIRECTORY_ROUTER_ENABLED=false` (RMW-03). |
| notifier.stella-ops.local | notifier-web | /api/v1/notifier, /notifier | D | Developer + Test Automation (Wave D) | Migrate API prefix first, then root compatibility path. | Route type revert + `NOTIFIER_ROUTER_ENABLED=false` (RMW-03). |
| notify.stella-ops.local | notify-web | /api/v1/notify, /notify | D | Developer + Test Automation (Wave D) | Migrate API prefix first, then root compatibility path. | Route type revert + `NOTIFY_ROUTER_ENABLED=false` (RMW-03). |
| opsmemory.stella-ops.local | opsmemory-web | /api/v1/opsmemory, /opsmemory | A | Developer + Test Automation (Wave A) | Migrate API prefix first, then root compatibility path. | Route type revert + `OPSMEMORY_ROUTER_ENABLED=false` (RMW-03). |
| orchestrator.stella-ops.local | orchestrator | /api/approvals, /api/orchestrator, /api/release-orchestrator, /api/releases, /api/v1/orchestrator, /api/v1/release-orchestrator, /api/v1/workflows, /orchestrator, /v1/runs | C | Developer + Test Automation (Wave C) | Migrate all API/v1 and v1 routes first; keep root compatibility path until control-plane acceptance. | Route type revert + `ORCHESTRATOR_ROUTER_ENABLED=false` (RMW-03). |
| packsregistry.stella-ops.local | packsregistry-web | /packsregistry | A | Developer + Test Automation (Wave A) | Add API-form endpoint mapping if required, then migrate root compatibility route. | Route type revert + `PACKSREGISTRY_ROUTER_ENABLED=false` (RMW-03). |
| platform.stella-ops.local | platform | /api, /api/admin, /api/analytics, /api/v1/authority/quotas, /api/v1/gateway/rate-limits, /api/v1/platform, /envsettings.json, /platform | C | Developer + Test Automation (Wave C) | Migrate API prefixes to Microservice; keep `/platform` and `/envsettings.json` reverse proxy for static/bootstrap behavior. | Route type revert + `PLATFORM_ROUTER_ENABLED=false` (RMW-03). |
| policy-engine.stella-ops.local | policy-engine | /api/risk, /api/risk-budget, /api/v1/determinization, /policyEngine | C | Developer + Test Automation (Wave C) | Migrate API prefixes first; keep root compatibility path until control-plane verification completes. | Route type revert + `POLICY_ENGINE_ROUTER_ENABLED=false` (RMW-03). |
| policy-gateway.stella-ops.local | policy | /api/cvss, /api/exceptions, /api/gate, /api/policy, /api/v1/governance, /api/v1/policy, /policy, /policyGateway | C | Developer + Test Automation (Wave C) | Migrate API prefixes first; keep `/policy` and `/policyGateway` compatibility paths until final cutover. | Route type revert + `POLICY_GATEWAY_ROUTER_ENABLED=false` (RMW-03). |
| reachgraph.stella-ops.local | reachgraph-web | /api/v1/reachability, /reachgraph | D | Developer + Test Automation (Wave D) | Migrate API prefix first, then root compatibility path. | Route type revert + `REACHGRAPH_ROUTER_ENABLED=false` (RMW-03). |
| registry-token.stella-ops.local | registry-token | /registryTokenservice | A | Developer + Test Automation (Wave A) | Migrate compatibility route with token flow validation in Wave A. | Route type revert + `REGISTRY_TOKEN_ROUTER_ENABLED=false` (RMW-03). |
| replay.stella-ops.local | replay-web | /replay | A | Developer + Test Automation (Wave A) | Migrate compatibility route in Wave A; add API-form alias if needed. | Route type revert + `REPLAY_ROUTER_ENABLED=false` (RMW-03). |
| riskengine.stella-ops.local | riskengine-web | /riskengine | C | Developer + Test Automation (Wave C) | Migrate compatibility route in control-plane wave; add API alias if required. | Route type revert + `RISKENGINE_ROUTER_ENABLED=false` (RMW-03). |
| sbomservice.stella-ops.local | sbomservice | /api/change-traces, /api/compare, /api/sbomservice, /api/v1/lineage, /api/v1/sbom, /api/v1/sources, /sbomservice | D | Developer + Test Automation (Wave D) | Migrate API prefixes first; keep root compatibility path until graph/feed wave acceptance. | Route type revert + `SBOMSERVICE_ROUTER_ENABLED=false` (RMW-03). |
| scanner.stella-ops.local | scanner-web | /api/fix-verification, /api/v1/scanner, /api/v1/secrets, /api/v1/triage, /api/v1/vulnerabilities, /api/v1/watchlist, /scanner | D | Developer + Test Automation (Wave D) | Migrate API prefixes first; keep root compatibility path until scanner behavioral checks pass. | Route type revert + `SCANNER_ROUTER_ENABLED=false` (RMW-03). |
| scheduler.stella-ops.local | scheduler-web | /api/scheduler, /scheduler | C | Developer + Test Automation (Wave C) | Migrate API prefix first, then root compatibility path. | Route type revert + `SCHEDULER_ROUTER_ENABLED=false` (RMW-03). |
| signals.stella-ops.local | signals | /api/v1/signals, /signals | D | Developer + Test Automation (Wave D) | Migrate API prefix first, then root compatibility path. | Route type revert + `SIGNALS_ROUTER_ENABLED=false` (RMW-03). |
| signer.stella-ops.local | signer | /signer | B | Developer + Test Automation (Wave B) | Migrate compatibility route in trust/evidence wave with signing validation. | Route type revert + `SIGNER_ROUTER_ENABLED=false` (RMW-03). |
| smremote.stella-ops.local | smremote | /smremote | A | Developer + Test Automation (Wave A) | Migrate compatibility route in Wave A; add API alias if required. | Route type revert + `SMREMOTE_ROUTER_ENABLED=false` (RMW-03). |
| symbols.stella-ops.local | symbols | /symbols | A | Developer + Test Automation (Wave A) | Migrate compatibility route in Wave A; add API alias if required. | Route type revert + `SYMBOLS_ROUTER_ENABLED=false` (RMW-03). |
| taskrunner.stella-ops.local | taskrunner-web | /taskrunner | C | Developer + Test Automation (Wave C) | Migrate compatibility route in control-plane wave; add API alias if required. | Route type revert + `TASKRUNNER_ROUTER_ENABLED=false` (RMW-03). |
| timelineindexer.stella-ops.local | timeline-indexer-web | /timelineindexer | PILOT | Developer (pilot accepted) | Timeline API is already microservice (`/api/v1/timeline`); keep root compatibility route reverse proxy until later cleanup. | Route type revert + `TIMELINE_ROUTER_ENABLED=false` (already supported). |
| unknowns.stella-ops.local | unknowns-web | /unknowns | A | Developer + Test Automation (Wave A) | Migrate compatibility route in Wave A; add API alias if required. | Route type revert + `UNKNOWNS_ROUTER_ENABLED=false` (RMW-03). |
| vexhub.stella-ops.local | vexhub-web | /api/v1/vex, /api/vex, /vexhub | D | Developer + Test Automation (Wave D) | Migrate API prefixes first, then root compatibility path. | Route type revert + `VEXHUB_ROUTER_ENABLED=false` (RMW-03). |
| vexlens.stella-ops.local | vexlens-web | /api/v1/vexlens, /vexlens | D | Developer + Test Automation (Wave D) | Migrate API prefix first, then root compatibility path. | Route type revert + `VEXLENS_ROUTER_ENABLED=false` (RMW-03). |
| vulnexplorer.stella-ops.local | api | /api/vuln-explorer, /vulnexplorer | D | Developer + Test Automation (Wave D) | Migrate API prefix first; keep root compatibility path until vuln explorer routing is validated. | Route type revert + `VULNEXPLORER_ROUTER_ENABLED=false` (RMW-03). |
## Wave Acceptance Mapping
| Wave | Acceptance Owner |
| --- | --- |
| A | Developer + Test Automation (Wave A) |
| B | Developer + Test Automation (Wave B) |
| C | Developer + Test Automation (Wave C) |
| D | Developer + Test Automation (Wave D) |
| PILOT | Developer (pilot accepted) |
## Notes
- This matrix is the authoritative inventory artifact for sprint task `RMW-01`.
- Route-level rollback is always available by reverting route `Type` back to `ReverseProxy` in `devops/compose/router-gateway-local.json`.
- Service env rollback keys are currently planning placeholders and are standardized in `RMW-03`.

View File

@@ -59,9 +59,16 @@ src/TimelineIndexer/StellaOps.TimelineIndexer/
## 4) REST API
```
GET /timeline?subject={id}&from={date}&to={date} → { events[] }
GET /timeline/{eventId} → { event }
GET /timeline/stats?subject={id} → { statistics }
GET /timeline?eventType=&source=&correlationId=&traceId=&severity=&since=&after=&limit=
GET /timeline/{eventId}
GET /timeline/{eventId}/evidence
POST /timeline/events
# Gateway microservice aliases
GET /api/v1/timeline
GET /api/v1/timeline/{eventId}
GET /api/v1/timeline/{eventId}/evidence
POST /api/v1/timeline/events
GET /healthz | /readyz | /metrics
```
@@ -72,3 +79,5 @@ GET /healthz | /readyz | /metrics
* Signals: `../signals/architecture.md`
* Scanner: `../scanner/architecture.md`

View File

@@ -29,9 +29,16 @@ Events are stored append-only with tenant-specific partitions. Producers include
## 2. APIs
- `GET /api/v1/timeline/events` paginated event stream with filters (tenant, category, time window, correlation IDs).
- `GET /api/v1/timeline/events/{id}` fetch single event payload.
- `GET /api/v1/timeline/export` NDJSON export for offline review.
- Native endpoints:
- `GET /timeline` - query timeline entries with filter parameters.
- `GET /timeline/{eventId}` - fetch a single timeline entry.
- `GET /timeline/{eventId}/evidence` - fetch evidence linked to a timeline entry.
- `POST /timeline/events` - ingestion ack endpoint.
- Router/Gateway aliases for microservice transport routing:
- `GET /api/v1/timeline`
- `GET /api/v1/timeline/{eventId}`
- `GET /api/v1/timeline/{eventId}/evidence`
- `POST /api/v1/timeline/events`
API headers: `X-Stella-Tenant`, optional `X-Stella-TraceId`, and `If-None-Match` for cache revalidation.
@@ -73,3 +80,4 @@ Privacy/PII: producers must redact PII before emission; once emitted, redactions
- `docs/modules/zastava/architecture.md`
- `docs/modules/export-center/architecture.md`
- `src/TimelineIndexer/StellaOps.TimelineIndexer`

View File

@@ -103,15 +103,38 @@ Infrastructure components (PostgreSQL, Valkey, RustFS) are pinned in the release
-f devops/compose/docker-compose.prod.yaml \
up -d
```
4. Tail logs for critical services (`docker compose logs -f authority concelier`).
5. Update monitoring dashboards/alerts to confirm normal operation.
4. Run migration status and verification checks:
```bash
stella system migrations-status --module all
stella system migrations-verify --module all
stella system migrations-run --module all --category release --dry-run
```
5. Execute release migrations when approved:
```bash
stella system migrations-run --module all --category release --force
stella system migrations-status --module all
```
6. Tail logs for critical services (`docker compose logs -f authority concelier`).
7. Update monitoring dashboards/alerts to confirm normal operation.
Migration notes:
- Compose PostgreSQL init scripts in `devops/compose/postgres-init` are first-initialization only.
- CLI module coverage is currently limited; consult `docs/db/MIGRATION_INVENTORY.md` before production upgrades.
- Consolidation target policy and cutover waves are documented in `docs/db/MIGRATION_CONSOLIDATION_PLAN.md`.
- For upgradeable on-prem installations, the `stella system migrations-*` sequence is the required release migration gate.
- UI-driven migration execution must call Platform admin APIs (`/api/v1/admin/migrations/*`) and not connect directly from browser to PostgreSQL.
### Rollback (Compose)
1. Check out the previous release tag (e.g. `git checkout 2025.09.1`).
2. Re-run `docker compose pull` and `docker compose up -d` with that profile. Docker will restore the prior digests.
3. If reverting to a known-good snapshot is required, restore volume backups (see `docs/modules/authority/operations/backup-restore.md` and associated service guides).
4. Log the rollback in the operations journal.
2. Capture migration state before rollback:
```bash
stella system migrations-status --module all
```
3. Re-run `docker compose pull` and `docker compose up -d` with that profile. Docker will restore the prior digests.
4. If schema incompatibility is detected, restore known-good database snapshots before service restart (see `docs/modules/authority/operations/backup-restore.md` and associated service guides).
5. Re-check migration status and log rollback actions in the operations journal.
---

View File

@@ -122,26 +122,37 @@ kubectl get pods -n stellaops-green -w
### Run Migrations
```bash
# Step 16: Apply Category A migrations (startup)
# Step 16: Check current migration state (CLI-registered modules)
stella system migrations-status --module all
# Step 17: Verify checksums before execution
stella system migrations-verify --module all
# Step 18: Preview release migrations (manual gate)
stella system migrations-run \
--category A \
--namespace stellaops-green
--module all \
--category release \
--dry-run
# Step 17: Verify migration success
stella system migrations-status --namespace stellaops-green
# All migrations should show "Applied"
# Step 18: Apply Category B migrations if needed (manual)
# Review migration list first
stella system migrations-pending --category B
# Apply after review
# Step 19: Execute approved release migrations
stella system migrations-run \
--category B \
--namespace stellaops-green \
--confirm
--module all \
--category release \
--force
# Step 20: Verify migration success
stella system migrations-status --module all
```
Migration notes:
- Category names are `startup`, `release`, `seed`, and `data`.
- Compose and service startup paths may apply additional migrations outside CLI coverage.
- Use `docs/db/MIGRATION_INVENTORY.md` for current module-by-module runner coverage before production upgrade.
- Canonical consolidation policy and wave plan are in `docs/db/MIGRATION_CONSOLIDATION_PLAN.md`.
- For upgradeable on-prem environments, treat this CLI sequence as the required release migration gate.
- UI-triggered migration operations must execute through Platform admin APIs (`/api/v1/admin/migrations/*`) with `platform.setup.admin` (no browser-direct PostgreSQL access).
### Evidence Migration (If Required)
```bash

View File

@@ -10,6 +10,7 @@ using StellaOps.AdvisoryAI.DependencyInjection;
using StellaOps.AdvisoryAI.Explanation;
using StellaOps.AdvisoryAI.Guardrails;
using StellaOps.AdvisoryAI.Inference;
using StellaOps.AdvisoryAI.KnowledgeSearch;
using StellaOps.AdvisoryAI.Metrics;
using StellaOps.AdvisoryAI.Outputs;
using StellaOps.AdvisoryAI.PolicyStudio;
@@ -96,6 +97,7 @@ public static class ServiceCollectionExtensions
services.AddSbomContext();
services.AddAdvisoryPipeline();
services.AddAdvisoryPipelineInfrastructure();
services.AddAdvisoryKnowledgeSearch(configuration);
services.AddOptions<AdvisoryGuardrailOptions>()
.Configure<IOptions<AdvisoryAiServiceOptions>, IHostEnvironment>((options, aiOptions, environment) =>

View File

@@ -377,9 +377,10 @@ public static class EvidencePackEndpoints
private static string GetUserId(HttpContext context)
{
if (context.Request.Headers.TryGetValue("X-StellaOps-User", out var user))
if (context.Request.Headers.TryGetValue("X-StellaOps-Actor", out var actor)
&& !string.IsNullOrWhiteSpace(actor.ToString()))
{
return user.ToString();
return actor.ToString();
}
return context.User?.FindFirst("sub")?.Value ?? "anonymous";

View File

@@ -0,0 +1,381 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using StellaOps.AdvisoryAI.KnowledgeSearch;
namespace StellaOps.AdvisoryAI.WebService.Endpoints;
public static class KnowledgeSearchEndpoints
{
private static readonly HashSet<string> AllowedKinds = new(StringComparer.OrdinalIgnoreCase)
{
"docs",
"api",
"doctor"
};
public static RouteGroupBuilder MapKnowledgeSearchEndpoints(this IEndpointRouteBuilder builder)
{
var group = builder.MapGroup("/v1/advisory-ai")
.WithTags("Advisory AI - Knowledge Search");
group.MapPost("/search", SearchAsync)
.WithName("AdvisoryAiKnowledgeSearch")
.WithSummary("Searches AdvisoryAI deterministic knowledge index (docs/api/doctor).")
.Produces<AdvisoryKnowledgeSearchResponse>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status400BadRequest)
.Produces(StatusCodes.Status403Forbidden);
group.MapPost("/index/rebuild", RebuildIndexAsync)
.WithName("AdvisoryAiKnowledgeIndexRebuild")
.WithSummary("Rebuilds AdvisoryAI knowledge search index from deterministic local sources.")
.Produces<AdvisoryKnowledgeRebuildResponse>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status403Forbidden);
return group;
}
private static async Task<IResult> SearchAsync(
HttpContext httpContext,
AdvisoryKnowledgeSearchRequest request,
IKnowledgeSearchService searchService,
CancellationToken cancellationToken)
{
if (!EnsureSearchAuthorized(httpContext))
{
return Results.StatusCode(StatusCodes.Status403Forbidden);
}
if (request is null || string.IsNullOrWhiteSpace(request.Q))
{
return Results.BadRequest(new { error = "q is required." });
}
if (request.Q.Length > 4096)
{
return Results.BadRequest(new { error = "q must be 4096 characters or fewer." });
}
var normalizedFilter = NormalizeFilter(request.Filters);
var domainRequest = new KnowledgeSearchRequest(
request.Q.Trim(),
request.K,
normalizedFilter,
request.IncludeDebug);
var response = await searchService.SearchAsync(domainRequest, cancellationToken).ConfigureAwait(false);
return Results.Ok(MapResponse(response));
}
private static async Task<IResult> RebuildIndexAsync(
HttpContext httpContext,
IKnowledgeIndexer indexer,
CancellationToken cancellationToken)
{
if (!EnsureIndexAdminAuthorized(httpContext))
{
return Results.StatusCode(StatusCodes.Status403Forbidden);
}
var summary = await indexer.RebuildAsync(cancellationToken).ConfigureAwait(false);
return Results.Ok(new AdvisoryKnowledgeRebuildResponse
{
DocumentCount = summary.DocumentCount,
ChunkCount = summary.ChunkCount,
ApiSpecCount = summary.ApiSpecCount,
ApiOperationCount = summary.ApiOperationCount,
DoctorProjectionCount = summary.DoctorProjectionCount,
DurationMs = summary.DurationMs
});
}
private static KnowledgeSearchFilter? NormalizeFilter(AdvisoryKnowledgeSearchFilter? filter)
{
if (filter is null)
{
return null;
}
var normalizedKinds = filter.Type is { Count: > 0 }
? filter.Type
.Where(static value => !string.IsNullOrWhiteSpace(value))
.Select(static value => value.Trim().ToLowerInvariant())
.Where(value => AllowedKinds.Contains(value))
.Distinct(StringComparer.Ordinal)
.OrderBy(static value => value, StringComparer.Ordinal)
.ToArray()
: null;
var normalizedTags = filter.Tags is { Count: > 0 }
? filter.Tags
.Where(static value => !string.IsNullOrWhiteSpace(value))
.Select(static value => value.Trim())
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(static value => value, StringComparer.OrdinalIgnoreCase)
.ToArray()
: null;
return new KnowledgeSearchFilter
{
Type = normalizedKinds,
Product = NormalizeOptional(filter.Product),
Version = NormalizeOptional(filter.Version),
Service = NormalizeOptional(filter.Service),
Tags = normalizedTags
};
}
private static string? NormalizeOptional(string? value)
{
return string.IsNullOrWhiteSpace(value) ? null : value.Trim();
}
private static AdvisoryKnowledgeSearchResponse MapResponse(KnowledgeSearchResponse response)
{
var results = response.Results
.Select(MapResult)
.ToArray();
return new AdvisoryKnowledgeSearchResponse
{
Query = response.Query,
TopK = response.TopK,
Results = results,
Diagnostics = new AdvisoryKnowledgeSearchDiagnostics
{
FtsMatches = response.Diagnostics.FtsMatches,
VectorMatches = response.Diagnostics.VectorMatches,
DurationMs = response.Diagnostics.DurationMs,
UsedVector = response.Diagnostics.UsedVector,
Mode = response.Diagnostics.Mode
}
};
}
private static AdvisoryKnowledgeSearchResult MapResult(KnowledgeSearchResult result)
{
var action = new AdvisoryKnowledgeOpenAction
{
Kind = result.Open.Kind switch
{
KnowledgeOpenActionType.Api => "api",
KnowledgeOpenActionType.Doctor => "doctor",
_ => "docs"
},
Docs = result.Open.Docs is null
? null
: new AdvisoryKnowledgeOpenDocAction
{
Path = result.Open.Docs.Path,
Anchor = result.Open.Docs.Anchor,
SpanStart = result.Open.Docs.SpanStart,
SpanEnd = result.Open.Docs.SpanEnd
},
Api = result.Open.Api is null
? null
: new AdvisoryKnowledgeOpenApiAction
{
Service = result.Open.Api.Service,
Method = result.Open.Api.Method,
Path = result.Open.Api.Path,
OperationId = result.Open.Api.OperationId
},
Doctor = result.Open.Doctor is null
? null
: new AdvisoryKnowledgeOpenDoctorAction
{
CheckCode = result.Open.Doctor.CheckCode,
Severity = result.Open.Doctor.Severity,
CanRun = result.Open.Doctor.CanRun,
RunCommand = result.Open.Doctor.RunCommand
}
};
return new AdvisoryKnowledgeSearchResult
{
Type = result.Type,
Title = result.Title,
Snippet = result.Snippet,
Score = result.Score,
Open = action,
Debug = result.Debug is null
? null
: new Dictionary<string, string>(result.Debug, StringComparer.Ordinal)
};
}
private static bool EnsureSearchAuthorized(HttpContext context)
{
return HasAnyScope(
context,
"advisory:run",
"advisory:search",
"advisory:read");
}
private static bool EnsureIndexAdminAuthorized(HttpContext context)
{
return HasAnyScope(
context,
"advisory:run",
"advisory:admin",
"advisory:index:write");
}
private static bool HasAnyScope(HttpContext context, params string[] expectedScopes)
{
var scopes = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
AddScopeTokens(scopes, context.Request.Headers["X-StellaOps-Scopes"]);
AddScopeTokens(scopes, context.Request.Headers["X-Stella-Scopes"]);
foreach (var expectedScope in expectedScopes)
{
if (scopes.Contains(expectedScope))
{
return true;
}
}
return false;
}
private static void AddScopeTokens(HashSet<string> scopes, IEnumerable<string> values)
{
foreach (var value in values)
{
if (string.IsNullOrWhiteSpace(value))
{
continue;
}
foreach (var token in value.Split(
[' ', ','],
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
{
scopes.Add(token);
}
}
}
}
public sealed record AdvisoryKnowledgeSearchRequest
{
public string Q { get; init; } = string.Empty;
public int? K { get; init; }
public AdvisoryKnowledgeSearchFilter? Filters { get; init; }
public bool IncludeDebug { get; init; }
}
public sealed record AdvisoryKnowledgeSearchFilter
{
public IReadOnlyList<string>? Type { get; init; }
public string? Product { get; init; }
public string? Version { get; init; }
public string? Service { get; init; }
public IReadOnlyList<string>? Tags { get; init; }
}
public sealed record AdvisoryKnowledgeSearchResponse
{
public string Query { get; init; } = string.Empty;
public int TopK { get; init; }
public IReadOnlyList<AdvisoryKnowledgeSearchResult> Results { get; init; } = [];
public AdvisoryKnowledgeSearchDiagnostics Diagnostics { get; init; } = new();
}
public sealed record AdvisoryKnowledgeSearchResult
{
public string Type { get; init; } = "docs";
public string Title { get; init; } = string.Empty;
public string Snippet { get; init; } = string.Empty;
public double Score { get; init; }
public AdvisoryKnowledgeOpenAction Open { get; init; } = new();
public IReadOnlyDictionary<string, string>? Debug { get; init; }
}
public sealed record AdvisoryKnowledgeOpenAction
{
public string Kind { get; init; } = "docs";
public AdvisoryKnowledgeOpenDocAction? Docs { get; init; }
public AdvisoryKnowledgeOpenApiAction? Api { get; init; }
public AdvisoryKnowledgeOpenDoctorAction? Doctor { get; init; }
}
public sealed record AdvisoryKnowledgeOpenDocAction
{
public string Path { get; init; } = string.Empty;
public string Anchor { get; init; } = "overview";
public int SpanStart { get; init; }
public int SpanEnd { get; init; }
}
public sealed record AdvisoryKnowledgeOpenApiAction
{
public string Service { get; init; } = string.Empty;
public string Method { get; init; } = "GET";
public string Path { get; init; } = "/";
public string OperationId { get; init; } = string.Empty;
}
public sealed record AdvisoryKnowledgeOpenDoctorAction
{
public string CheckCode { get; init; } = string.Empty;
public string Severity { get; init; } = "warn";
public bool CanRun { get; init; } = true;
public string RunCommand { get; init; } = string.Empty;
}
public sealed record AdvisoryKnowledgeSearchDiagnostics
{
public int FtsMatches { get; init; }
public int VectorMatches { get; init; }
public long DurationMs { get; init; }
public bool UsedVector { get; init; }
public string Mode { get; init; } = "fts-only";
}
public sealed record AdvisoryKnowledgeRebuildResponse
{
public int DocumentCount { get; init; }
public int ChunkCount { get; init; }
public int ApiSpecCount { get; init; }
public int ApiOperationCount { get; init; }
public int DoctorProjectionCount { get; init; }
public long DurationMs { get; init; }
}

View File

@@ -0,0 +1,449 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using StellaOps.AdvisoryAI.Inference.LlmProviders;
using StellaOps.AdvisoryAI.Plugin.Unified;
using StellaOps.Plugin.Abstractions.Capabilities;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json.Serialization;
using PluginLlmCompletionRequest = StellaOps.Plugin.Abstractions.Capabilities.LlmCompletionRequest;
using PluginLlmCompletionResult = StellaOps.Plugin.Abstractions.Capabilities.LlmCompletionResult;
namespace StellaOps.AdvisoryAI.WebService.Endpoints;
/// <summary>
/// Unified LLM adapter exposure endpoints.
/// Provides provider discovery and an OpenAI-compatible completion surface.
/// </summary>
public static class LlmAdapterEndpoints
{
/// <summary>
/// Maps unified LLM adapter endpoints.
/// </summary>
/// <param name="builder">Endpoint route builder.</param>
/// <returns>Route group builder.</returns>
public static RouteGroupBuilder MapLlmAdapterEndpoints(this IEndpointRouteBuilder builder)
{
var group = builder.MapGroup("/v1/advisory-ai/adapters")
.WithTags("Advisory AI - LLM Adapters");
group.MapGet("/llm/providers", ListProvidersAsync)
.WithName("ListLlmProviders")
.WithSummary("Lists LLM providers exposed via the unified adapter layer.")
.Produces<IReadOnlyList<LlmProviderExposureResponse>>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status403Forbidden);
group.MapPost("/llm/{providerId}/chat/completions", CompleteWithProviderAsync)
.WithName("LlmProviderChatCompletions")
.WithSummary("OpenAI-compatible chat completion for a specific unified provider.")
.Produces<OpenAiChatCompletionResponse>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status400BadRequest)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status503ServiceUnavailable);
group.MapPost("/openai/v1/chat/completions", CompleteOpenAiCompatAsync)
.WithName("OpenAiAdapterChatCompletions")
.WithSummary("OpenAI-compatible chat completion alias backed by providerId=openai.")
.Produces<OpenAiChatCompletionResponse>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status400BadRequest)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status503ServiceUnavailable);
return group;
}
private static IResult ListProvidersAsync(
HttpContext context,
LlmProviderCatalog catalog,
LlmPluginAdapterFactory adapterFactory,
IServiceProvider services)
{
if (!EnsureAdapterReadAuthorized(context))
{
return Results.StatusCode(StatusCodes.Status403Forbidden);
}
var availableProviders = catalog.GetAvailablePlugins(services)
.Select(plugin => plugin.ProviderId)
.ToHashSet(StringComparer.OrdinalIgnoreCase);
var providers = new List<LlmProviderExposureResponse>();
foreach (var plugin in catalog.GetPlugins().OrderBy(p => p.ProviderId, StringComparer.Ordinal))
{
var config = catalog.GetConfiguration(plugin.ProviderId);
var configured = config is not null;
var validation = configured
? plugin.ValidateConfiguration(config!)
: LlmProviderConfigValidation.Failed("Provider configuration file was not found.");
var exposed = false;
var exposureErrors = new List<string>();
try
{
exposed = adapterFactory.GetCapability(plugin.ProviderId) is not null;
}
catch (Exception ex)
{
exposureErrors.Add(ex.Message);
}
var completionPath = string.Equals(plugin.ProviderId, "openai", StringComparison.OrdinalIgnoreCase)
? "/v1/advisory-ai/adapters/openai/v1/chat/completions"
: $"/v1/advisory-ai/adapters/llm/{plugin.ProviderId}/chat/completions";
var errors = validation.Errors.ToList();
errors.AddRange(exposureErrors);
providers.Add(new LlmProviderExposureResponse
{
ProviderId = plugin.ProviderId,
DisplayName = plugin.DisplayName,
Description = plugin.Description,
Configured = configured,
Valid = configured && validation.IsValid,
Available = availableProviders.Contains(plugin.ProviderId),
Exposed = exposed,
CompletionPath = completionPath,
Warnings = validation.Warnings,
Errors = errors
});
}
return Results.Ok(providers);
}
private static Task<IResult> CompleteOpenAiCompatAsync(
HttpContext context,
[FromBody] OpenAiChatCompletionRequest request,
LlmPluginAdapterFactory adapterFactory,
TimeProvider timeProvider,
CancellationToken cancellationToken)
{
return CompleteWithProviderAsync(
context,
"openai",
request,
adapterFactory,
timeProvider,
cancellationToken);
}
private static async Task<IResult> CompleteWithProviderAsync(
HttpContext context,
string providerId,
[FromBody] OpenAiChatCompletionRequest request,
LlmPluginAdapterFactory adapterFactory,
TimeProvider timeProvider,
CancellationToken cancellationToken)
{
if (!EnsureAdapterInvokeAuthorized(context, providerId))
{
return Results.StatusCode(StatusCodes.Status403Forbidden);
}
if (request.Messages.Count == 0)
{
return Results.BadRequest(new { error = "messages must contain at least one item." });
}
if (request.Stream)
{
return Results.BadRequest(new { error = "stream=true is not supported by the adapter endpoint." });
}
if (!TryBuildPrompts(request.Messages, out var systemPrompt, out var userPrompt))
{
return Results.BadRequest(new { error = "messages must include at least one non-empty user or assistant content." });
}
var capability = adapterFactory.GetCapability(providerId);
if (capability is null)
{
return Results.NotFound(new { error = $"Provider '{providerId}' is not configured for adapter exposure." });
}
if (!await capability.IsAvailableAsync(cancellationToken).ConfigureAwait(false))
{
return Results.StatusCode(StatusCodes.Status503ServiceUnavailable);
}
var completionRequest = new PluginLlmCompletionRequest(
UserPrompt: userPrompt,
SystemPrompt: systemPrompt,
Model: request.Model,
Temperature: request.Temperature ?? 0,
MaxTokens: request.MaxTokens is > 0 ? request.MaxTokens.Value : 4096,
Seed: request.Seed,
StopSequences: request.Stop,
RequestId: string.IsNullOrWhiteSpace(request.RequestId) ? context.TraceIdentifier : request.RequestId);
var completion = await capability.CompleteAsync(completionRequest, cancellationToken).ConfigureAwait(false);
var response = new OpenAiChatCompletionResponse
{
Id = BuildCompletionId(completion),
Object = "chat.completion",
Created = timeProvider.GetUtcNow().ToUnixTimeSeconds(),
Model = completion.ModelId,
Choices =
[
new OpenAiChatCompletionChoice
{
Index = 0,
Message = new OpenAiChatMessage { Role = "assistant", Content = completion.Content },
FinishReason = string.IsNullOrWhiteSpace(completion.FinishReason) ? "stop" : completion.FinishReason
}
],
Usage = new OpenAiUsageInfo
{
PromptTokens = completion.InputTokens ?? 0,
CompletionTokens = completion.OutputTokens ?? 0,
TotalTokens = (completion.InputTokens ?? 0) + (completion.OutputTokens ?? 0)
}
};
return Results.Ok(response);
}
private static bool TryBuildPrompts(
IReadOnlyList<OpenAiChatMessageRequest> messages,
out string? systemPrompt,
out string userPrompt)
{
var systemLines = new List<string>();
var userLines = new List<string>();
foreach (var message in messages)
{
if (string.IsNullOrWhiteSpace(message.Content))
{
continue;
}
var role = message.Role?.Trim() ?? string.Empty;
var content = message.Content.Trim();
if (role.Equals("system", StringComparison.OrdinalIgnoreCase))
{
systemLines.Add(content);
continue;
}
if (role.Equals("user", StringComparison.OrdinalIgnoreCase))
{
userLines.Add(content);
continue;
}
if (string.IsNullOrWhiteSpace(role))
{
userLines.Add(content);
continue;
}
userLines.Add($"{role.ToLowerInvariant()}: {content}");
}
systemPrompt = systemLines.Count == 0
? null
: string.Join(Environment.NewLine, systemLines);
userPrompt = string.Join(Environment.NewLine, userLines);
return !string.IsNullOrWhiteSpace(userPrompt);
}
private static string BuildCompletionId(PluginLlmCompletionResult completion)
{
if (!string.IsNullOrWhiteSpace(completion.RequestId))
{
return completion.RequestId;
}
var input = $"{completion.ProviderId}|{completion.ModelId}|{completion.Content}";
var hash = SHA256.HashData(Encoding.UTF8.GetBytes(input));
var hex = Convert.ToHexString(hash).ToLowerInvariant();
return $"chatcmpl-{hex[..24]}";
}
private static bool EnsureAdapterReadAuthorized(HttpContext context)
{
return HasAnyScope(context, "advisory:run", "advisory:adapter:read", "advisory:openai:read");
}
private static bool EnsureAdapterInvokeAuthorized(HttpContext context, string providerId)
{
var providerScope = $"advisory:{providerId}:invoke";
return HasAnyScope(
context,
"advisory:run",
"advisory:adapter:invoke",
"advisory:openai:invoke",
providerScope);
}
private static bool HasAnyScope(HttpContext context, params string[] expectedScopes)
{
var scopes = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
// Only read from gateway-managed headers that are stripped and rewritten
// from validated JWT claims by IdentityHeaderPolicyMiddleware.
// Do NOT read from X-Scopes — it is not in the gateway's ReservedHeaders
// list and can be spoofed by external clients.
AddScopeTokens(scopes, context.Request.Headers["X-StellaOps-Scopes"]);
AddScopeTokens(scopes, context.Request.Headers["X-Stella-Scopes"]);
foreach (var expectedScope in expectedScopes)
{
if (scopes.Contains(expectedScope))
{
return true;
}
}
return false;
}
private static void AddScopeTokens(HashSet<string> scopes, IEnumerable<string> values)
{
foreach (var value in values)
{
if (string.IsNullOrWhiteSpace(value))
{
continue;
}
foreach (var token in value.Split(
[' ', ','],
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
{
scopes.Add(token);
}
}
}
}
public sealed record LlmProviderExposureResponse
{
[JsonPropertyName("provider_id")]
public required string ProviderId { get; init; }
[JsonPropertyName("display_name")]
public required string DisplayName { get; init; }
[JsonPropertyName("description")]
public required string Description { get; init; }
[JsonPropertyName("configured")]
public required bool Configured { get; init; }
[JsonPropertyName("valid")]
public required bool Valid { get; init; }
[JsonPropertyName("available")]
public required bool Available { get; init; }
[JsonPropertyName("exposed")]
public required bool Exposed { get; init; }
[JsonPropertyName("completion_path")]
public required string CompletionPath { get; init; }
[JsonPropertyName("warnings")]
public required IReadOnlyList<string> Warnings { get; init; }
[JsonPropertyName("errors")]
public required IReadOnlyList<string> Errors { get; init; }
}
public sealed record OpenAiChatCompletionRequest
{
[JsonPropertyName("model")]
public string? Model { get; init; }
[JsonPropertyName("messages")]
public IReadOnlyList<OpenAiChatMessageRequest> Messages { get; init; } = [];
[JsonPropertyName("temperature")]
public double? Temperature { get; init; }
[JsonPropertyName("max_tokens")]
public int? MaxTokens { get; init; }
[JsonPropertyName("stream")]
public bool Stream { get; init; }
[JsonPropertyName("seed")]
public int? Seed { get; init; }
[JsonPropertyName("stop")]
public IReadOnlyList<string>? Stop { get; init; }
[JsonPropertyName("request_id")]
public string? RequestId { get; init; }
}
public sealed record OpenAiChatMessageRequest
{
[JsonPropertyName("role")]
public string Role { get; init; } = string.Empty;
[JsonPropertyName("content")]
public string? Content { get; init; }
}
public sealed record OpenAiChatCompletionResponse
{
[JsonPropertyName("id")]
public required string Id { get; init; }
[JsonPropertyName("object")]
public required string Object { get; init; }
[JsonPropertyName("created")]
public required long Created { get; init; }
[JsonPropertyName("model")]
public required string Model { get; init; }
[JsonPropertyName("choices")]
public required IReadOnlyList<OpenAiChatCompletionChoice> Choices { get; init; }
[JsonPropertyName("usage")]
public required OpenAiUsageInfo Usage { get; init; }
}
public sealed record OpenAiChatCompletionChoice
{
[JsonPropertyName("index")]
public required int Index { get; init; }
[JsonPropertyName("message")]
public required OpenAiChatMessage Message { get; init; }
[JsonPropertyName("finish_reason")]
public required string FinishReason { get; init; }
}
public sealed record OpenAiChatMessage
{
[JsonPropertyName("role")]
public required string Role { get; init; }
[JsonPropertyName("content")]
public required string Content { get; init; }
}
public sealed record OpenAiUsageInfo
{
[JsonPropertyName("prompt_tokens")]
public required int PromptTokens { get; init; }
[JsonPropertyName("completion_tokens")]
public required int CompletionTokens { get; init; }
[JsonPropertyName("total_tokens")]
public required int TotalTokens { get; init; }
}

View File

@@ -14,9 +14,11 @@ using StellaOps.AdvisoryAI.Diagnostics;
using StellaOps.AdvisoryAI.Evidence;
using StellaOps.AdvisoryAI.Explanation;
using StellaOps.AdvisoryAI.Hosting;
using StellaOps.AdvisoryAI.Inference.LlmProviders;
using StellaOps.AdvisoryAI.Metrics;
using StellaOps.AdvisoryAI.Orchestration;
using StellaOps.AdvisoryAI.Outputs;
using StellaOps.AdvisoryAI.Plugin.Unified;
using StellaOps.AdvisoryAI.PolicyStudio;
using StellaOps.AdvisoryAI.Queue;
using StellaOps.AdvisoryAI.Remediation;
@@ -41,6 +43,20 @@ builder.Configuration
.AddEnvironmentVariables(prefix: "ADVISORYAI__");
builder.Services.AddAdvisoryAiCore(builder.Configuration);
var llmAdapterEnabled = builder.Configuration.GetValue<bool?>("AdvisoryAI:Adapters:Llm:Enabled") ?? false;
if (llmAdapterEnabled)
{
var llmProviderConfigDirectory = builder.Configuration["AdvisoryAI:LlmProviders:ConfigDirectory"];
if (string.IsNullOrWhiteSpace(llmProviderConfigDirectory))
{
llmProviderConfigDirectory = Path.Combine(builder.Environment.ContentRootPath, "etc", "llm-providers");
}
builder.Services.AddLlmProviderPlugins(llmProviderConfigDirectory);
builder.Services.AddUnifiedLlmPlugins();
}
builder.Services.AddAdvisoryChat(builder.Configuration);
builder.Services.TryAddSingleton<ICodexCompanionService, CodexZastavaCompanionService>();
@@ -73,11 +89,11 @@ builder.Services.AddOpenApi();
builder.Services.AddProblemDetails();
// Stella Router integration
var routerOptions = builder.Configuration.GetSection("AdvisoryAI:Router").Get<StellaRouterOptionsBase>();
builder.Services.TryAddStellaRouter(
var routerEnabled = builder.Services.AddRouterMicroservice(
builder.Configuration,
serviceName: "advisoryai",
version: typeof(Program).Assembly.GetName().Version?.ToString() ?? "1.0.0",
routerOptions: routerOptions);
version: System.Reflection.CustomAttributeExtensions.GetCustomAttribute<System.Reflection.AssemblyInformationalVersionAttribute>(System.Reflection.Assembly.GetExecutingAssembly())?.InformationalVersion ?? "1.0.0",
routerOptionsSection: "Router");
builder.Services.AddStellaOpsCors(builder.Environment, builder.Configuration);
@@ -121,7 +137,7 @@ if (app.Environment.IsDevelopment())
app.UseStellaOpsCors();
app.UseRateLimiter();
app.TryUseStellaRouter(routerOptions);
app.TryUseStellaRouter(routerEnabled);
app.MapGet("/health", () => Results.Ok(new { status = "ok" }));
@@ -214,8 +230,17 @@ app.MapAttestationEndpoints();
// Evidence Pack endpoints (Sprint: SPRINT_20260109_011_005 Task: EVPK-010)
app.MapEvidencePackEndpoints();
// AdvisoryAI Knowledge Search endpoints (Sprint: SPRINT_20260222_051)
app.MapKnowledgeSearchEndpoints();
if (llmAdapterEnabled)
{
// Unified LLM adapter exposure endpoints (RVM-08)
app.MapLlmAdapterEndpoints();
}
// Refresh Router endpoint cache
app.TryRefreshStellaRouterEndpoints(routerOptions);
app.TryRefreshStellaRouterEndpoints(routerEnabled);
app.Run();
@@ -812,7 +837,8 @@ static string GetTenantId(HttpContext context)
static string GetUserId(HttpContext context)
{
return context.Request.Headers.TryGetValue("X-StellaOps-User", out var value)
return context.Request.Headers.TryGetValue("X-StellaOps-Actor", out var value)
&& !string.IsNullOrWhiteSpace(value.ToString())
? value.ToString()
: "anonymous";
}
@@ -1031,10 +1057,8 @@ static async Task<IResult> HandleCreateConversation(
return Results.StatusCode(StatusCodes.Status403Forbidden);
}
// Get user ID from header
var userId = httpContext.Request.Headers.TryGetValue("X-StellaOps-User", out var userHeader)
? userHeader.ToString()
: "anonymous";
// Get user ID from gateway-protected Actor header
var userId = GetUserId(httpContext);
var conversationRequest = new ConversationRequest
{
@@ -1237,8 +1261,9 @@ static async Task<IResult> HandleListConversations(
? tenantHeader.ToString()
: "default");
// Get user from header for filtering
var userId = httpContext.Request.Headers.TryGetValue("X-StellaOps-User", out var userHeader)
// Get user from gateway-protected Actor header for filtering
var userId = httpContext.Request.Headers.TryGetValue("X-StellaOps-Actor", out var userHeader)
&& !string.IsNullOrWhiteSpace(userHeader.ToString())
? userHeader.ToString()
: null;
@@ -1265,40 +1290,19 @@ static async Task<IResult> HandleListConversations(
static bool EnsureChatAuthorized(HttpContext context)
{
var tokens = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
if (context.Request.Headers.TryGetValue("X-StellaOps-Scopes", out var scopes))
if (!context.Request.Headers.TryGetValue("X-StellaOps-Scopes", out var scopes))
{
AddHeaderTokens(tokens, scopes);
return false;
}
if (context.Request.Headers.TryGetValue("X-StellaOps-Roles", out var roles))
{
AddHeaderTokens(tokens, roles);
}
var allowed = scopes
.SelectMany(value => value?.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) ?? [])
.ToHashSet(StringComparer.OrdinalIgnoreCase);
return tokens.Contains("advisory:run")
|| tokens.Contains("advisory:chat")
|| tokens.Contains("chat:user")
|| tokens.Contains("chat:admin");
}
static void AddHeaderTokens(HashSet<string> target, IEnumerable<string> values)
{
foreach (var value in values)
{
if (string.IsNullOrWhiteSpace(value))
{
continue;
}
foreach (var token in value.Split(
new[] { ' ', ',' },
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
{
target.Add(token);
}
}
return allowed.Contains("advisory:run")
|| allowed.Contains("advisory:chat")
|| allowed.Contains("chat:user")
|| allowed.Contains("chat:admin");
}
static string GeneratePlaceholderResponse(string userMessage)
@@ -1361,3 +1365,6 @@ namespace StellaOps.AdvisoryAI.WebService
{
public partial class Program { }
}

View File

@@ -52,7 +52,7 @@ public sealed class HeaderBasedAuthorizationService : IAuthorizationService
{
private const string ScopesHeader = "X-StellaOps-Scopes";
private const string TenantHeader = "X-StellaOps-Tenant";
private const string UserHeader = "X-StellaOps-User";
private const string UserHeader = "X-StellaOps-Actor";
public bool IsAuthorized(HttpContext context, AdvisoryTaskType taskType)
{

View File

@@ -12,6 +12,7 @@
<ItemGroup>
<ProjectReference Include="..\StellaOps.AdvisoryAI\StellaOps.AdvisoryAI.csproj" />
<ProjectReference Include="..\StellaOps.AdvisoryAI.Hosting\StellaOps.AdvisoryAI.Hosting.csproj" />
<ProjectReference Include="..\StellaOps.AdvisoryAI.Plugin.Unified\StellaOps.AdvisoryAI.Plugin.Unified.csproj" />
<ProjectReference Include="..\..\Router/__Libraries/StellaOps.Router.AspNet\StellaOps.Router.AspNet.csproj" />
<!-- AI Attestations (Sprint: SPRINT_20260109_011_001) -->
<ProjectReference Include="..\..\__Libraries\StellaOps.AdvisoryAI.Attestation\StellaOps.AdvisoryAI.Attestation.csproj" />
@@ -21,4 +22,8 @@
<ProjectReference Include="..\..\__Libraries\StellaOps.Determinism.Abstractions\StellaOps.Determinism.Abstractions.csproj" />
<ProjectReference Include="..\..\Authority\StellaOps.Authority\StellaOps.Auth.ServerIntegration\StellaOps.Auth.ServerIntegration.csproj" />
</ItemGroup>
<PropertyGroup Label="StellaOpsReleaseVersion">
<Version>1.0.0-alpha1</Version>
<InformationalVersion>1.0.0-alpha1</InformationalVersion>
</PropertyGroup>
</Project>

View File

@@ -1,10 +1,18 @@
{
"AdvisoryAI": {
"SbomBaseAddress": "http://localhost:5210/",
"Queue": {
"DirectoryPath": "../var/advisory-ai-queue"
}
},
"AdvisoryAI": {
"SbomBaseAddress": "http://localhost:5210/",
"Queue": {
"DirectoryPath": "../var/advisory-ai-queue"
},
"Adapters": {
"Llm": {
"Enabled": false
}
},
"LlmProviders": {
"ConfigDirectory": "etc/llm-providers"
}
},
"Logging": {
"LogLevel": {
"Default": "Information",

View File

@@ -0,0 +1,35 @@
using System.Text.Json;
namespace StellaOps.AdvisoryAI.KnowledgeSearch;
internal sealed record DoctorSearchSeedEntry(
string CheckCode,
string Title,
string Severity,
string Description,
string Remediation,
string RunCommand,
IReadOnlyList<string> Symptoms,
IReadOnlyList<string> Tags,
IReadOnlyList<string> References);
internal static class DoctorSearchSeedLoader
{
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);
public static IReadOnlyList<DoctorSearchSeedEntry> Load(string absolutePath)
{
if (!File.Exists(absolutePath))
{
return [];
}
using var stream = File.OpenRead(absolutePath);
var entries = JsonSerializer.Deserialize<List<DoctorSearchSeedEntry>>(stream, JsonOptions) ?? [];
return entries
.Where(static entry => !string.IsNullOrWhiteSpace(entry.CheckCode))
.OrderBy(static entry => entry.CheckCode, StringComparer.Ordinal)
.ToList();
}
}

View File

@@ -0,0 +1,6 @@
namespace StellaOps.AdvisoryAI.KnowledgeSearch;
public interface IKnowledgeIndexer
{
Task<KnowledgeRebuildSummary> RebuildAsync(CancellationToken cancellationToken);
}

View File

@@ -0,0 +1,6 @@
namespace StellaOps.AdvisoryAI.KnowledgeSearch;
public interface IKnowledgeSearchBenchmarkDatasetGenerator
{
Task<KnowledgeBenchmarkDataset> GenerateAsync(KnowledgeBenchmarkDatasetOptions options, CancellationToken cancellationToken);
}

View File

@@ -0,0 +1,9 @@
namespace StellaOps.AdvisoryAI.KnowledgeSearch;
public interface IKnowledgeSearchBenchmarkRunner
{
Task<KnowledgeBenchmarkRunResult> RunAsync(
KnowledgeBenchmarkDataset dataset,
KnowledgeBenchmarkRunOptions options,
CancellationToken cancellationToken);
}

View File

@@ -0,0 +1,6 @@
namespace StellaOps.AdvisoryAI.KnowledgeSearch;
public interface IKnowledgeSearchService
{
Task<KnowledgeSearchResponse> SearchAsync(KnowledgeSearchRequest request, CancellationToken cancellationToken);
}

Some files were not shown because too many files have changed in this diff Show More