From fdf95e0f46a5c12e38590a9b57faecf646d16e2d Mon Sep 17 00:00:00 2001 From: master <> Date: Sun, 19 Apr 2026 14:45:09 +0300 Subject: [PATCH] docs: module dossier + install/quickstart sync for truthful cutover sprints - API_CLI_REFERENCE.md, INSTALL_GUIDE.md, quickstart.md, architecture/integrations.md, dev/DEV_ENVIRONMENT_SETUP.md, integrations/LOCAL_SERVICES.md: reflect real-service wiring. - docs/modules/**: module dossier updates across the modules touched by SPRINT_20260415_001..007 + SPRINT_20260416_003..017 + SPRINT_20260417_018..024 + SPRINT_20260418_025 + SPRINT_20260419_026. - docs/features/checked/web/**: update feature notes where UI changed. - docs/qa/feature-checks/runs/web/evidence-presentation-ux/: QA evidence artifacts. - docs/setup/**, docs/technical/**: align with setup wizard contracts. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/API_CLI_REFERENCE.md | 2 +- docs/INSTALL_GUIDE.md | 12 + docs/architecture/integrations.md | 4 +- docs/dev/DEV_ENVIRONMENT_SETUP.md | 21 +- .../checked/web/evidence-presentation-ux.md | 8 + .../web/explainer-timeline-ui-component.md | 1 - ...ndings-compare-baseline-availability-ui.md | 2 +- docs/features/checked/web/graph-export.md | 1 - .../web/graph-split-view-with-diff-engine.md | 1 - .../checked/web/lineage-compare-panel.md | 1 - .../checked/web/lineage-timeline-slider.md | 1 - ...eage-ui-api-wiring-with-angular-signals.md | 1 - .../checked/web/pinned-explanations-panel.md | 1 - .../web/release-investigation-routes.md | 2 +- .../web/web-gateway-graph-platform-client.md | 1 - docs/integrations/LOCAL_SERVICES.md | 6 +- .../advisory-ai/architecture-detail.md | 11 + docs/modules/advisory-ai/architecture.md | 9 +- docs/modules/airgap/architecture.md | 5 + docs/modules/attestor/README.md | 5 +- docs/modules/attestor/architecture.md | 73 ++--- docs/modules/attestor/implementation_plan.md | 3 + docs/modules/authority/AUTHORITY.md | 2 +- docs/modules/authority/README.md | 9 +- docs/modules/authority/architecture.md | 8 +- docs/modules/binary-index/architecture.md | 20 +- docs/modules/cli/guides/setup-guide.md | 1 + docs/modules/concelier/architecture.md | 67 +++-- docs/modules/doctor/architecture.md | 5 +- docs/modules/excititor/architecture.md | 7 +- docs/modules/excititor/graph-overlays.md | 10 +- docs/modules/export-center/README.md | 2 + docs/modules/export-center/api.md | 2 +- docs/modules/export-center/architecture.md | 25 +- .../export-center/provenance-and-signing.md | 3 + docs/modules/findings-ledger/README.md | 7 + .../findings-ledger/implementation_plan.md | 8 +- docs/modules/graph/README.md | 6 + docs/modules/graph/architecture.md | 20 +- docs/modules/graph/implementation_plan.md | 3 +- docs/modules/integrations/README.md | 2 +- docs/modules/integrations/architecture.md | 2 +- docs/modules/issuer-directory/README.md | 10 +- docs/modules/issuer-directory/architecture.md | 1 + docs/modules/jobengine/architecture.md | 10 +- docs/modules/notifier/README.md | 11 + docs/modules/notify/README.md | 6 + docs/modules/notify/architecture.md | 5 + .../notify/pack-approvals-integration.md | 8 + docs/modules/platform/platform-service.md | 31 ++- docs/modules/policy/architecture.md | 30 ++- .../contracts/reachability-input-contract.md | 2 + docs/modules/reach-graph/architecture.md | 10 +- docs/modules/registry/architecture.md | 11 +- docs/modules/replay/architecture.md | 2 +- docs/modules/router/architecture.md | 1 + docs/modules/sbom-service/architecture.md | 249 +++++++----------- docs/modules/scanner/architecture.md | 7 + docs/modules/signer/README.md | 5 +- docs/modules/timeline/README.md | 5 + docs/modules/timeline/architecture.md | 6 + .../evidence/playwright-ui-evidence.txt | 18 ++ .../run-004/tier2-ui-check.json | 32 +++ docs/quickstart.md | 25 +- docs/setup/setup-wizard-capabilities.md | 44 +++- docs/setup/setup-wizard-ux.md | 39 ++- docs/technical/cicd/dsse-build-flow.md | 2 +- 67 files changed, 590 insertions(+), 360 deletions(-) create mode 100644 docs/qa/feature-checks/runs/web/evidence-presentation-ux/run-004/evidence/playwright-ui-evidence.txt create mode 100644 docs/qa/feature-checks/runs/web/evidence-presentation-ux/run-004/tier2-ui-check.json diff --git a/docs/API_CLI_REFERENCE.md b/docs/API_CLI_REFERENCE.md index eda552b20..747ed523c 100755 --- a/docs/API_CLI_REFERENCE.md +++ b/docs/API_CLI_REFERENCE.md @@ -144,7 +144,7 @@ Release safety guard: ### Related Commands ```bash -# Seed demo datasets (seed migrations) +# Optional manual demo datasets (not part of default bootstrap) stella admin seed-demo --dry-run stella admin seed-demo --confirm ``` diff --git a/docs/INSTALL_GUIDE.md b/docs/INSTALL_GUIDE.md index afb4648cf..3fa6667f4 100755 --- a/docs/INSTALL_GUIDE.md +++ b/docs/INSTALL_GUIDE.md @@ -206,6 +206,18 @@ that apply advances the backend state to the cache step, and proves that a page reload resumes the same persisted session. Evidence is written to `src/Web/StellaOps.Web/output/playwright/live-setup-wizard-state-truth-check.json`. +For a fresh-volume first-run bootstrap proof that drives the full installation +wizard, creates the first administrator account, and then proves that the new +credentials can authenticate through the frontdoor: + +```powershell +npm --prefix src/Web/StellaOps.Web run test:e2e:platform:bootstrap:first-run:live +``` + +Evidence is written to +`src/Web/StellaOps.Web/output/playwright/live-setup-wizard-first-run-bootstrap.json` +plus the paired authentication report/state files in the same directory. + Verified current UI boundary on `2026-04-14`: - The browser flow can create the full 16-entry local integration catalog. - GitLab-class providers can now be created from the UI without a manual Vault diff --git a/docs/architecture/integrations.md b/docs/architecture/integrations.md index dcbb75a70..8e99ccfbc 100644 --- a/docs/architecture/integrations.md +++ b/docs/architecture/integrations.md @@ -260,8 +260,8 @@ The live Integration Catalog contract is served by the Integrations WebService a ### Provider Metadata - `GET /api/v1/integrations/providers` returns `ProviderInfo[]` with `name`, `type`, `provider`, `isTestOnly`, `supportsDiscovery`, and `supportedResourceTypes`. -- Test-only providers are hidden by default. `GET /api/v1/integrations/providers?includeTestOnly=true` exposes providers such as `InMemory` for explicit test/dev workflows. -- Built-in provider coverage now includes Harbor, Docker Registry, GitLab Container Registry, GitHub App, Gitea, GitLab Server, GitLab CI, Jenkins, Nexus, Vault, Consul, eBPF Agent, the `S3Compatible` object-storage provider, feed mirror providers (`StellaOpsMirror`, `NvdMirror`, `OsvMirror`), and the hidden test-only `InMemory` plugin. +- Test-only providers are hidden by default. `GET /api/v1/integrations/providers?includeTestOnly=true` only exposes them when the current runtime or test harness explicitly registers a test-only connector such as `InMemory`. +- Built-in provider coverage now includes Harbor, Docker Registry, GitLab Container Registry, GitHub App, Gitea, GitLab Server, GitLab CI, Jenkins, Nexus, Vault, Consul, eBPF Agent, the `S3Compatible` object-storage provider, and feed mirror providers (`StellaOpsMirror`, `NvdMirror`, `OsvMirror`). The `InMemory` connector remains test-only and is not part of the default host composition. ### Discovery diff --git a/docs/dev/DEV_ENVIRONMENT_SETUP.md b/docs/dev/DEV_ENVIRONMENT_SETUP.md index fb1dcfc53..bd3ca4543 100644 --- a/docs/dev/DEV_ENVIRONMENT_SETUP.md +++ b/docs/dev/DEV_ENVIRONMENT_SETUP.md @@ -37,7 +37,7 @@ The setup scripts do not start the optional real-provider compose lane in `devop On Windows and Linux, the backend image builder now publishes each selected .NET service locally and builds the hardened runtime image from a small temporary context. That avoids repeatedly streaming the whole monorepo into Docker during scratch setup. -### Quick validation + demo seed (first-run path) +### Quick validation + UI setup (first-run path) ```powershell # 1) Bring platform up quickly (reuse existing images) @@ -46,15 +46,8 @@ On Windows and Linux, the backend image builder now publishes each selected .NET # 2) Validate platform health docker compose -f devops/compose/docker-compose.stella-ops.yml ps -# 3) Preview seed work -dotnet run --project src/Cli/StellaOps.Cli/StellaOps.Cli.csproj -- ` - admin seed-demo --dry-run ` - --connection "Host=127.1.1.1;Port=5432;Database=stellaops_platform;Username=stellaops;Password=stellaops" - -# 4) Execute demo seeding -dotnet run --project src/Cli/StellaOps.Cli/StellaOps.Cli.csproj -- ` - admin seed-demo --confirm ` - --connection "Host=127.1.1.1;Port=5432;Database=stellaops_platform;Username=stellaops;Password=stellaops" +# 3) Open https://stella-ops.local and complete the setup wizard +# 4) Sign in with the administrator credentials you created ``` ### Known warnings vs blocking failures @@ -65,7 +58,7 @@ dotnet run --project src/Cli/StellaOps.Cli/StellaOps.Cli.csproj -- ` | `SM remote service probe failed (localhost:56080)` during `stella --verbose ...` or crypto diagnostics | Warning | Optional SM remote provider is unavailable | Ignore unless validating China SM remote crypto profile; ordinary CLI payload commands now suppress this startup noise | | `stellaops-dev-rekor restarting` without `--profile sigstore` | Warning | Optional Sigstore container from prior run | Ignore for default profile or remove stale container | | `policy ... scheduler_exceptions_tenant_isolation already exists` | Blocking | Outdated Scheduler migration idempotency | Update code and rerun seeding | -| `POST /api/v1/admin/seed-demo` returns 500 after patching source | Blocking | Running stale platform container image | Rebuild/restart platform image | +| Manual `POST /api/v1/admin/seed-demo` returns 500 after patching source | Blocking | Running stale platform container image | Rebuild/restart platform image | --- @@ -360,9 +353,11 @@ docker compose -f devops/compose/docker-compose.stella-ops.yml ps --- -## 8. Seed demo data and verify endpoint errors +## 8. Optional manual demo data seeding and endpoint verification -Use the CLI seeder for local bootstraps and demo datasets: +This lane is for demo purposes only. It is not part of the default local setup path. + +Use the CLI seeder only when you intentionally want demo/sample datasets: ```powershell # dry-run diff --git a/docs/features/checked/web/evidence-presentation-ux.md b/docs/features/checked/web/evidence-presentation-ux.md index 18dfeab6e..b33a7227d 100644 --- a/docs/features/checked/web/evidence-presentation-ux.md +++ b/docs/features/checked/web/evidence-presentation-ux.md @@ -40,3 +40,11 @@ Comprehensive evidence presentation: tabbed panels across triage/findings/SBOM/p - Status: VERIFIED (strict Tier 2 UI replay) - Tier 2 evidence: `docs/qa/feature-checks/runs/web/evidence-presentation-ux/run-003/tier2-ui-check.json` - Notes: Strict evidence-center drawer flow now verifies signed/verified presentation states and contents-section interaction from an end-user route. + +## Recheck (run-004) +- Date (UTC): 2026-04-15T17:03:18Z +- Status: VERIFIED (strict Tier 2 UI replay) +- Tier 2 evidence: `docs/qa/feature-checks/runs/web/evidence-presentation-ux/run-004/tier2-ui-check.json` +- Replay scope: + - Open `/evidence/exports?tab=profiles` and verify the mounted Export Center renders truthful copy instead of seeded profile actions. + - Open `/evidence/exports?tab=runs` and verify the runs view reports browser-started exports plus completed StellaBundle jobs without fake seeded rows. diff --git a/docs/features/checked/web/explainer-timeline-ui-component.md b/docs/features/checked/web/explainer-timeline-ui-component.md index 735bda8de..c3a876da5 100644 --- a/docs/features/checked/web/explainer-timeline-ui-component.md +++ b/docs/features/checked/web/explainer-timeline-ui-component.md @@ -38,7 +38,6 @@ Interactive step-by-step verdict explanation visualization with expand/collapse - `src/Web/StellaOps.Web/src/app/features/lineage/components/audit-pack-export/models/audit-pack.models.ts` - `src/Web/StellaOps.Web/src/app/features/lineage/components/diff-table/models/diff-table.models.ts` - `src/Web/StellaOps.Web/src/app/features/lineage/components/explainer-timeline/models/explainer.models.ts` - - `src/Web/StellaOps.Web/src/app/features/lineage/components/node-diff-table/models/diff-table.models.ts` - `src/Web/StellaOps.Web/src/app/features/lineage/components/pinned-explanation/models/pinned.models.ts` - **Source**: SPRINT_20251229_001_005_FE_explainer_timeline.md diff --git a/docs/features/checked/web/findings-compare-baseline-availability-ui.md b/docs/features/checked/web/findings-compare-baseline-availability-ui.md index 574a43eeb..61921a0a9 100644 --- a/docs/features/checked/web/findings-compare-baseline-availability-ui.md +++ b/docs/features/checked/web/findings-compare-baseline-availability-ui.md @@ -35,7 +35,7 @@ The embedded compare surface on `/security/findings` now treats `active-scan` as - Notes: - Focused unit coverage passed: `1/1` tests in `findings-list.audit-export.behavior.spec.ts`. - Deterministic Playwright passed: `2/2` scenarios for no-baseline diff behavior and detail-mode audit-export removal. - - Live authenticated replay on `https://stella-ops.local/security/findings?tenant=demo-prod®ions=us-east&environments=stage&timeWindow=7d` showed `Active scan`, rendered `No baseline recommendations available for this scan`, kept `Export` disabled, and issued no `/api/compare/delta` request without a selected baseline. + - Live authenticated replay on `https://stella-ops.local/security/findings?tenant=default®ions=us-east&environments=stage&timeWindow=7d` showed `Active scan`, rendered `No baseline recommendations available for this scan`, kept `Export` disabled, and issued no `/api/compare/delta` request without a selected baseline. - Production build passed; existing bundle-budget warnings remain unchanged from the baseline. - Verified on (UTC): 2026-03-08T13:04:26Z diff --git a/docs/features/checked/web/graph-export.md b/docs/features/checked/web/graph-export.md index e34eb5248..0f53bf1dd 100644 --- a/docs/features/checked/web/graph-export.md +++ b/docs/features/checked/web/graph-export.md @@ -17,7 +17,6 @@ Graph export service supporting SVG and PNG formats with options for scale, lege - `graph-filters` (`src/Web/StellaOps.Web/src/app/features/graph/graph-filters.component.ts`) - `graph-hotkey-help` (`src/Web/StellaOps.Web/src/app/features/graph/graph-hotkey-help.component.ts`) - `graph-overlays` (`src/Web/StellaOps.Web/src/app/features/graph/graph-overlays.component.ts`) - - `graph-side-panels` (`src/Web/StellaOps.Web/src/app/features/graph/graph-side-panels.component.ts`) - **Services**: - `graph-accessibility` (`src/Web/StellaOps.Web/src/app/features/graph/graph-accessibility.service.ts`) - **Source**: Feature matrix scan diff --git a/docs/features/checked/web/graph-split-view-with-diff-engine.md b/docs/features/checked/web/graph-split-view-with-diff-engine.md index bf1f7dacd..9d9c42b6a 100644 --- a/docs/features/checked/web/graph-split-view-with-diff-engine.md +++ b/docs/features/checked/web/graph-split-view-with-diff-engine.md @@ -17,7 +17,6 @@ Visual graph diff engine with split-view component for comparing two dependency/ - `graph-filters` (`src/Web/StellaOps.Web/src/app/features/graph/graph-filters.component.ts`) - `graph-hotkey-help` (`src/Web/StellaOps.Web/src/app/features/graph/graph-hotkey-help.component.ts`) - `graph-overlays` (`src/Web/StellaOps.Web/src/app/features/graph/graph-overlays.component.ts`) - - `graph-side-panels` (`src/Web/StellaOps.Web/src/app/features/graph/graph-side-panels.component.ts`) - **Services**: - `graph-accessibility` (`src/Web/StellaOps.Web/src/app/features/graph/graph-accessibility.service.ts`) - **Source**: Feature matrix scan diff --git a/docs/features/checked/web/lineage-compare-panel.md b/docs/features/checked/web/lineage-compare-panel.md index b8a3180a1..288e2d0ba 100644 --- a/docs/features/checked/web/lineage-compare-panel.md +++ b/docs/features/checked/web/lineage-compare-panel.md @@ -38,7 +38,6 @@ Interactive side-by-side comparison panel for SBOM lineage graph with dedicated - `src/Web/StellaOps.Web/src/app/features/lineage/components/audit-pack-export/models/audit-pack.models.ts` - `src/Web/StellaOps.Web/src/app/features/lineage/components/diff-table/models/diff-table.models.ts` - `src/Web/StellaOps.Web/src/app/features/lineage/components/explainer-timeline/models/explainer.models.ts` - - `src/Web/StellaOps.Web/src/app/features/lineage/components/node-diff-table/models/diff-table.models.ts` - `src/Web/StellaOps.Web/src/app/features/lineage/components/pinned-explanation/models/pinned.models.ts` - **Source**: SPRINT_20251228_008_FE_sbom_lineage_graph_ii.md diff --git a/docs/features/checked/web/lineage-timeline-slider.md b/docs/features/checked/web/lineage-timeline-slider.md index 3f18a2ff3..fb8455297 100644 --- a/docs/features/checked/web/lineage-timeline-slider.md +++ b/docs/features/checked/web/lineage-timeline-slider.md @@ -38,7 +38,6 @@ Interactive timeline slider for navigating SBOM lineage graph history. Allows sc - `src/Web/StellaOps.Web/src/app/features/lineage/components/audit-pack-export/models/audit-pack.models.ts` - `src/Web/StellaOps.Web/src/app/features/lineage/components/diff-table/models/diff-table.models.ts` - `src/Web/StellaOps.Web/src/app/features/lineage/components/explainer-timeline/models/explainer.models.ts` - - `src/Web/StellaOps.Web/src/app/features/lineage/components/node-diff-table/models/diff-table.models.ts` - `src/Web/StellaOps.Web/src/app/features/lineage/components/pinned-explanation/models/pinned.models.ts` - **Source**: SPRINT_20251228_008_FE_sbom_lineage_graph_ii.md diff --git a/docs/features/checked/web/lineage-ui-api-wiring-with-angular-signals.md b/docs/features/checked/web/lineage-ui-api-wiring-with-angular-signals.md index a36a6b414..36451dd28 100644 --- a/docs/features/checked/web/lineage-ui-api-wiring-with-angular-signals.md +++ b/docs/features/checked/web/lineage-ui-api-wiring-with-angular-signals.md @@ -38,7 +38,6 @@ Frontend API client wiring for SBOM lineage graph with Angular signals-based sta - `src/Web/StellaOps.Web/src/app/features/lineage/components/audit-pack-export/models/audit-pack.models.ts` - `src/Web/StellaOps.Web/src/app/features/lineage/components/diff-table/models/diff-table.models.ts` - `src/Web/StellaOps.Web/src/app/features/lineage/components/explainer-timeline/models/explainer.models.ts` - - `src/Web/StellaOps.Web/src/app/features/lineage/components/node-diff-table/models/diff-table.models.ts` - `src/Web/StellaOps.Web/src/app/features/lineage/components/pinned-explanation/models/pinned.models.ts` - **Source**: SPRINT_20251229_005_FE_lineage_ui_wiring.md diff --git a/docs/features/checked/web/pinned-explanations-panel.md b/docs/features/checked/web/pinned-explanations-panel.md index 58fb1e8d6..d31e6b6b6 100644 --- a/docs/features/checked/web/pinned-explanations-panel.md +++ b/docs/features/checked/web/pinned-explanations-panel.md @@ -39,7 +39,6 @@ Floating panel for pinning AI explanations and evidence summaries with multi-for - `src/Web/StellaOps.Web/src/app/features/lineage/components/audit-pack-export/models/audit-pack.models.ts` - `src/Web/StellaOps.Web/src/app/features/lineage/components/diff-table/models/diff-table.models.ts` - `src/Web/StellaOps.Web/src/app/features/lineage/components/explainer-timeline/models/explainer.models.ts` - - `src/Web/StellaOps.Web/src/app/features/lineage/components/node-diff-table/models/diff-table.models.ts` - `src/Web/StellaOps.Web/src/app/features/lineage/components/pinned-explanation/models/pinned.models.ts` - **Verification harness**: - `src/Web/StellaOps.Web/src/tests/lineage/pinned-explanations-panel.spec.ts` diff --git a/docs/features/checked/web/release-investigation-routes.md b/docs/features/checked/web/release-investigation-routes.md index 43d468bf0..84e79b234 100644 --- a/docs/features/checked/web/release-investigation-routes.md +++ b/docs/features/checked/web/release-investigation-routes.md @@ -22,7 +22,7 @@ Integrated disconnected release-investigation route families (timeline, deploy-d ## Direct-Load Workspace Contract - `/releases/investigation/deploy-diff` no longer fails with `Missing Parameters`. Direct navigation now shows `No Comparison Selected` plus recovery actions back to `/releases/deployments` and `/releases/overview`. - `/releases/investigation/change-trace` no longer renders an inert `No Change Trace Loaded` shell. Direct navigation now shows `No Comparison Selected` plus recovery actions to `/releases/deployments`, or back to deploy-diff when `from`/`to` digests are already present. -- Both workspaces preserve tenant/scope query context when it exists and fall back to the canonical `demo-prod` tenant on a fresh shell load. +- Both workspaces preserve tenant/scope query context when it exists and fall back to the canonical `default` tenant on a fresh shell load. ## Timeline Decision **Bounded-secondary-route** (not absorb-into-run-workspace). The investigation timeline is a correlation-based tool that spans multiple services by correlationId, which is conceptually different from the run workspace's timeline tab showing run execution flow. Mounting it under `/releases/investigation/timeline` avoids URL collision and keeps both capabilities distinct. diff --git a/docs/features/checked/web/web-gateway-graph-platform-client.md b/docs/features/checked/web/web-gateway-graph-platform-client.md index 46d30a0eb..6a561c398 100644 --- a/docs/features/checked/web/web-gateway-graph-platform-client.md +++ b/docs/features/checked/web/web-gateway-graph-platform-client.md @@ -17,7 +17,6 @@ Web gateway client for Graph Platform APIs with tile streaming, search, path que - `graph-filters` (`src/Web/StellaOps.Web/src/app/features/graph/graph-filters.component.ts`) - `graph-hotkey-help` (`src/Web/StellaOps.Web/src/app/features/graph/graph-hotkey-help.component.ts`) - `graph-overlays` (`src/Web/StellaOps.Web/src/app/features/graph/graph-overlays.component.ts`) - - `graph-side-panels` (`src/Web/StellaOps.Web/src/app/features/graph/graph-side-panels.component.ts`) - **Services**: - `graph-accessibility` (`src/Web/StellaOps.Web/src/app/features/graph/graph-accessibility.service.ts`) - **Source**: SPRINT_0213_0001_0002_web_ii.md diff --git a/docs/integrations/LOCAL_SERVICES.md b/docs/integrations/LOCAL_SERVICES.md index 9daa8c746..f07111cee 100644 --- a/docs/integrations/LOCAL_SERVICES.md +++ b/docs/integrations/LOCAL_SERVICES.md @@ -105,9 +105,9 @@ node src/Web/StellaOps.Web/scripts/live-integrations-ui-bootstrap.mjs - The separate first-run setup wizard (`/setup-wizard/wizard`) now reaches the Platform setup API through the frontdoor and uses persisted, installation-scoped setup sessions for the five truthful control-plane steps. -- The wizard's Admin step uses Authority's internal bootstrap API, so the local - Platform container must receive the same bootstrap key via - `STELLAOPS_BOOTSTRAP_KEY` that Authority exposes through +- The wizard's Admin and Advisory/VEX Sources steps use internal bootstrap APIs, + so both the local Platform and Concelier containers must receive the same + bootstrap key via `STELLAOPS_BOOTSTRAP_KEY` that Authority exposes through `AUTHORITY_BOOTSTRAP_APIKEY`. CLI convergence path: diff --git a/docs/modules/advisory-ai/architecture-detail.md b/docs/modules/advisory-ai/architecture-detail.md index 6fe9c38b9..4b1617c0f 100644 --- a/docs/modules/advisory-ai/architecture-detail.md +++ b/docs/modules/advisory-ai/architecture-detail.md @@ -49,6 +49,7 @@ Key stages: | `GuardrailService` | Applies redaction filters, prompt allowlists, validation schemas, and DSSE sealing. | Shares configuration with Security Guild. | | `ProfileRegistry` | Maps profile IDs to runtime implementations (local model, remote connector). | Enforces tenant consent and allowlists. | | `AdvisoryOutputStore` | PostgreSQL table storing cached artefacts plus provenance manifest. | TTL defaults 24h; DSSE metadata optional. | +| `PostgresAiConsentStore` / `PostgresAiAttestationStore` | Durable runtime state for tenant consent and AI attestations. | Bound by `AddAdvisoryAiRuntimePersistence`; fail-fast outside Development/Testing when no DB is configured. | | `AdvisoryPipelineWorker` | Background executor for queued jobs (future sprint once 004A wires queue). | Consumes `advisory.pipeline.execute` messages. | ## 3. Data contracts @@ -132,6 +133,16 @@ Profile selection is controlled via Authority configuration (`advisoryAi.allowed Cache misses trigger orchestration and inference; hits return stored artefacts immediately. TTL expiry removes entries unless `forceRefresh` has already regenerated them. +Additional live runtime tables: + +| Table | Purpose | +|-------|---------| +| `advisoryai.ai_consents` | Authoritative per-tenant, per-user consent records for AI execution and remote-profile opt-in. | +| `advisoryai.ai_run_attestations` | Durable run-level attestation payloads, content digests, and optional signed envelopes. | +| `advisoryai.ai_claim_attestations` | Durable claim-level attestation payloads keyed by claim and linked back to their parent run. | + +Runtime connection precedence for these tables is `AdvisoryAI:Storage:ConnectionString` -> `ConnectionStrings:Default` -> `Database:ConnectionString`. The web host auto-migrates this schema on startup and refuses live startup outside Development/Testing when no durable store is configured. + ## 7. Telemetry & SLOs Metrics (registered in Observability backlog): diff --git a/docs/modules/advisory-ai/architecture.md b/docs/modules/advisory-ai/architecture.md index 205f8cdf0..f7d0a6234 100644 --- a/docs/modules/advisory-ai/architecture.md +++ b/docs/modules/advisory-ai/architecture.md @@ -107,6 +107,9 @@ All context references include `content_hash` and `source_id` enabling verifiabl - `generated_at`, `model_id`, `profile` (Sovereign/FIPS etc.). - `signatures` (optional DSSE if run in deterministic mode). - Offline bundle format contains `summary.md`, `citations.json`, `context_manifest.json`, `signatures/`. +- Runtime consent and attestation state is now durable under `advisoryai.ai_consents`, `advisoryai.ai_run_attestations`, and `advisoryai.ai_claim_attestations`. +- The remaining mutable runtime state is also durable under `advisoryai.runtime_explanations`, `advisoryai.runtime_policy_intents`, `advisoryai.runtime_runs`, `advisoryai.advisory_chat_settings`, `advisoryai.conversations`, and `advisoryai.turns`. +- Runtime storage connection resolution is `AdvisoryAI:Storage:ConnectionString` -> `ConnectionStrings:Default` -> `Database:ConnectionString`; outside `Testing` the host fails fast instead of silently falling back to process-local consent, explanation, policy, run, conversation, or chat-settings state. ## 7) Profiles & sovereignty @@ -136,9 +139,11 @@ All endpoints accept `profile` parameter (default `fips-local`) and return `outp ## 11) Hosting surfaces -- **WebService** — exposes `/v1/advisory-ai/pipeline/{task}` to materialise plans and enqueue execution messages. -- **Worker** — background service draining the advisory pipeline queue (file-backed stub) pending integration with shared transport. +- **WebService** - exposes `/v1/advisory-ai/pipeline/{task}` to materialise plans and enqueue execution messages. +- **Worker** - background service draining the advisory pipeline queue (file-backed stub) pending integration with shared transport. - Both hosts register `AddAdvisoryAiCore`, which wires the SBOM context client, deterministic toolset, pipeline orchestrator, and queue metrics. +- `advisory-ai-web` registers `AddAdvisoryAiRuntimePersistence`, and `advisory-ai-worker` registers `AddAdvisoryAiCoreRuntimePersistence`; together those paths auto-migrate the `advisoryai` schema and bind durable PostgreSQL-backed consent, attestation, explanation replay, policy-intent, run, conversation, and chat-settings stores whenever runtime database configuration is present. +- In-memory runtime-state implementations are now reserved for explicit test harnesses or `Testing` fallback. Non-testing hosts fail fast when the runtime database contract is missing. - SBOM base address + tenant metadata are configured via `AdvisoryAI:SbomBaseAddress` and propagated through `AddSbomContext`. ## 12) QA harness & determinism (Sprint 110 refresh) diff --git a/docs/modules/airgap/architecture.md b/docs/modules/airgap/architecture.md index ad43188f7..65fa5757d 100644 --- a/docs/modules/airgap/architecture.md +++ b/docs/modules/airgap/architecture.md @@ -76,6 +76,11 @@ src/AirGap/ * **Cryptography** - Bundle signature verification * **Object storage** - Bundle staging and quarantine +### Runtime state ownership + +Controller seal state and time-anchor state converge on the same durable PostgreSQL record in `airgap.state`. +`StellaOps.AirGap.Time` and `StellaOps.AirGap.Controller` are not allowed to own authoritative in-memory runtime state outside `Testing`; live runtime now requires the same PostgreSQL-backed state path for both hosts. + --- ## 3) Contracts & data model diff --git a/docs/modules/attestor/README.md b/docs/modules/attestor/README.md index f58e73e0c..ea371b893 100644 --- a/docs/modules/attestor/README.md +++ b/docs/modules/attestor/README.md @@ -7,6 +7,7 @@ Attestor converts signed DSSE evidence from the Signer into transparency-log pro - Sprint tracker `docs/implplan/SPRINT_0313_0001_0001_docs_modules_attestor.md` and module `TASKS.md` added to mirror status. - Observability runbook stub + dashboard placeholder added under `operations/` (offline import) pending next demo outputs. - Platform Events samples (2025-10-18/19) remain the current canonical `attestor.logged@1`; keep verification workflows aligned. +- 2026-04-16: live Attestor runtime cut over to PostgreSQL-backed canonical attestation entry storage, PostgreSQL-backed watchlist storage, and truthful non-testing `501` behavior for bulk verification until a durable worker/store path exists. ## Why it exists - **Evidence first:** organisations need portable, verifiable attestations that prove build provenance, SBOM availability, policy verdicts, and VEX statements. @@ -38,13 +39,13 @@ All predicates capture subjects, issuer metadata, policy context, materials, opt - All verification/list APIs share the token-bucket rate limiter (`quotas.perCaller`) in addition to the existing submission limiter. ## UI, CLI, and SDK workflows -- **Console:** Evidence browser, verification reports, chain-of-custody graph, issuer/key management, attestation workbench, and bulk verification flows. +- **Console:** Evidence browser, verification reports, chain-of-custody graph, issuer/key management, attestation workbench, and bulk verification surfaces. Outside `Testing`, bulk verification currently returns truthful `501` until a durable worker/store path ships. - **CLI / SDK:** `stella attest sign|verify|list|fetch|key` commands plus language SDKs to integrate build pipelines and offline verification scripts. - **Policy Studio:** Verification policies author required predicate types, issuers, witness requirements, and freshness windows; simulations show enforcement impact. Reference: `docs/modules/attestor/guides/timestamp-policy.md` for RFC-3161 policy assertions. ## Storage, offline & air-gap posture -- PostgreSQL stores entry metadata, dedupe keys, and audit events; object storage optionally archives DSSE bundles. +- PostgreSQL stores canonical attestation entries in `attestor.entries`, watchlist state in `attestor.identity_watchlist` / `attestor.identity_alert_dedup`, and audit events in `proofchain.audit_log`; startup migrations are the runtime authority. - Export Center packages attestation bundles (`stella export attestation-bundle`) for Offline Kit delivery. - Transparency logs can be mirrored; offline mode records gaps and provides compensating controls. diff --git a/docs/modules/attestor/architecture.md b/docs/modules/attestor/architecture.md index a750fefd2..7651e6f30 100644 --- a/docs/modules/attestor/architecture.md +++ b/docs/modules/attestor/architecture.md @@ -120,20 +120,27 @@ Database: `attestor` ```sql CREATE TABLE attestor.entries ( - id UUID PRIMARY KEY, -- rekor-uuid + rekor_uuid TEXT PRIMARY KEY, + bundle_sha256 TEXT NOT NULL UNIQUE, artifact_sha256 TEXT NOT NULL, - artifact_kind TEXT NOT NULL, -- sbom|report|vex-export + artifact_kind TEXT NOT NULL, -- sbom|report|vex-export|policy-eval artifact_image_digest TEXT, artifact_subject_uri TEXT, - bundle_sha256 TEXT NOT NULL, -- canonicalized DSSE - log_index INTEGER, -- log index/sequence if provided by backend - proof_checkpoint JSONB, -- { origin, size, rootHash, timestamp } - proof_inclusion JSONB, -- { leafHash, path[] } Merkle path (tiles) - log_url TEXT, + log_index BIGINT, + log_backend TEXT NOT NULL, + log_url TEXT NOT NULL, log_id TEXT, + log_integrated_time BIGINT, created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW(), status TEXT NOT NULL, -- included|pending|failed - signer_identity JSONB -- { mode, issuer, san?, kid? } + signer_mode TEXT NOT NULL, + signer_issuer TEXT, + signer_subject_alternative_name TEXT, + signer_key_id TEXT, + proof JSONB, -- canonical Rekor checkpoint + inclusion proof + witness JSONB, + mirror JSONB ); ``` @@ -148,27 +155,27 @@ Database: `attestor` ); ``` -* `audit` table +* `audit log` table ```sql - CREATE TABLE attestor.audit ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - ts TIMESTAMPTZ DEFAULT NOW(), - caller_cn TEXT, - caller_mtls_thumbprint TEXT, - caller_sub TEXT, - caller_aud TEXT, - action TEXT NOT NULL, -- submit|verify|fetch - artifact_sha256 TEXT, - bundle_sha256 TEXT, - rekor_uuid UUID, - log_index INTEGER, - result TEXT NOT NULL, - latency_ms INTEGER, - backend TEXT + CREATE TABLE 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() ); ``` +* `watchlist` tables + + ```sql + CREATE TABLE attestor.identity_watchlist (...); + CREATE TABLE attestor.identity_alert_dedup (...); + ``` + Indexes: * `entries`: indexes on `artifact_sha256`, `bundle_sha256`, `created_at`, and composite `(status, created_at DESC)`. @@ -506,9 +513,13 @@ The exception signing service provides endpoints for signing, verifying, and ren ### 4.5 Bulk verification -`POST /api/v1/rekor/verify:bulk` enqueues a verification job containing up to `quotas.bulk.maxItemsPerJob` items. Each item mirrors the single verification payload (uuid | artifactSha256 | subject+envelopeId, optional policyVersion/refreshProof). The handler persists a PostgreSQL job record (`bulk_jobs` table) and returns `202 Accepted` with a job descriptor and polling URL. +Note (2026-04-16): non-testing runtime now returns truthful `501 Not Implemented` for the bulk verification endpoints until a durable bulk job store plus worker path exists. The previous in-memory queue behavior is test-only. -`GET /api/v1/rekor/verify:bulk/{jobId}` returns progress and per-item results (subject/uuid, status, issues, cached verification report if available). Jobs are tenant- and subject-scoped; only the initiating principal can read their progress. +Historical design note below is retained for the future durable implementation. It is not the current live host behavior. + +When the durable worker ships, `POST /api/v1/rekor/verify:bulk` should enqueue a verification job containing up to `quotas.bulk.maxItemsPerJob` items and return `202 Accepted` with a job descriptor plus polling URL. + +When the durable worker ships, `GET /api/v1/rekor/verify:bulk/{jobId}` should return progress and per-item results (subject/uuid, status, issues, cached verification report if available). Jobs remain tenant- and subject-scoped; only the initiating principal can read their progress. **Worker path:** `BulkVerificationWorker` claims queued jobs (`status=queued → running`), executes items sequentially through the cached verification service, updates progress counters, and records metrics: @@ -935,6 +946,8 @@ The Attestor provides proactive monitoring for signing identities appearing in t Authorization for the live watchlist surface follows the canonical trust scope family (`trust:read`, `trust:write`, `trust:admin`). The service still accepts legacy `watchlist:*` aliases for backward compatibility, but new clients and UI sessions should rely on the trust scopes. +Non-testing runtime persists watchlist state in PostgreSQL (`attestor.identity_watchlist`, `attestor.identity_alert_dedup`) and reserves in-memory watchlist storage for test harnesses only. + ### Event Flow ``` @@ -2695,17 +2708,17 @@ Source consolidation places all trust-domain code under a single directory for o | Attestation evidence (proofchain, inclusion proofs, Rekor entries) | Attestor | `attestor` PostgreSQL schema | High -- tamper-evident, integrity-critical | | Provenance evidence (SLSA predicates, build attestations, Merkle trees) | Provenance (library) | Consumed by Attestor/EvidenceLocker | High -- deterministic, reproducible | | Signer metadata (audit events, signing ceremony state, rate limits) | Signer | `signer` PostgreSQL schema | High -- operational security | -| Signer key material (KMS/HSM refs, Fulcio certs, trust anchors, rotation state) | Signer (KeyManagement) | `key_management` PostgreSQL schema | Critical -- cryptographic trust root | +| Signer key material (KMS/HSM refs, Fulcio certs, trust anchors, rotation state) | Signer (KeyManagement) | `signer` PostgreSQL schema | Critical -- cryptographic trust root | ### PostgreSQL Schema Ownership Each trust-domain service retains its own DbContext and dedicated PostgreSQL schema: - **`attestor` schema** -- Owned by the Attestor service. Contains `entries`, `dedupe`, `audit` tables for transparency log state. -- **`signer` schema** -- Owned by the Signer service. Contains signing ceremony audit, rate limit state, and operational metadata. -- **`key_management` schema** -- Owned by the Signer KeyManagement library. Contains key rotation records, trust anchor configurations, and HSM/KMS binding metadata. +- **`signer` schema** -- Owned by the Signer trust-domain runtime. Contains ceremony state/audit plus key-management tables (`trust_anchors`, `key_history`, `key_audit_log`) that the live Signer host auto-migrates on startup. +- **KeyManagement library boundary** -- Key rotation, trust anchor management, and HSM/KMS binding logic remain isolated in the `StellaOps.Signer.KeyManagement` library, but the canonical runtime tables now live inside the `signer` schema rather than a separate `key_management` schema. -There is **no cross-schema merge**. Each service connects with its own connection string scoped to its own schema. +There is **no cross-service schema merge**. Attestor evidence state remains isolated from Signer runtime state, and the Signer host now requires a real KeyManagement connection string outside `Testing`. The stub bearer path is test-only and is not part of the live composition root. ### Security Boundary: No-Merge Decision (ADR) diff --git a/docs/modules/attestor/implementation_plan.md b/docs/modules/attestor/implementation_plan.md index 69507898b..f99311945 100644 --- a/docs/modules/attestor/implementation_plan.md +++ b/docs/modules/attestor/implementation_plan.md @@ -4,11 +4,13 @@ Provide a concise, living plan for Attestor feature delivery, timestamping, and offline verification workflows. ## Active work +- `docs/implplan/SPRINT_20260416_017_Attestor_truthful_runtime_storage_cutover.md` - `docs/implplan/SPRINT_20260119_010_Attestor_tst_integration.md` - `docs/implplan/SPRINT_20260119_013_Attestor_cyclonedx_1.7_generation.md` - `docs/implplan/SPRINT_20260119_014_Attestor_spdx_3.0.1_generation.md` ## Near-term deliverables +- Durable bulk verification worker/store path to replace the current truthful non-testing `501` unsupported runtime. - RFC-3161 timestamping integration (signing, verification, policy context). - CycloneDX 1.7 predicate writer updates and determinism tests. - SPDX 3.0.1 predicate writer updates and determinism tests. @@ -20,6 +22,7 @@ Provide a concise, living plan for Attestor feature delivery, timestamping, and - Policy evaluation integration for timestamp assertions. ## Evidence of completion +- PostgreSQL-backed runtime proof tests for canonical entry/audit storage and watchlist persistence under `src/Attestor/StellaOps.Attestor/StellaOps.Attestor.Tests/Integration/AttestorTruthfulRuntimeTests.cs`. - Attestor timestamping library changes under `src/Attestor/__Libraries/`. - Updated CLI command handlers and tests under `src/Cli/`. - Deterministic unit tests and fixtures in `src/Attestor/__Tests/`. diff --git a/docs/modules/authority/AUTHORITY.md b/docs/modules/authority/AUTHORITY.md index c9fa6661b..cf0d1cb57 100644 --- a/docs/modules/authority/AUTHORITY.md +++ b/docs/modules/authority/AUTHORITY.md @@ -402,7 +402,7 @@ Authority now understands two flavours of sender-constrained OAuth clients: - `security.senderConstraints.dpop.allowTemporaryBypass` toggles an emergency-only bypass for sealed drills. When set to `true`, Authority logs `authority.dpop.proof.bypass`, tags `authority.dpop_result=bypass`, and issues tokens without a DPoP `cnf` claim so downstream servers know sender constraints are disabled. **Reset to `false` immediately after the exercise.** - `security.senderConstraints.dpop.nonce.enabled` enables nonce challenges for high-value audiences (`requiredAudiences`, normalised to case-insensitive strings). When a nonce is required but missing or expired, `/token` replies with `WWW-Authenticate: DPoP error="use_dpop_nonce"` (and, when available, a fresh `DPoP-Nonce` header). Clients must retry with the issued nonce embedded in the proof. - Refresh-token requests honour the original sender constraint (DPoP or mTLS). `/token` revalidates the proof/certificate, enforces the recorded thumbprint/JKT, and reuses that metadata so the new access/refresh tokens remain bound to the same key. - - `security.senderConstraints.dpop.nonce.store` selects `memory` (default) or `redis` (Valkey-backed). When `redis` is configured, set `security.senderConstraints.dpop.nonce.redisConnectionString` so replicas share nonce issuance and high-value clients avoid replay gaps during failover. + - `security.senderConstraints.dpop.nonce.store` is `redis`/Valkey-backed for non-testing DPoP-enabled runtime. `memory` remains a testing-only mode so live Authority replicas never boot fake replay/nonce state. When `redis` is configured, set `security.senderConstraints.dpop.nonce.redisConnectionString` so replicas share nonce issuance and high-value clients avoid replay gaps during failover. - Telemetry: every nonce challenge increments `authority_dpop_nonce_miss_total{reason=...}` while mTLS mismatches increment `authority_mtls_mismatch_total{reason=...}`. - Example (enabling Valkey-backed nonces; adjust audiences per deployment): ```yaml diff --git a/docs/modules/authority/README.md b/docs/modules/authority/README.md index 93b546e4d..9ae28074d 100644 --- a/docs/modules/authority/README.md +++ b/docs/modules/authority/README.md @@ -1,8 +1,9 @@ # StellaOps Authority -Authority is the platform OIDC/OAuth2 control plane that mints short-lived, sender-constrained operational tokens (OpToks) for every StellaOps service and tool. - -## Latest updates (2025-12-04) +Authority is the platform OIDC/OAuth2 control plane that mints short-lived, sender-constrained operational tokens (OpToks) for every StellaOps service and tool. + +## Latest updates (2026-04-16) +- Authority no longer uses live in-memory DPoP replay or nonce state outside `Testing`; non-testing DPoP-enabled runtime now requires durable Valkey-backed state and restart-survival proof exists in `AuthorityDpopRuntimeTests`. - Added gap remediation package for AU1–AU10 and RR1–RR10 (31-Nov-2025 FINDINGS) under `docs/modules/authority/gaps/`; includes deliverable map + evidence layout. - Sprint tracker `docs/implplan/SPRINT_0314_0001_0001_docs_modules_authority.md` and module `TASKS.md` mirror status. - Monitoring/observability references consolidated; Grafana JSON remains offline import (`operations/grafana-dashboard.json`). @@ -26,7 +27,7 @@ Authority is the platform OIDC/OAuth2 control plane that mints short-lived, send ## Operational notes - PostgreSQL (schema `authority`) for tenant, client, and token state. -- Standard plugin bootstrap provisioning retries transient storage failures during startup so seeded local users/clients converge after PostgreSQL becomes reachable. +- Standard plugin bootstrap provisioning retries transient storage failures during startup so first-party local clients converge after PostgreSQL becomes reachable; the first human admin is created through setup, not pre-seeded. - Key material in KMS/HSM with rotation runbooks (`operations/key-rotation.md`). - Monitoring runbook (`operations/monitoring.md`) and offline-import Grafana JSON (`operations/grafana-dashboard.json`). diff --git a/docs/modules/authority/architecture.md b/docs/modules/authority/architecture.md index 519b29b69..fb6b1660c 100644 --- a/docs/modules/authority/architecture.md +++ b/docs/modules/authority/architecture.md @@ -33,8 +33,10 @@ * **Device Code** (CLI login on headless agents; optional when enabled by the deployment profile) * **Authorization Code + PKCE** (browser login for UI and future human CLI flows; optional) * **Password** (current local/dev bootstrap compatibility path for human CLI login; not the target long-term operator flow) -* **Current local/dev standard-plugin seed** (`etc/authority/plugins/standard.yaml`): +* **Current local/dev standard-plugin bootstrap** (`etc/authority/plugins/standard.yaml`): + * generic tenant anchor: `default` + * no pre-seeded human admin user; the first administrator is created through the setup wizard * `stella-ops-ui`: `authorization_code refresh_token` * `stellaops-cli`: public human client with `authorization_code password refresh_token`; localhost redirect URIs are PKCE-required, and the CLI currently uses this client for fresh-shell interactive username/password login * `stellaops-cli-automation`: confidential automation client with `client_credentials` @@ -153,7 +155,7 @@ plan? = // optional hint for UIs; not used for e * `POST /revoke` → revokes refresh tokens or opaque access tokens. > Requests targeting the legacy `/oauth/{introspect|revoke}` paths receive deprecation headers and are scheduled for removal after 1 May 2026. -* **Replay prevention**: maintain **DPoP `jti` cache** (TTL ≤ 10 min) to reject duplicate proofs when services supply DPoP nonces (Signer requires nonce for high‑value operations). +* **Replay prevention**: maintain **DPoP `jti` cache** (TTL ≤ 10 min) to reject duplicate proofs when services supply DPoP nonces (Signer requires nonce for high‑value operations). In non-testing Authority runtime, this replay/nonce state must be durable and shared (Valkey-backed); in-memory state is reserved for `Testing` only. ### 3.4 UserInfo (optional for UI) @@ -556,6 +558,8 @@ Manages trusted VEX/CSAF publisher metadata. Owns: **Compiled models:** IssuerDirectoryDbContext also uses EF Core compiled models. The `` directive for `EfCore/CompiledModels/IssuerDirectoryDbContextAssemblyAttributes.cs` lives in `src/Authority/__Libraries/StellaOps.IssuerDirectory.Persistence/StellaOps.IssuerDirectory.Persistence.csproj` (relocated from `src/IssuerDirectory/` by Sprint 216). +Non-testing IssuerDirectory web runtime now requires PostgreSQL persistence; in-memory repositories remain a testing-only path. + ### 21.3 No-merge security rationale **Decision:** Schemas remain permanently separate. No cross-schema DB merge. diff --git a/docs/modules/binary-index/architecture.md b/docs/modules/binary-index/architecture.md index 05950500c..0400f561c 100644 --- a/docs/modules/binary-index/architecture.md +++ b/docs/modules/binary-index/architecture.md @@ -633,6 +633,8 @@ public enum MatchMethod { BuildIdCatalog, FingerprintMatch, RangeMatch } The `binaries` schema stores binary identity, fingerprint, and match data. +`BinaryIndex.WebService` uses the `binaries` schema as its authoritative live runtime store for binary vulnerability assertions, fix-index reads, and delta-signature state whenever `ConnectionStrings:Default` is configured. The service no longer falls back to process-local in-memory stores for these backends in live mode; missing Postgres configuration is treated as a startup configuration error. + ```sql CREATE SCHEMA IF NOT EXISTS binaries; CREATE SCHEMA IF NOT EXISTS binaries_app; @@ -1187,6 +1189,12 @@ BinaryIndex: | `RetryOnFailure` | bool | true | Retry transient failures | | `BatchSize` | int | 100 | Batch insert size | +For `BinaryIndex.WebService`, the live host contract is: +- `ConnectionStrings:Default` is required for authoritative PostgreSQL-backed golden-set, vulnerability, and delta-signature state. +- `ConnectionStrings:Redis` is required for the authoritative resolution cache. +- Live runtime does not degrade to in-memory storage when either connection string is absent. +- Restart-survival proof is covered by `BinaryIndexDurableRuntimeTests`, which writes golden-set state through the HTTP API, persists resolution cache data in Valkey, restarts the host, and reads both back from the durable services. + ```yaml Postgres: BinaryIndex: @@ -1787,10 +1795,10 @@ The Symbols subsystem provides debug symbol storage, resolution, and marketplace |---------|----------|------| | `StellaOps.Symbols.Core` | `__Libraries/StellaOps.Symbols.Core/` | Leaf library: models, abstractions (`ISymbolRepository`, `ISymbolResolver`), hashing | | `StellaOps.Symbols.Client` | `__Libraries/StellaOps.Symbols.Client/` | HTTP client for Symbols.Server API (depends on Core) | -| `StellaOps.Symbols.Infrastructure` | `__Libraries/StellaOps.Symbols.Infrastructure/` | In-memory and persistent storage, Blake3 hashing (depends on Core) | +| `StellaOps.Symbols.Infrastructure` | `__Libraries/StellaOps.Symbols.Infrastructure/` | Manifest/blob/runtime plumbing, Blake3 hashing, and testing-only local fallback storage (depends on Core) | | `StellaOps.Symbols.Marketplace` | `__Libraries/StellaOps.Symbols.Marketplace/` | Marketplace scoring and catalog (leaf) | | `StellaOps.Symbols.Bundle` | `__Libraries/StellaOps.Symbols.Bundle/` | Deterministic symbol bundles for air-gapped installs with DSSE manifests (depends on Core) | -| `StellaOps.Symbols.Server` | `StellaOps.Symbols.Server/` | Deployable ASP.NET Core WebService (depends on Core, Infrastructure, Marketplace) | +| `StellaOps.Symbols.Server` | `StellaOps.Symbols.Server/` | Deployable ASP.NET Core WebService with PostgreSQL-backed source/catalog runtime and startup migrations (depends on Core, Infrastructure, Marketplace) | | `StellaOps.Symbols.Tests` | `__Tests/StellaOps.Symbols.Tests/` | Test project covering all Symbols libraries | ### Symbols.Server API Surface @@ -1806,7 +1814,9 @@ The server exposes REST endpoints on port 8080 (mapped to `127.1.0.38:80` in com | POST | `/v1/symbols/resolve` | `symbols:read` | Batch-resolve symbol addresses | | GET | `/v1/symbols/by-debug-id/{debugId}` | `symbols:read` | Get manifests by debug ID | -Additional marketplace endpoints are mapped via `app.MapSymbolSourceEndpoints()`. +Additional marketplace endpoints are mapped via `app.MapSymbolSourceEndpoints()`. `StellaOps.Symbols.Server` now has a split runtime contract: +- Source/catalog endpoints wire `AddSymbolsRuntimePersistence(...)`, which loads `Migrations/001_initial_schema.sql` at startup and requires `ConnectionStrings:Default` outside `Testing`. +- Manifest/blob/resolve endpoints use in-memory storage only in `Testing`; outside `Testing` they return truthful `501 problem+json` responses until a durable manifest/blob backend exists. ### Consumers @@ -1815,6 +1825,6 @@ Additional marketplace endpoints are mapped via `app.MapSymbolSourceEndpoints()` --- -*Document Version: 1.6.0* -*Last Updated: 2026-03-04* +*Document Version: 1.6.1* +*Last Updated: 2026-04-16* diff --git a/docs/modules/cli/guides/setup-guide.md b/docs/modules/cli/guides/setup-guide.md index c5b8e2b5e..9a8df04ee 100644 --- a/docs/modules/cli/guides/setup-guide.md +++ b/docs/modules/cli/guides/setup-guide.md @@ -61,6 +61,7 @@ Human operators: stella auth login ``` +- Use the Authority username/password created during the setup wizard; no demo admin is pre-seeded. - The CLI defaults to the seeded human client `stellaops-cli`. - In a fresh interactive shell, if no Authority username/password is preconfigured, the CLI prompts and uses the current password-grant bootstrap path. diff --git a/docs/modules/concelier/architecture.md b/docs/modules/concelier/architecture.md index d925ed9b1..d85b1a3bc 100644 --- a/docs/modules/concelier/architecture.md +++ b/docs/modules/concelier/architecture.md @@ -109,7 +109,7 @@ Running the same export job twice against the same snapshot must yield byte-iden **Process shape:** single ASP.NET Core service `StellaOps.Concelier.WebService` hosting: -* **Scheduler** with distributed locks (PostgreSQL backed). +* **Scheduler** with distributed PostgreSQL leases backed by `vuln.job_leases`. Lease coordination is durable; job-run history and internal orchestrator registry state are not yet durably implemented in the live host. * **Connectors** (fetch/parse/map) that emit immutable observation candidates. * **Observation writer** enforcing AOC invariants via `AOCWriteGuard`. * **Linkset builder** that correlates observations into `advisory_linksets` and annotates conflicts. @@ -362,9 +362,11 @@ Events are emitted via Valkey Streams. Consumers acknowledge idempotently using --- -## 7) Storage schema (PostgreSQL) - -### Tables & indexes (LNM path) +## 7) Storage schema (PostgreSQL) + +Fresh blank databases must enable PostgreSQL extension prerequisites before Concelier's initial schema applies. In practice, Concelier now carries a dedicated pre-schema startup migration for `pg_trgm`, so trigram-backed GIN indexes converge on first boot without relying on external manual database prep. + +### Tables & indexes (LNM path) * `concelier.sources` `{_id, type, baseUrl, enabled, notes}` — connector catalog. * `concelier.source_state` `{sourceName(unique), enabled, cursor, lastSuccess, backoffUntil, paceOverrides}` — run-state (TTL indexes on `backoffUntil`). @@ -427,8 +429,8 @@ Events are emitted via Valkey Streams. Consumers acknowledge idempotently using * TTL index on `occurredAt` (configurable retention), `{type:1, occurredAt:-1}` for replay. * `concelier.export_state` `{_id(exportKind), baseExportId?, baseDigest?, lastFullDigest?, lastDeltaDigest?, cursor, files[]}` -* `locks` `{_id(jobKey), holder, acquiredAt, heartbeatAt, leaseMs, ttlAt}` (TTL cleans dead locks) -* `jobs` `{_id, type, args, state, startedAt, heartbeatAt, endedAt, error}` +* `job_leases` `{lease_key, holder, acquired_at, heartbeat_at, lease_ms, ttl_at}` used by live scheduler coordination; expired leases can be stolen safely by another runner. +* `jobs` `{_id, type, args, state, startedAt, heartbeatAt, endedAt, error}` is the planned durable owner for future job-run history. Current live runtime does not persist `/jobs` or `/internal/orch/*` state and returns `501` until a durable job/orchestrator registry backend lands. **Legacy tables** (`advisory`, `alias`, `affected`, `reference`, `merge_event`) remain read-only during the migration window to support back-compat exports. New code must not write to them; scheduled cleanup removes them after Link-Not-Merge GA. @@ -459,8 +461,8 @@ Events are emitted via Valkey Streams. Consumers acknowledge idempotently using ``` * Optional ORAS push (OCI layout) for registries. * Offline kit bundles include Trivy DB + JSON tree + export manifest. -* Mirror-ready bundles: when `concelier.trivy.mirror` defines domains, the exporter emits `mirror/index.json` plus per-domain `manifest.json`, `metadata.json`, and `db.tar.gz` files with SHA-256 digests so Concelier mirrors can expose domain-scoped download endpoints. -* Concelier.WebService serves `/concelier/exports/index.json` and `/concelier/exports/mirror/{domain}/…` directly from the export tree with hour-long budgets (index: 60 s, bundles: 300 s, immutable) and per-domain rate limiting; the endpoints honour Stella Ops Authority or CIDR bypass lists depending on mirror topology. +* Mirror-ready bundles: when `concelier.trivy.mirror` defines domains, the exporter emits `mirror/index.json` plus per-domain `manifest.json`, `metadata.json`, and `db.tar.gz` files with SHA-256 digests so Concelier mirrors can expose domain-scoped download endpoints. +* Concelier.WebService serves `/concelier/exports/index.json` and `/concelier/exports/mirror/{domain}/…` directly from the export tree with hour-long budgets (index: 60 s, bundles: 300 s, immutable) and per-domain rate limiting. Live downloads now require persisted mirror-domain state from the mirror read model rather than env-seeded domain lists; `Testing` can still seed that state through the management API. ### 7.3 Hand‑off to Signer/Attestor (optional) @@ -480,14 +482,19 @@ GET /healthz | /readyz GET /status → sources, last runs, export cursors ``` -**Sources & jobs** +**Sources & jobs** ``` GET /sources → list of configured sources POST /sources/{name}/trigger → { jobId } POST /sources/{name}/pause | /resume → toggle -GET /jobs/{id} → job status -``` +GET /jobs/{id} → job status +``` + +Current runtime note: `/jobs`, `/internal/orch/*`, and the coordinator-backed manual sync compatibility routes (`/api/v1/advisory-sources/{sourceId}/sync`, `/api/v1/advisory-sources/sync`, `/api/v1/concelier/mirrors/{mirrorId}/sync`) are not durably implemented in the live host. Outside `Testing` they return explicit `501` responses rather than falling back to in-memory state. +Current signals note: `/v1/signals/symbols/*` is also not durably implemented in the live host. Outside `Testing` it returns explicit `501` responses rather than falling back to the process-local affected-symbol store. +Current advisory source note: the live host also exposes `/api/v1/advisory-sources/*` for operator/UI source status, enablement, health checks, and sync triggers. Enabled state is now persisted in the Concelier source store so restarts, setup skip/apply flows, and later integrations-page toggles all observe the same source truth. Platform setup uses bootstrap-key-protected `/internal/setup/advisory-sources/{probe,apply}` endpoints to seed initial source configuration without requiring a tenant session. +Mirror bootstrap truthfulness note: `/internal/setup/advisory-sources/{probe,apply}` now validates the selected mirror/source configuration before apply succeeds. Mirror mode probes `/concelier/exports/index.json` with normal runtime TLS rules, so certificate/hostname mismatches are returned as actionable setup failures instead of "background sync" warnings. **Exports** @@ -495,11 +502,13 @@ GET /jobs/{id} → job status POST /exports/json { full?:bool, force?:bool, attest?:bool } → { exportId, digest, rekor? } POST /exports/trivy { full?:bool, force?:bool, publish?:bool, attest?:bool } → { exportId, digest, rekor? } GET /exports/{id} → export metadata (kind, digest, createdAt, rekor?) -GET /concelier/exports/index.json → mirror index describing available domains/bundles -GET /concelier/exports/mirror/{domain}/manifest.json -GET /concelier/exports/mirror/{domain}/bundle.json -GET /concelier/exports/mirror/{domain}/bundle.json.jws -``` +GET /concelier/exports/index.json → mirror index describing available domains/bundles +GET /concelier/exports/mirror/{domain}/manifest.json +GET /concelier/exports/mirror/{domain}/bundle.json +GET /concelier/exports/mirror/{domain}/bundle.json.jws +``` + +Current mirror runtime note: the public mirror download surface no longer trusts `ConcelierOptions.Mirror.Domains` in live runtime. Outside `Testing`, index/download requests succeed only when the requested domain exists in the persisted mirror-domain store; config-only seeded domains are ignored. **Search (operator debugging)** @@ -509,7 +518,7 @@ GET /advisories?scheme=CVE&value=CVE-2025-12345 GET /affected?productKey=pkg:rpm/openssl&limit=100 ``` -**Mirror domain management** (under `/api/v1/mirror`) +**Mirror domain management** (under `/api/v1/advisory-sources/mirror`) ``` GET /config → current mirror config (mode, signing, refresh interval) @@ -526,7 +535,7 @@ GET /domains/{domainId}/status → domain sync status (last gener POST /test → test mirror endpoint connectivity ``` -**Mirror consumer configuration** (under `/api/v1/mirror`) +**Mirror consumer configuration** (under `/api/v1/advisory-sources/mirror`) ``` GET /consumer → current consumer connector configuration (base address, domain, signature, timeout, connection status, last sync) @@ -535,18 +544,18 @@ POST /consumer/discover → fetch mirror index from base a POST /consumer/verify-signature → fetch JWS header from selected domain's bundle, return detected algorithm, key ID, and provider ``` -The consumer endpoints configure the `StellaOpsMirrorConnector` at runtime without requiring service restarts. Configuration is persisted via `IMirrorConsumerConfigStore` (in-memory, with planned DB backend). The `/consumer/discover` endpoint enables the UI setup wizard to present operators with a list of available domains before committing to a configuration. +The consumer endpoints configure the `StellaOpsMirrorConnector` at runtime without requiring service restarts. Configuration is persisted via `IMirrorConsumerConfigStore` in PostgreSQL. The `/consumer/discover` endpoint enables the UI setup wizard to present operators with a list of available domains before committing to a configuration. -**Air-gap bundle import** (under `/api/v1/mirror`) - -``` -POST /import → import a mirror bundle from a local filesystem path { bundlePath, verifyChecksums, verifyDsse, trustRootsPath? } -GET /import/status → import progress and result (exports imported, total size, errors, warnings) -``` - -The import endpoint triggers an async import of a mirror bundle directory accessible to the Concelier container. It parses the bundle manifest, verifies SHA-256 checksums (when `verifyChecksums` is true), detects DSSE envelopes (when `verifyDsse` is true), and copies artifacts into the local data store. Import state is tracked by `IMirrorBundleImportStore`. This exposes the same functionality as the CLI `MirrorBundleImportService` via HTTP. - -Mirror domains group export plans with shared rate limits and authentication rules. Exports support multi-value filter shorthands: `sourceCategory` (e.g., `"Distribution"` resolves to all distro sources), `sourceTag` (e.g., `"linux"`), and comma-separated `sourceVendor` values. Domain configuration is persisted in `excititor.mirror_domains` / `excititor.mirror_exports` tables, with env-var config as fallback. The `MirrorExportScheduler` background service periodically regenerates stale bundles (configurable via `RefreshIntervalMinutes`, default 60 minutes). +**Air-gap bundle import** (under `/api/v1/advisory-sources/mirror`) + +``` +POST /import → import a mirror bundle from a local filesystem path { bundlePath, verifyChecksums, verifyDsse, trustRootsPath? } +GET /import/status → import progress and result (exports imported, total size, errors, warnings) +``` + +The current HTTP mirror import path is durably implemented in the live host. `/api/v1/advisory-sources/mirror/import` validates a local mirror bundle, persists import status, projects `manifest.json` and `bundle.json` into the live `/concelier/exports/mirror/` surface, and refreshes the public `index.json`. `/api/v1/advisory-sources/mirror/import/status` reads the latest persisted import status instead of fabricating progress from a filesystem inspection pass. Both `bundlePath` and `trustRootsPath` must resolve under the configured `Mirror.ImportRoot`; relative paths are resolved against that allowlisted root, and paths outside it are rejected. Detached JWS verification is supported when callers provide a `trustRootsPath`; unsigned bundles still import truthfully with a warning rather than a synthetic success banner. + +Mirror domains group export plans with shared rate limits and authentication rules. Exports support multi-value filter shorthands: `sourceCategory` (e.g., `"Distribution"` resolves to all distro sources), `sourceTag` (e.g., `"linux"`), and comma-separated `sourceVendor` values. Domain configuration is persisted in `excititor.mirror_domains` / `excititor.mirror_exports` tables. The `MirrorExportScheduler` background service periodically regenerates stale bundles (configurable via `RefreshIntervalMinutes`, default 60 minutes). **AuthN/Z:** Authority tokens (OpTok) with roles: `concelier.read`, `concelier.admin`, `concelier.export`. diff --git a/docs/modules/doctor/architecture.md b/docs/modules/doctor/architecture.md index 2b158507f..436ad3761 100644 --- a/docs/modules/doctor/architecture.md +++ b/docs/modules/doctor/architecture.md @@ -39,6 +39,9 @@ Three default Doctor schedules are seeded by `SystemScheduleBootstrap`: The Doctor WebService (`src/Doctor/StellaOps.Doctor.WebService/`) remains the execution engine. The plugin communicates with it via HTTP POST to `/api/v1/doctor/run`. +The legacy `src/Doctor/StellaOps.Doctor.Scheduler/` host is retained only for Development/Testing. +It now fails fast outside Development/Testing so live runtime cannot silently fall back to the deprecated in-memory schedule/trend repositories. + ### AdvisoryAI Diagnosis Surface (run-003 remediation) Doctor WebService now exposes a diagnosis endpoint for AdvisoryAI-backed health analysis: @@ -51,7 +54,7 @@ The endpoint accepts either: Runtime wiring includes: - `IDoctorContextAdapter` for deterministic context projection from Doctor reports - `IDoctorAIDiagnosisService` (deterministic implementation) for assessment, root cause, correlation, and remediation projection -- schema enrichment through `IEvidenceSchemaRegistry.RegisterCommonSchemas()` +- a built-in `IEvidenceSchemaRegistry` seeded with the common diagnosis schemas during service registration; live runtime no longer binds the mutable in-memory registry type ### AdvisoryAI Diagnosis Surface (run-002 remediation) diff --git a/docs/modules/excititor/architecture.md b/docs/modules/excititor/architecture.md index 6315c2516..8c2c4d030 100644 --- a/docs/modules/excititor/architecture.md +++ b/docs/modules/excititor/architecture.md @@ -159,7 +159,8 @@ Schema: `vex` - **Observations/linksets** - use the append-only Postgres linkset schema already defined for `IAppendOnlyLinksetStore` (tables `vex_linksets`, `vex_linkset_observations`, `vex_linkset_disagreements`, `vex_linkset_mutations`) with indexes on `(tenant, vulnerability_id, product_key)` and `updated_at`. - **Claims** - `vex.claims` stores normalized, queryable claim projections keyed by deterministic `claim_hash`, with JSONB columns for product/document metadata plus indexes on `(tenant, provider_id, vulnerability_id, product_key, last_seen)` and `(tenant, vulnerability_id, last_seen)`. -- **Graph overlays** - materialized cache table `vex_overlays` (tenant, purl, advisory_id, source) storing JSONB payloads that follow `docs/modules/excititor/schemas/vex_overlay.schema.json` (schemaVersion 1.0.0). Cache eviction via `cached_at + ttl_seconds`; overlays regenerate when linkset or observation hashes change. +- **Attestations** - `vex.attestations` stores durable DSSE/VEX attestation envelopes keyed by `(tenant, attestation_id)` with manifest lookup and attested-at indexes. Startup migration `005_vex_attestations.sql` creates the table in the active runtime schema so isolated test schemas and the default `vex` schema use the same contract. +- **Graph overlays** - materialized cache table `vex.graph_overlays` (tenant, purl, advisory_id, source) storing JSONB payloads that follow `docs/modules/excititor/schemas/vex_overlay.schema.json` (schemaVersion 1.0.0). Live WebService runtime resolves `IGraphOverlayStore` to the Postgres-backed store; there is no in-memory production fallback. **Canonicalisation & hashing** @@ -178,8 +179,8 @@ List/query `/vex/raw` via `SELECT ... FROM vex.vex_raw_documents WHERE tenant=@t **Runtime convergence** -1. `StellaOps.Excititor.WebService` and `StellaOps.Excititor.Worker` resolve `IVexProviderStore`, `IVexConnectorStateRepository`, and `IVexClaimStore` from `AddExcititorPersistence`; the live hosts do not register in-memory fallbacks. -2. `StellaOps.Excititor.Persistence` owns startup migrations for the `vex` schema, including `vex.claims` creation and cleanup of historical demo rows from older local installs. +1. `StellaOps.Excititor.WebService` and `StellaOps.Excititor.Worker` resolve `IVexProviderStore`, `IVexConnectorStateRepository`, `IVexClaimStore`, `IVexAttestationStore`, and the WebService graph overlay store from persisted services; the live hosts do not register in-memory fallbacks for these paths. +2. `StellaOps.Excititor.Persistence` owns startup migrations for the active runtime schema, including claims, attestation storage, graph overlays, and cleanup of historical demo rows from older local installs. 3. The Excititor migration assembly embeds only active top-level SQL files. Archived pre-1.0 scripts and demo-seed SQL are excluded so startup/test migration loaders do not replay historical or fake runtime state. --- diff --git a/docs/modules/excititor/graph-overlays.md b/docs/modules/excititor/graph-overlays.md index 60a687846..9c864ff17 100644 --- a/docs/modules/excititor/graph-overlays.md +++ b/docs/modules/excititor/graph-overlays.md @@ -13,11 +13,11 @@ Defines the graph-ready overlay built from Link-Not-Merge observations/linksets - Provenance: carries `linksetId`, `linksetHash`, `observationHashes[]`, optional `policyHash`, `sbomContextHash`, and `planCacheKey` for replay. ## Postgres materialization (IAppendOnlyLinksetStore) -- Table `vex_overlays` (materialized cache): +- Table `vex.graph_overlays` (materialized cache): - Primary key: `(tenant, purl, advisory_id, source)`. - - Columns: `status`, `justifications` (jsonb), `conflicts` (jsonb), `observations` (jsonb), `provenance` (jsonb), `cached_at`, `ttl_seconds`, `schema_version`. - - Indexes: unique `(tenant, purl, advisory_id, source)`, plus `(tenant, cached_at)` for TTL sweeps. -- Overlay rows are regenerated when linkset hash or observation hash set changes; cache evictions use `cached_at + ttl_seconds`. + - Columns: `tenant`, `purl`, `advisory_id`, `source`, `generated_at`, `payload` (`jsonb`). + - Indexes: primary key plus `(tenant, generated_at DESC)` for deterministic recency reads. +- Overlay rows are regenerated when linkset hash or observation hash set changes; the latest materialized payload is upserted per `(tenant, purl, advisory_id, source)` key. - Linksets and observation hashes come from the append-only linkset store (`IAppendOnlyLinksetStore`) to preserve Aggregation-Only Contract guarantees. ## API shape (Graph/Vuln Explorer) @@ -84,4 +84,4 @@ Defines the graph-ready overlay built from Link-Not-Merge observations/linksets - Consumers (Console, Vuln Explorer, Policy Engine, Risk) should treat `vex_overlay.schema.json` as the authoritative contract. - Offline kits must bundle the schema file and sample payloads under `docs/modules/excititor/samples/` with SHA256 manifests. - Future schema versions must bump `schemaVersion` and add migration notes to this document and `docs/modules/excititor/architecture.md`. -- Policy and Risk surfaces in WebService now read overlays directly (with claim-store fallback for policy tests) to produce lookup and risk feeds; overlay cache/store are selected per tenant (in-memory by default, Postgres `vex.graph_overlays` when configured). +- Policy and Risk surfaces in WebService now read overlays directly (with claim-store fallback for policy tests) to produce lookup and risk feeds; the live WebService binds `IGraphOverlayStore` to the Postgres-backed `vex.graph_overlays` store and does not fall back to an in-memory runtime implementation. diff --git a/docs/modules/export-center/README.md b/docs/modules/export-center/README.md index 5f20e7819..e75f311a4 100644 --- a/docs/modules/export-center/README.md +++ b/docs/modules/export-center/README.md @@ -6,6 +6,7 @@ Export Center packages reproducible evidence bundles (JSON, Trivy DB, mirror) wi - Sprint tracker `docs/implplan/SPRINT_0320_0001_0001_docs_modules_export_center.md` and module `TASKS.md` added to mirror status. - Observability runbook stub + dashboard placeholder added under `operations/` (offline import). - Bundle/profile/offline manifest guidance reaffirmed (`devportal-offline*.md`, `mirror-bundles.md`, `provenance-and-signing.md`). +- 2026-04-16 truthful-runtime cutover: non-testing `StellaOps.ExportCenter.WebService` now uses PostgreSQL-backed canonical export repositories plus a real Evidence Locker client, while non-durable verification, attestation, incident, risk-bundle, simulation-export, audit-bundle, and exception-report host paths return explicit `501 problem+json` until durable backends exist. Timeline publication also no longer defaults to an in-memory sink outside `Testing`. ## Responsibilities - Coordinate export jobs based on profiles and scope selectors. @@ -34,6 +35,7 @@ Export Center packages reproducible evidence bundles (JSON, Trivy DB, mirror) wi - Observability assets: `operations/observability.md` and `operations/dashboards/export-center-observability.json` (offline import). - Mirror bundle instructions and validation notes. - Telemetry dashboards for export latency and retry rates. +- Testing-only in-memory runtime switches are explicit (`Export:AllowInMemoryRepositories`, `Export:UseInMemoryEvidenceLocker`, `Export:UseInMemoryVerificationArtifactStore`, `Export:UseInMemoryAttestationStore`, `Export:UseInMemoryPromotionAttestationStore`, `Export:UseInMemoryIncidentManager`, `Export:UseInMemoryRiskBundleJobHandler`, `Export:UseInMemorySimulationExporter`, `Export:UseInMemoryAuditBundleJobHandler`, `Export:UseInMemoryExceptionReportGenerator`, `Export:UseInMemoryTimelineNotificationSink`). Non-testing runtime must use durable services or truthful `501` gaps. ## Related resources - ./operations/runbook.md diff --git a/docs/modules/export-center/api.md b/docs/modules/export-center/api.md index 8180401e5..fa662fc90 100644 --- a/docs/modules/export-center/api.md +++ b/docs/modules/export-center/api.md @@ -5,7 +5,7 @@ This reference describes the Export Center API introduced in Export Center Phase 1 (Epic 10) and extended in Phase 2. Use it alongside the [Export Center Architecture](architecture.md) and [Profiles](profiles.md) guides for service-level semantics. -> Status: Endpoint implementation lands with `EXPORT-SVC-35-006` (Sprint 35) and related follow-on tasks. As of the current build the WebService hosts only the template stub; use this contract for coordination and update once the API is wired. +> Status: The current ExportCenter host now uses PostgreSQL-backed canonical export profile/run/artifact repositories in non-testing runtime. Verification artifact readback, export/promotion attestation readback and verify, incident management, risk bundle jobs, simulation export, audit bundle generation, and exception report generation are still durable-backend gaps and currently return `501 problem+json` outside `Testing` instead of simulating in-memory persistence. Timeline publication also no longer silently routes through an in-memory sink outside `Testing`. ## 1. Authentication and headers diff --git a/docs/modules/export-center/architecture.md b/docs/modules/export-center/architecture.md index a34c78564..a31fa7aca 100644 --- a/docs/modules/export-center/architecture.md +++ b/docs/modules/export-center/architecture.md @@ -4,13 +4,19 @@ The Export Center is the dedicated service layer that packages StellaOps evidence and policy overlays into reproducible bundles. It runs as a multi-surface API backed by asynchronous workers and format adapters, enforcing Aggregation-Only Contract (AOC) guardrails while providing deterministic manifests, signing, and distribution paths. -## Runtime topology -- **Export Center API (`StellaOps.ExportCenter.WebService`).** Receives profile CRUD, export run requests, status queries, and download streams through the unified Web API gateway. Enforces tenant scopes, RBAC, quotas, and concurrency guards. -- **Export Center Worker (`StellaOps.ExportCenter.Worker`).** Dequeues export jobs from the Orchestrator, resolves selectors, invokes adapters, and writes manifests and bundle artefacts. Stateless; scales horizontally. -- **Backing stores.** - - PostgreSQL tables: `export_profiles`, `export_runs`, `export_inputs`, `export_distributions`, `export_events`. - - Object storage bucket or filesystem for staging bundle payloads. - - Optional registry/object storage credentials injected via Authority-scoped secrets. +## Runtime topology +- **Export Center API (`StellaOps.ExportCenter.WebService`).** Receives profile CRUD, export run requests, status queries, and download streams through the unified Web API gateway. Enforces tenant scopes, RBAC, quotas, and concurrency guards. +- **Export Center Worker (`StellaOps.ExportCenter.Worker`).** Dequeues export jobs from the Orchestrator, resolves selectors, invokes adapters, and writes manifests and bundle artefacts. Stateless; scales horizontally. +- **Backing stores.** + - PostgreSQL tables: `export_profiles`, `export_runs`, `export_inputs`, `export_distributions`, `export_events`. + - Object storage bucket or filesystem for staging bundle payloads. + - Optional registry/object storage credentials injected via Authority-scoped secrets. +- **Current truthful runtime boundary.** + - Non-testing `StellaOps.ExportCenter.WebService` now requires PostgreSQL-backed canonical export repositories and a real `EvidenceLocker:BaseUrl`; it no longer falls back to `Development` in-memory repositories or an in-memory evidence locker client. + - Non-testing export verification artifact readback plus export and promotion attestation readback/verification are currently explicit `501 problem+json` gaps until a durable backend exists. + - Non-testing incident management, risk bundle job orchestration, simulation export, audit bundle generation, and exception report generation also now fail with explicit `501 problem+json` responses instead of keeping canonical state in-process. + - Non-testing timeline publication no longer defaults to `InMemoryExportNotificationSink`; without a durable sink backend it now reports truthful delivery failure, while `Testing` can opt into the in-memory sink explicitly. + - `Testing` can still opt into the in-memory host implementations, but only through explicit `Export:UseInMemory*` switches. - **Integration peers.** - **Findings Ledger** for advisory, VEX, SBOM payload streaming. - **Policy Engine** for deterministic policy snapshots and evaluated findings. @@ -65,8 +71,9 @@ All endpoints require Authority-issued JWT + DPoP tokens with scopes `export:run - Trivy adapters materialise SQLite databases or tar archives matching Trivy DB expectations; schema version gates prevent unsupported outputs. - Mirror adapters assemble deterministic filesystem trees (manifests, indexes, payload subtrees) and, when configured, OCI artefact layers. - **Manifest generator.** Aggregates counts, bytes, hash digests (SHA-256), profile metadata, and input references. Writes `export.json` and `provenance.json` using canonical JSON (sorted keys, RFC3339 UTC timestamps). -- **Signing service.** Integrates with platform KMS via Authority (default cosign signer). Produces in-toto SLSA attestations when configured. Supports detached signatures and optional in-bundle signatures. -- **Distribution drivers.** `dist-http` exposes staged files via download endpoint; `dist-oci` pushes artefacts to registries using ORAS with digest pinning; `dist-objstore` uploads to tenant-specific prefixes with immutability flags. +- **Signing service.** Integrates with platform KMS via Authority (default cosign signer). Produces in-toto SLSA attestations when configured. Supports detached signatures and optional in-bundle signatures. +- **Distribution drivers.** `dist-http` exposes staged files via download endpoint; `dist-oci` pushes artefacts to registries using ORAS with digest pinning; `dist-objstore` uploads to tenant-specific prefixes with immutability flags. +- **Truthful unsupported paths.** The current web host no longer pretends attestation/verification readback or the incident/risk/simulation/audit/exception job surfaces are durable. Until durable backends land, those endpoints fail with `501 problem+json` outside `Testing`. Timeline publication also no longer silently buffers to an in-process sink outside `Testing`. ## Data model snapshots diff --git a/docs/modules/export-center/provenance-and-signing.md b/docs/modules/export-center/provenance-and-signing.md index bc0a56150..b3cd116e1 100644 --- a/docs/modules/export-center/provenance-and-signing.md +++ b/docs/modules/export-center/provenance-and-signing.md @@ -4,6 +4,9 @@ Export Center runs emit deterministic manifests, provenance records, and signatures so operators can prove bundle integrity end-to-end—whether the artefact is downloaded over HTTPS, pulled as an OCI object, or staged through the Offline Kit. This guide captures the canonical artefacts, signing pipeline, verification workflows, and failure handling expectations that backlogs `EXPORT-SVC-35-005` and `EXPORT-SVC-37-002` implement. +Current runtime note: +- Non-testing `StellaOps.ExportCenter.WebService` no longer keeps attestation or verification readback state in-process. Until a durable backend is implemented, the direct attestation and promotion-attestation readback/verify surfaces return `501 problem+json` instead of simulating persistence in memory. + --- ## 1. Goals & scope diff --git a/docs/modules/findings-ledger/README.md b/docs/modules/findings-ledger/README.md index cb8491296..a5430c2af 100644 --- a/docs/modules/findings-ledger/README.md +++ b/docs/modules/findings-ledger/README.md @@ -33,6 +33,13 @@ Previously archived docs for RiskEngine and VulnExplorer are in `docs-archived/m - `signedScore` is emitted only when cached or historical scoring state exists for the resolved finding. - `proofSubjectId` is surfaced only when the projection carries replay/proof identity, allowing the Web console to enable verification only when a real proof subject exists. +## Runtime cutover status + +- `RiskEngine.WebService` is PostgreSQL-backed in every non-testing environment. The previous live in-memory score-result path is no longer part of the production host composition root. +- `Findings.Ledger.WebService` keeps compatibility-only scoring state, webhook registration state, runtime traces/timeline state, and merged VulnExplorer write state isolated to `Testing`. In non-testing environments those retired write surfaces return truthful `501 problem+json` responses instead of fabricating success. +- Projection-backed read surfaces remain live and truthful: `GET /v1/vulns`, `GET /v1/vulns/{id}`, `GET /v1/evidence-subgraph/{vulnId}`, and `GET /api/v2/security/vulnerabilities/{identifier}` still resolve from persisted Findings projections. +- `LedgerDataSource` now applies UTC session defaults and `search_path=findings,public` on every PostgreSQL connection so raw-SQL repositories resolve canonical Findings tables consistently after restart. + ## Implementation Status ### Delivery Phases diff --git a/docs/modules/findings-ledger/implementation_plan.md b/docs/modules/findings-ledger/implementation_plan.md index 510e9f70f..c54217a70 100644 --- a/docs/modules/findings-ledger/implementation_plan.md +++ b/docs/modules/findings-ledger/implementation_plan.md @@ -4,7 +4,13 @@ Define the delivery plan for the Findings Ledger service, replay harness, observability, and air-gap provenance so audits can verify deterministic state reconstruction. ## Active work -- No active sprint tracked here yet. Use `docs/modules/findings-ledger/gaps-FL1-FL10.md` for remediation tracking. +- Runtime fake-removal work for Findings/RiskEngine was completed under `docs/implplan/SPRINT_20260415_006_DOCS_policy_findings_signer_real_backend_cutover.md`. +- Use `docs/modules/findings-ledger/gaps-FL1-FL10.md` for the remaining product-capability remediation backlog. + +## Current host posture +- `RiskEngine.WebService` now runs against PostgreSQL outside `Testing`; in-memory result stores are test-only. +- `Findings.Ledger.WebService` non-testing hosts no longer fabricate scoring/webhook/runtime/VulnExplorer write state. Retired compatibility writes fail with truthful `501 problem+json`, while projection-backed reads remain served from persisted Findings state. +- The standalone `StellaOps.VulnExplorer.Api` host remains retired; no separate fake backend was reintroduced for legacy write flows. ## Near-term deliverables - Observability baselines: metrics, logs, traces, dashboards, and alert rules per `docs/modules/findings-ledger/observability.md`. diff --git a/docs/modules/graph/README.md b/docs/modules/graph/README.md index 1465648fd..9a7abc1cd 100644 --- a/docs/modules/graph/README.md +++ b/docs/modules/graph/README.md @@ -2,6 +2,12 @@ Graph Indexer + Graph API build the tenant-scoped knowledge graph that powers blast-radius analysis, provenance timelines, and saved-query automation across StellaOps. Cartographer has been retired as of 2025-10-30 (see `docs/updates/2025-10-30-devops-governance.md`); this module now owns ingestion, storage, overlays, and query surfaces for graph data. +## Runtime cutover status +- Non-testing Graph API hosts now require Graph PostgreSQL configuration; only `Testing` may boot with the deterministic empty in-memory repository. +- Saved views are durable in non-testing hosts via `graph.saved_views`; the in-memory saved-view store is harness-only. +- Live Graph query/search/path/diff surfaces remain repository-backed, but live overlays, export jobs, and edge-metadata explanations no longer fabricate in-process data. Non-testing hosts return truthful `501` for those routes until durable backends land. +- Live audit logging is log-only and no longer keeps in-process request history. + ## Scope & responsibilities - Ingest SBOM snapshots, advisory/VEX events, policy overlays, and runtime signals to maintain a first-party graph representation with deterministic node/edge identities. - Serve APIs and saved-query tooling for impact analysis, dependency traversal, diffing, and policy/VEX overlays with explainable provenance. diff --git a/docs/modules/graph/architecture.md b/docs/modules/graph/architecture.md index e08fe1ce9..9347d3c88 100644 --- a/docs/modules/graph/architecture.md +++ b/docs/modules/graph/architecture.md @@ -31,14 +31,14 @@ - `POST /graph/diff` — compares `snapshotA` vs `snapshotB`, streaming node/edge added/removed/changed tiles plus stats; budget enforcement mirrors `/graph/query`. - `POST /graph/export` — async job producing deterministic manifests (`sha256`, size, format) for `ndjson/csv/graphml/png/svg`; download via `/graph/export/{jobId}`. - `POST /graph/lineage` - returns SBOM lineage nodes/edges anchored by `artifactDigest` or `sbomDigest`, with optional relationship filters and depth limits. -- Runtime repository selection is config-driven at service resolution time: when `Postgres:Graph` is configured, the live `/graph/query`, `/graph/diff`, and shipped `/graphs*` compatibility surfaces materialize persisted rows from `graph.graph_nodes`, `graph.graph_edges`, and `graph.pending_snapshots`; when it is not configured, the runtime fallback is an empty in-memory repository rather than historical demo-seeded graph data. +- Runtime repository selection is config-driven at service resolution time: when `Postgres:Graph` is configured, the live `/graph/query`, `/graph/diff`, and shipped `/graphs*` compatibility surfaces materialize persisted rows from `graph.graph_nodes`, `graph.graph_edges`, and `graph.pending_snapshots`; when it is not configured, only the `Testing` environment may use the empty in-memory repository harness. - Compatibility facade for the shipped Angular explorer: - `GET /graphs`, `GET /graphs/{graphId}`, `GET /graphs/{graphId}/tiles` - `GET /search`, `GET /paths` - `GET /graphs/{graphId}/export`, `GET /assets/{assetId}/snapshot`, `GET /nodes/{nodeId}/adjacency` - `GET/POST/DELETE /graphs/{graphId}/saved-views` - The compatibility tile surface emits only `policy`, `vex`, and `aoc` overlays on the shipped web path. - - Saved views are persisted in PostgreSQL table `graph.saved_views` when `Postgres:Graph` is configured; the API falls back to an in-memory store only for hosts that do not wire Graph persistence. + - Saved views are persisted in PostgreSQL table `graph.saved_views` when `Postgres:Graph` is configured; the API falls back to an in-memory store only in the `Testing` environment. - **Edge Metadata API** (added 2025-01): - `POST /graph/edges/metadata` — batch query for edge explanations; request contains `EdgeIds[]`, response includes `EdgeTileWithMetadata[]` with full provenance. - `GET /graph/edges/{edgeId}/metadata` — single edge metadata with explanation, via, provenance, and evidence references. @@ -47,7 +47,17 @@ - `GET /graph/edges/by-evidence?evidenceType=&evidenceRef=` — query edges by evidence reference. - Legacy: `GET /graph/nodes/{id}`, `POST /graph/query/saved`, `GET /graph/impact/{advisoryKey}`, `POST /graph/overlay/policy` remain in spec but should align to the NDJSON surfaces above as they are brought forward. -### 3.1) Tenant and auth resolution contract (Sprint 20260222.058) +### 3.1) Current runtime posture (Sprint 20260416_003) + +- Non-testing Graph hosts require `Postgres:Graph`; startup fails fast when the canonical Graph PostgreSQL connection string is absent. +- `Testing` is the only supported environment for the empty in-memory Graph runtime harness. +- Non-testing `POST /graph/query` and `POST /graph/paths` no longer fabricate overlays. Requests that ask for overlays return truthful `501 GRAPH_FEATURE_UNAVAILABLE` responses until a durable overlay backend exists. +- Non-testing `POST /graph/export` and `GET /graph/export/{jobId}` now return truthful `501 GRAPH_FEATURE_UNAVAILABLE` responses instead of synthesizing in-memory export jobs. +- Non-testing edge-metadata surfaces (`/graph/edges/metadata`, `/graph/edges/{edgeId}/metadata`, `/graph/edges/path/...`, `/graph/edges/by-reason/...`, `/graph/edges/by-evidence`) now return truthful `501 GRAPH_FEATURE_UNAVAILABLE` responses until a durable backend exists. +- The shipped `/graphs/{graphId}/tiles` compatibility surface no longer emits fabricated overlays outside `Testing`. +- Live audit logging is log-only; request history is no longer retained in-process as pretend durable state. + +### 3.2) Tenant and auth resolution contract (Sprint 20260222.058) - Graph uses a single tenant resolver path (`GraphRequestContextResolver`) across search/query/paths/diff/lineage/export and edge-metadata endpoints. - Tenant source precedence and compatibility: @@ -61,7 +71,7 @@ - Scope checks are policy-driven (`Graph.ReadOrQuery`, `Graph.Query`, `Graph.Export`) and no endpoint directly trusts raw scope headers. - Rate limiting and audit logging use the resolved tenant context; authenticated flows no longer collapse to ambiguous `"unknown"` tenant keys. -### 3.2) Edge Metadata Contracts +### 3.3) Edge Metadata Contracts The edge metadata system provides explainability for graph relationships: @@ -71,7 +81,7 @@ The edge metadata system provides explainability for graph relationships: - **EdgeProvenanceRef** record: Source system, collection timestamp, SBOM digest, scan digest, attestation ID, event offset. - **EdgeTileWithMetadata** record: Extends `EdgeTile` with `Explanation` property containing the full metadata. -### 3.3) Localization runtime contract (Sprint 20260224_002) +### 3.4) Localization runtime contract (Sprint 20260224_002) - Graph API now initializes localization via `AddStellaOpsLocalization(...)`, `AddTranslationBundle(...)`, `AddRemoteTranslationBundles()`, `UseStellaOpsLocalization()`, and `LoadTranslationsAsync()`. - Locale resolution order for API messages is deterministic: `X-Locale` header -> `Accept-Language` header -> default locale (`en-US`). diff --git a/docs/modules/graph/implementation_plan.md b/docs/modules/graph/implementation_plan.md index d674b099b..55d05c2e6 100644 --- a/docs/modules/graph/implementation_plan.md +++ b/docs/modules/graph/implementation_plan.md @@ -8,7 +8,7 @@ Provide a living plan for Graph deliverables, dependencies, and evidence. - Update this file when new scoped work is approved. ## Near-term deliverables -- TBD (add when sprint is staffed). +- `docs/implplan/SPRINT_20260416_003_Graph_graph_api_truthful_runtime_cutover.md` - completed live runtime cleanup for Graph API host fallbacks and fabricated overlay/export/edge-metadata services. ## Dependencies - `docs/modules/graph/architecture.md` @@ -22,3 +22,4 @@ Provide a living plan for Graph deliverables, dependencies, and evidence. ## Notes - Keep deterministic and offline-first expectations aligned with module AGENTS. +- `Testing` is now the only supported harness environment for in-memory Graph overlays/export/edge metadata. Non-testing hosts require Graph Postgres and return truthful `501` for unsupported live feature slices. diff --git a/docs/modules/integrations/README.md b/docs/modules/integrations/README.md index 58d0a16d3..0cffd104e 100644 --- a/docs/modules/integrations/README.md +++ b/docs/modules/integrations/README.md @@ -19,7 +19,7 @@ Integrations provides a unified API for registering, configuring, and health-che ## Key Features -- **Plugin-based connector architecture:** Extensible provider system with built-in connectors for GitHub App, GitLab, Harbor, and in-memory testing +- **Plugin-based connector architecture:** Extensible provider system with built-in connectors for GitHub App, GitLab, Harbor, and other real providers; in-memory doubles stay in explicit test harnesses - **Health checks:** Per-integration health probing with status tracking and alerting - **Credential management:** AuthRef URI scheme for vault-referenced credentials; no plaintext secrets stored in the integration catalog - **Tenant isolation:** All integrations are scoped to a tenant; cross-tenant access is prohibited diff --git a/docs/modules/integrations/architecture.md b/docs/modules/integrations/architecture.md index 6a10e40b5..94b4f882d 100644 --- a/docs/modules/integrations/architecture.md +++ b/docs/modules/integrations/architecture.md @@ -94,7 +94,7 @@ public interface IIntegrationPlugin - **GitHubApp** - GitHub App installation authentication, repository listing, webhook setup - **GitLab** - Personal/project access token authentication, project discovery - **Harbor** - Robot account authentication, project and repository enumeration -- **InMemory** - Deterministic test double for integration tests and offline development +- **InMemory** - Deterministic test double for explicit test harnesses; the default webservice runtime does not load it ### Provider endpoint contracts diff --git a/docs/modules/issuer-directory/README.md b/docs/modules/issuer-directory/README.md index c33da4aca..2c433638a 100644 --- a/docs/modules/issuer-directory/README.md +++ b/docs/modules/issuer-directory/README.md @@ -4,6 +4,12 @@ **Source:** `src/Authority/StellaOps.IssuerDirectory/` (previously `src/IssuerDirectory/`) **Owner:** Authority domain (Identity & Trust) +## Latest updates (2026-04-16) + +- IssuerDirectory web runtime no longer silently falls back to in-memory persistence outside `Testing`; non-testing hosts now require PostgreSQL wiring. +- Canonical configuration now lives under `IssuerDirectory:Persistence:*`, while legacy `IssuerDirectory:Postgres:*` settings remain supported for compatibility. +- Focused runtime coverage lives in `StellaOps.IssuerDirectory.WebService.Tests`. + ## Purpose IssuerDirectory maintains a trust registry of CSAF publishers and VEX statement issuers. Provides discovery, validation, and trust scoring for upstream vulnerability advisories and VEX statements. @@ -24,7 +30,9 @@ See `docs/modules/authority/architecture.md` (sections 21.1--21.4) for schema ow See `etc/issuer-directory.yaml.sample` for configuration options. Key settings: -- PostgreSQL connection (schema: `issuer_directory`) +- `IssuerDirectory:Persistence:Provider=Postgres` +- `IssuerDirectory:Persistence:PostgresConnectionString` +- `IssuerDirectory:Persistence:SchemaName` (defaults to `issuer`) - Authority integration settings - Issuer discovery endpoints - Trust validation policies diff --git a/docs/modules/issuer-directory/architecture.md b/docs/modules/issuer-directory/architecture.md index fcb6d9c41..d5530f0ab 100644 --- a/docs/modules/issuer-directory/architecture.md +++ b/docs/modules/issuer-directory/architecture.md @@ -17,3 +17,4 @@ The IssuerDirectory service retains its own container, hostname, and endpoints. Schema isolation from AuthorityDbContext is a deliberate security feature (see ADR in Authority architecture). +Non-testing runtime is PostgreSQL-backed; in-memory persistence is reserved for `Testing` only. diff --git a/docs/modules/jobengine/architecture.md b/docs/modules/jobengine/architecture.md index 864ea946b..2b5f5f58b 100644 --- a/docs/modules/jobengine/architecture.md +++ b/docs/modules/jobengine/architecture.md @@ -167,7 +167,12 @@ The Scheduler service re-evaluates already-cataloged images when intelligence ch **Deployables:** `StellaOps.Scheduler.WebService` (stateless API + embedded worker BackgroundServices). Worker processes run in the same host by default (`Scheduler:Worker:Embedded=true`). For K8s scale-out, set `Embedded=false` and deploy `StellaOps.Scheduler.Worker.Host` separately. -**Database:** `SchedulerDbContext` (schema `scheduler`, 11 entities). Owns `schedules`, `runs`, `impact_cursors`, `locks`, `audit` tables. See archived docs: `docs-archived/modules/scheduler/architecture.md`. +**Database:** `SchedulerDbContext` (schema `scheduler`, 11 entities). Owns `schedules`, `runs`, `impact_cursors`, `locks`, `audit`, and canonical `scheduler.jobs` queue/state rows used by the resolver-job API when `Scheduler:Storage` is configured. See archived docs: `docs-archived/modules/scheduler/architecture.md`. + +**Runtime storage notes (2026-04-15):** +- Live Scheduler hosts now resolve graph jobs, schedules, runs, run summaries, policy-run state, audit, and resolver-job state through PostgreSQL-backed services whenever `Scheduler:Storage` is present. +- Vulnerability resolver job submissions persist into `scheduler.jobs` and survive host restart; `InMemoryResolverJobService` remains a non-durable fallback only for the `Testing` environment. Live hosts fail fast when `Scheduler:Storage:ConnectionString` is absent. +- Inbound Conselier/Excitor webhook rate limiting is no longer process-local in non-testing runtime. Enabled inbound webhooks now resolve a Redis-backed distributed sliding-window limiter from the existing `scheduler:queue` transport contract; `InMemoryWebhookRateLimiter` remains `Testing`-only, and non-testing startup fails fast if inbound webhooks stay enabled without Redis queue configuration. ### 8.2) TaskRunner subdomain (REMOVED) @@ -186,8 +191,9 @@ The PacksRegistry manages compliance/automation pack definitions, versions, and - Blob/object payloads (`pack content`, `provenance content`, `attestation content`) are persisted through the seed-fs object-store channel (`SeedFsPacksRegistryBlobStore`). - Startup fails fast when `Storage:ObjectStore:Driver` is set to `rustfs` (not implemented) or any unsupported driver value. - Non-development startup fails fast when `Storage:Driver=postgres` and no connection string is configured. +- PacksRegistry persistence auto-applies embedded startup migrations on host boot; fresh databases now reconcile the legacy `packs.packs` table shape to the current repository contract before serving traffic. - PostgreSQL keeps metadata and compatibility placeholders; payload retrieval resolves from object storage first. -- Explicit non-production overrides remain available (`filesystem`, `inmemory`) but are no longer implicit defaults. +- The live host accepts only `postgres` and `filesystem` storage drivers; the old `inmemory` branch has been removed from runtime composition. --- diff --git a/docs/modules/notifier/README.md b/docs/modules/notifier/README.md index 4bab4a7c6..bc0c08bf7 100644 --- a/docs/modules/notifier/README.md +++ b/docs/modules/notifier/README.md @@ -10,6 +10,17 @@ Notifier provides the deployable WebService and Worker that compose the Notify libraries into the Notifications Studio experience. It's the entry point for notification delivery, rule management, and delivery history. +## Latest Updates + +- `2026-04-15`: production `Notifier` runtime no longer shadows the shared Notify persistence/queue stack with in-memory repositories. +- `2026-04-15`: pack-approval ingestion persists durably in `notify.pack_approvals` and uses durable `notify.locks` idempotency coordination. +- `2026-04-15`: restart-survival proof now exists via `NotifierDurableRuntimeProofTests`, covering submit -> persist -> process -> readback on real Postgres + Redis. +- `2026-04-16`: `NullNotifyEventQueue` is no longer available in `Development`; only the `Testing` environment may use the null queue fallback. +- `2026-04-16`: non-testing throttle and operator-override admin APIs now persist through PostgreSQL-backed suppression services and legacy compat adapters; restart-survival proof is covered by `NotifierSuppressionDurableRuntimeTests`. +- `2026-04-16`: non-testing escalation-policy and on-call schedule APIs now persist through PostgreSQL-backed services and legacy compat adapters; restart-survival proof is covered by `NotifierEscalationOnCallDurableRuntimeTests`. +- `2026-04-16`: non-testing quiet-hours calendars and maintenance windows now persist through PostgreSQL-backed runtime services and legacy compat adapters; restart-survival proof is covered by `NotifierQuietHoursMaintenanceDurableRuntimeTests`. +- `2026-04-16`: non-testing webhook security, tenant isolation, dead-letter administration, and retention cleanup state now persist through PostgreSQL-backed runtime services and legacy compat adapters; restart-survival proof is covered by `NotifierSecurityDeadLetterDurableRuntimeTests`. + ## Relationship to Notify | Component | Path | Purpose | diff --git a/docs/modules/notify/README.md b/docs/modules/notify/README.md index eb0ad0da5..b58f9ef86 100644 --- a/docs/modules/notify/README.md +++ b/docs/modules/notify/README.md @@ -6,6 +6,12 @@ Notify (Notifications Studio) converts platform events into tenant-scoped alerts - Sprint tracker `docs/implplan/SPRINT_322_docs_modules_notify.md` and module `TASKS.md` added to mirror status. - Observability runbook stub and Grafana placeholder added under `operations/` (offline import); finalize after next demo. - NOTIFY-DOCS-0002 remains blocked pending NOTIFY-SVC-39-001..004 outputs (correlation/digests/simulation/quiet hours). +- `2026-04-15`: Notify/Notifier production hosts now use the shared PostgreSQL + Redis-backed Notify persistence/queue path instead of live in-memory shadow registrations. +- `2026-04-15`: durable pack-approval persistence and restart-survival proof landed under sprint `SPRINT_20260415_002_DOCS_notify_notifier_real_backend_cutover.md`. +- `2026-04-16`: non-testing throttle and operator-override admin APIs now persist through PostgreSQL-backed suppression services and legacy compat adapters in both hosts; restart-survival proof landed in `NotifierSuppressionDurableRuntimeTests`. +- `2026-04-16`: non-testing escalation-policy and on-call schedule APIs now resolve through PostgreSQL-backed services plus durable legacy compat adapters in both hosts; restart-survival proof landed in `NotifierEscalationOnCallDurableRuntimeTests`. +- `2026-04-16`: non-testing quiet-hours and maintenance-window admin/runtime state now persists through PostgreSQL-backed quiet-hours calendar/evaluator services plus durable compat adapters in both hosts; restart-survival proof landed in `NotifierQuietHoursMaintenanceDurableRuntimeTests`. +- `2026-04-16`: non-testing webhook security, tenant isolation, dead-letter administration, and retention cleanup state now persist through PostgreSQL-backed runtime services plus durable compat adapters in both hosts; restart-survival proof landed in `NotifierSecurityDeadLetterDurableRuntimeTests`. ## Scope & responsibilities - Apply tenant-scoped rules to events from Scanner, Scheduler, VEX Lens, Attestor, Task Runner, and Zastava. diff --git a/docs/modules/notify/architecture.md b/docs/modules/notify/architecture.md index 02198337e..356ca7e3a 100644 --- a/docs/modules/notify/architecture.md +++ b/docs/modules/notify/architecture.md @@ -1,6 +1,11 @@ > **Scope.** Implementation‑ready architecture for **Notify** (aligned with Epic 11 – Notifications Studio): a rules‑driven, tenant‑aware notification service that consumes platform events (scan completed, report ready, rescan deltas, attestation logged, admission decisions, etc.), evaluates operator‑defined routing rules, renders **channel‑specific messages** (Slack/Teams/Email/Webhook), and delivers them **reliably** with idempotency, throttling, and digests. It is UI‑managed, auditable, and safe by default (no secrets leakage, no spam storms). * **Console frontdoor compatibility (updated 2026-03-10).** The web console reaches Notifier Studio through the gateway-owned `/api/v1/notifier/*` prefix, which translates onto the service-local `/api/v2/notify/*` surface without requiring browser calls to raw service-prefixed routes. +* **Runtime durability cutover (updated 2026-04-16).** Default `src/Notifier/*` production wiring now resolves queue and storage through the shared `StellaOps.Notify.Persistence` and `StellaOps.Notify.Queue` libraries. `NullNotifyEventQueue` is allowed only in the `Testing` environment, `notify.pack_approvals` is durable, and restart-survival proof is covered by `NotifierDurableRuntimeProofTests` against real Postgres + Redis. +* **Suppression admin durability (updated 2026-04-16).** Non-testing throttle configuration and operator override APIs no longer use live in-memory state. Both hosts now resolve canonical `/api/v2/throttles*` and `/api/v2/overrides*` plus legacy `/api/v2/notify/throttle-configs*` and `/api/v2/notify/overrides*` through PostgreSQL-backed suppression services, with restart-survival proof in `NotifierSuppressionDurableRuntimeTests`. +* **Escalation/on-call durability (updated 2026-04-16).** Non-testing escalation-policy and on-call schedule APIs no longer use live in-memory services or compat repositories. Both hosts now resolve canonical `/api/v2/escalation-policies*` and `/api/v2/oncall-schedules*` plus legacy `/api/v2/notify/escalation-policies*` and `/api/v2/notify/oncall-schedules*` through PostgreSQL-backed runtime services, with restart-survival proof in `NotifierEscalationOnCallDurableRuntimeTests`. +* **Quiet-hours/maintenance durability (updated 2026-04-16).** Non-testing quiet-hours calendars and maintenance windows no longer use live in-memory compat repositories or maintenance evaluators. Both hosts now resolve canonical `/api/v2/quiet-hours*` plus legacy `/api/v2/notify/quiet-hours*` and `/api/v2/notify/maintenance-windows*` through PostgreSQL-backed runtime services on the shared `notify.quiet_hours` and `notify.maintenance_windows` tables, with restart-survival proof in `NotifierQuietHoursMaintenanceDurableRuntimeTests`. Fixed-time daily/weekly cron expressions project truthfully into canonical schedules; more complex cron shapes are persisted for round-trip reads but remain inert until a cron-native evaluator lands. +* **Security/dead-letter durability (updated 2026-04-16).** Non-testing webhook security, tenant isolation, dead-letter administration, and retention cleanup state no longer use live in-memory services. Both hosts now resolve `/api/v2/security*`, `/api/v2/notify/dead-letter*`, `/api/v1/observability/dead-letters*`, and retention endpoints through PostgreSQL-backed runtime services on shared `notify.webhook_security_configs`, `notify.webhook_validation_nonces`, `notify.tenant_resource_owners`, `notify.cross_tenant_grants`, `notify.tenant_isolation_violations`, `notify.dead_letter_entries`, `notify.retention_policies_runtime`, and `notify.retention_cleanup_executions_runtime` tables, with restart-survival proof in `NotifierSecurityDeadLetterDurableRuntimeTests`. --- diff --git a/docs/modules/notify/pack-approvals-integration.md b/docs/modules/notify/pack-approvals-integration.md index cb7eae92f..4d27a1586 100644 --- a/docs/modules/notify/pack-approvals-integration.md +++ b/docs/modules/notify/pack-approvals-integration.md @@ -8,6 +8,14 @@ Task Runner now produces pack plans with explicit approval and policy-gate metad Deliverables feed Sprint 37 tasks (`NOTIFY-SVC-37-00x`) and unblock Task Runner sprint 43 (`TASKRUN-43-001`). +## Implementation Status (2026-04-15) + +- The current ingestion endpoint is `POST /api/v1/notify/pack-approvals` in `StellaOps.Notifier.WebService`. +- Approval state now persists durably in `notify.pack_approvals`, with ingest/ack idempotency coordinated through durable `notify.locks`. +- Pack-approval ingest writes through the shared Notify audit/repository path rather than host-local in-memory stores. +- Restart-survival proof now exists in `StellaOps.Notifier.Tests.Integration.NotifierDurableRuntimeProofTests`: it boots real Postgres + Redis, submits a pack-approval event, restarts the web host, leases/processes the queued event through the live `NotifierEventProcessor`, and reads the durable delivery back through `/api/v2/notify/deliveries`. +- The proof harness uncovered and closed a real persistence bug in `LockRepository` (`notify.locks` first-acquire semantics), which is now covered by `StellaOps.Notify.Persistence.Postgres.Tests.LockRepositoryTests`. + ## Functional Requirements ### 1. Approval Event Contract diff --git a/docs/modules/platform/platform-service.md b/docs/modules/platform/platform-service.md index 3ab0f306d..18e6427e3 100644 --- a/docs/modules/platform/platform-service.md +++ b/docs/modules/platform/platform-service.md @@ -30,7 +30,7 @@ Provide a single, deterministic aggregation layer for cross-service UX workflows - GET `/api/v1/platform/health/incidents` - GET `/api/v1/platform/health/metrics` - `GET /api/v1/platform/health/readiness` is the canonical readiness contract for both setup gating and post-boot diagnostics. -- Required setup-blocking dependencies currently include the five converged setup steps (`database`, `cache`, `migrations`, `admin-bootstrap`, `crypto-profile`) plus live `frontdoor` and `authority` probes. +- Required setup-blocking dependencies currently include the five converged required setup steps (`database`, `valkey`, `migrations`, `admin-bootstrap`, `crypto-profile`) plus live `frontdoor` and `authority` probes. - Optional post-boot dependencies are discovered from configured `STELLAOPS_*_URL` endpoints and currently include `release-orchestrator`, `policy-engine`, `scanner`, `signals`, `notify`, `scheduler`, `registry-token`, `sbomservice`, `packsregistry`, and `advisoryai`. ### Quota aggregation @@ -286,9 +286,20 @@ Current runtime behavior: - Setup secret protection key precedence is `Platform:Setup:SecretProtectionKey`, `STELLAOPS_SECRETS_ENCRYPTION_KEY`, then `STELLAOPS_BOOTSTRAP_KEY`. -- The live wizard now owns only the five control-plane steps the running - control plane can truthfully validate and converge: `database`, `cache`, - `migrations`, `admin`, and `crypto`. +- The live wizard now owns the five required control-plane steps the running + control plane can truthfully validate and converge plus one optional + advisory bootstrap step: `database`, `valkey`, `migrations`, `admin`, + `crypto`, and `sources`. +- The optional `sources` step defaults to StellaOps Mirror, supports manual + advisory/VEX source selection, and preserves truthful skip semantics so a + fresh install can clearly show advisories as off until the operator enables + them. +- The `sources` step now performs a real reachability check before it can + pass. Mirror/source connectivity failures stay on the setup step instead of + being downgraded to a background warning. +- Certificate or hostname validation failures are surfaced directly. Local + browser automation may ignore local certs, but product advisory aggregation + still requires a hostname-valid certificate. - Tenant onboarding remains outside the bootstrap wizard. Integrations, feeds, notifications, environments, agents, branding, and related repeatable operations continue on `/setup/*` and module-owned authenticated APIs. @@ -331,18 +342,18 @@ Session payloads distinguish: | Step ID | Title | Required | Depends On | Notes | |---------|-------|----------|------------|-------| | `database` | PostgreSQL Database | Yes | - | Probe verifies reachability. Apply records convergence against the current runtime connection. | -| `cache` | Cache / Valkey | Yes | - | Probe verifies cache reachability. Apply records convergence against the current runtime connection. | +| `valkey` | Valkey / Redis Setup | Yes | - | Probe verifies cache reachability. Apply records convergence against the current runtime connection. | | `migrations` | Database Migrations | Yes | `database` | Probe reports pending migration state. Apply runs the migration-admin path against the canonical `Platform` and `ReleaseOrchestrator` registry modules and re-validates convergence. | | `admin` | Admin Bootstrap | Yes | `migrations` | Probe validates bootstrap prerequisites. Apply ensures the bootstrap admin exists. | | `crypto` | Crypto Profile | Yes | `admin` | Probe validates the requested crypto profile. Apply records the converged profile selection. | +| `sources` | Advisory & VEX Sources | No | `admin` | Probe validates mirror or manual source selections. Apply persists source enablement through Concelier and schedules initial aggregation for newly enabled sources. | Legacy aliases accepted during the compatibility window: -- `valkey -> cache` - `authority -> admin` - `users -> admin` -Former tenant-onboarding steps such as `vault`, `scm`, `registry`, `sources`, -`notify`, `environments`, `agents`, `telemetry`, `llm`, and `settingsstore` +Former tenant-onboarding steps such as `vault`, `scm`, `registry`, `notify`, +`environments`, `agents`, `telemetry`, `llm`, and `settingsstore` are no longer valid setup targets. Platform returns explicit handoff guidance to the authenticated onboarding surfaces instead. @@ -373,6 +384,10 @@ to the authenticated onboarding surfaces instead. to the installation scope without an authenticated caller. - After setup is marked complete, anonymous setup session reads and mutations return `401` and the normal authenticated `platform.setup.*` policies apply. +- After setup is marked complete, an authenticated operator may still create a + new installation-scoped setup session for reconfiguration on the same + `/api/v1/setup/sessions` contract; the expected browser flow is anonymous + `401` followed by authenticated session creation after sign-in. - `Finalize` succeeds only after every required control-plane step has converged and the required readiness dependencies are not blocked. diff --git a/docs/modules/policy/architecture.md b/docs/modules/policy/architecture.md index aa300a6d1..7efb502e5 100644 --- a/docs/modules/policy/architecture.md +++ b/docs/modules/policy/architecture.md @@ -40,14 +40,10 @@ Non-goals: policy authoring UI (handled by Console), ingestion or advisory norma ### 1.2 · Simulation compatibility contract (Sprint 20260309_011) -- The Policy Gateway exposes a deterministic compatibility surface for the Console simulation history workflow while the deeper Policy Engine read models continue to evolve. -- Compatibility endpoints under `/policy` include: - - `GET /policy/simulations/history` - - `GET /policy/simulations/compare` - - `POST /policy/simulations/{simulationId}/verify` - - `PATCH /policy/simulations/{simulationId}` -- These endpoints return tenant-scoped history entries, comparison diffs, reproducibility checks, and pin state with stable field names that match the live Console contract (`resultHash`, `findingsBySeverity`, `pinned`, `matchPercentage`, `discrepancies`). -- The compatibility layer is intentionally stateful per tenant so operators can exercise history actions end to end in the live shell without client-side mocks. +- The legacy compatibility surface under `/policy` no longer fabricates tenant-scoped simulation state in live hosts. +- `StellaOps.Policy.Gateway` and the merged `StellaOps.Policy.Engine` gateway surface now return `501 problem+json` for the `/policy` simulation compatibility routes until a durable Policy Registry backend is wired. +- This explicitly covers the previous shadow-mode, simulation-history, simulation-compare, exception-compatibility, merge-preview, and batch-evaluation compatibility routes that had been backed by process-local dictionaries. +- `StellaOps.Policy.Registry` still contains in-memory store implementations for its internal test harness, but the public runtime DI helper that exposed those registrations was removed. Registry in-memory storage is now testing-only and is no longer advertised as a live host composition path. --- @@ -109,6 +105,12 @@ Key notes: - Orchestrator manages run scheduling/fairness; writes run tickets to queue, leases jobs to worker pool. - Workers evaluate policies using cached IR; join external services via tenant-scoped clients; pull immutable advisories/VEX from the raw stores; write derived overlays to PostgreSQL and optional explain bundles to blob storage. - Observability (metrics/traces/logs) integrated via OpenTelemetry (not shown). +- Canonical policy pack runtime state is durable: pack containers, revision activation state, approval history, bundle digests, signed payloads, AOC metadata, and persisted DSL source now live in PostgreSQL (`policy.packs`, `policy.pack_versions`). Runtime reload reconstructs compiled bundle documents by recompiling the persisted source text, so pack activation/evaluation survives host restart without an in-memory bundle cache. +- Canonical violation runtime state is also durable: emitted violation events, fused severities, and persisted conflict records now live in PostgreSQL (`policy.violation_events`, `policy.violation_fusion_results`, `policy.conflicts`). Restarted hosts reuse persisted event/fusion/conflict state through the `/policy/violations/*` API instead of rebuilding from an in-memory store. +- Additional canonical Policy runtime state is now durable as well: verification policies, attestation reports, risk scoring jobs, console export jobs/executions/bundles, policy-pack bundle imports, and sealed mode state persist in PostgreSQL (`policy.verification_policies`, `policy.attestation_reports`, `policy.risk_scoring_jobs`, `policy.console_export_*`, `policy.policy_pack_bundle_imports`, `policy.sealed_mode_states`) and survive host restart without in-memory recovery logic. +- The standalone Policy Gateway no longer uses a process-local outbound token cache. Client-credentials token reuse now flows through `MessagingTokenCache` backed by PostgreSQL transport tables on the Policy DSN, matching the engine's durable messaging-cache pattern. +- The merged gateway governance surface is no longer backed by process-local dictionaries. `/api/v1/governance/sealed-mode/status` and `/api/v1/governance/sealed-mode/toggle` resolve through the persisted `ISealedModeService`, `/api/v1/governance/risk-profiles*` now exposes only configuration-backed read-only projections plus stateless validation, and unsupported governance write/trust-weight/staleness/conflict/audit compatibility routes return truthful `501` responses instead of fabricated state. +- The standalone Policy Gateway also no longer fabricates governance compatibility state. Its `/api/v1/governance/*` compatibility routes now return truthful `501` responses unless a real governance backend is explicitly wired behind the host. --- @@ -379,7 +381,7 @@ Determinism guard instrumentation wraps the evaluator, rejecting access to forbi - `status`, `justification`, `status_notes`, `impact_statement`, `action_statement`. - `products` (purl) and `evidence` array referencing `reachability.json`, `sbom.cdx.json`, `runtimeFacts`. 3. **DSSE signing.** The emitter calls Signer `POST /api/v1/signer/sign/dsse` with predicate `stella.ops/vexDecision@v1`. Signer verifies PoE + scanner integrity and returns a DSSE envelope (`decision.dsse.json`). -4. **Transparency (optional).** When Rekor integration is enabled, Attestor logs the DSSE payload and returns `{uuid, logIndex, checkpoint}` which we persist next to the decision. +4. **Transparency (optional).** When Rekor integration is enabled, Attestor logs the DSSE payload and returns `{uuid, logIndex, checkpoint}` which we persist next to the decision. Hosts that do not configure Rekor anchoring must fail truthfully with an unsupported submission path; they must never fabricate a successful transparency receipt. 5. **Export.** API/CLI endpoints expose `decision.openvex.json`, `decision.dsse.json`, `rekor.txt`, and evidence metadata so Export Center + bench automation can mirror them into `bench/findings/**` as defined in the [VEX Evidence Playbook](../../benchmarks/vex-evidence-playbook.md). All payloads are immutable and include analyzer fingerprints (`scanner.native@sha256:...`, `policyEngine@sha256:...`) so replay tooling can recompute identical digests. Determinism tests cover both the OpenVEX JSON and the DSSE payload bytes. @@ -465,6 +467,7 @@ Bypass attempts are logged to `policy.gate_bypass_audit`: - Sync and async gate evaluation now materialize a tenant-scoped target snapshot in `policy.engine_snapshots` before delta computation. The runtime derives that target snapshot from the latest persisted `policy.engine_ledger_exports` document (or the baseline snapshot's export), stamps the artifact digest/repository/tag onto the snapshot row, and then passes the real snapshot identifier into `DeltaComputer`. - `IOrchestratorJobStore` and `IWorkerResultStore` now resolve to persisted adapters over `policy.orchestrator_jobs` and `policy.worker_results`, so Policy export/bootstrap logic survives host recreates instead of depending on process-local completed-job state. - Direct `/policy/orchestrator/jobs` submissions now use a real producer runtime. `OrchestratorJobService.SubmitAsync` signals `PolicyOrchestratorJobWorkerHost`, the host leases the next queued job from `IOrchestratorJobStore`, marks it `running`, executes `PolicyWorkerService`, persists `policy.worker_results`, and records terminal `completed` or `failed` state instead of requiring a separate manual `/policy/worker/run` call. +- `IReachabilityFactsOverlayCache` now resolves to `MessagingReachabilityFactsOverlayCache` backed by the PostgreSQL messaging transport on the same Policy DSN. Cache rows survive host restart in transport-owned `messaging.cache_*` tables, and the live invalidation path now uses normalized logical keys instead of the old double-prefixed `rf:rf:*` shape that broke tenant invalidation after cutover. - The deterministic `/api/policy/eval/batch` surface remains stateless by contract. It returns evaluation results to callers but does not populate `policy.orchestrator_jobs` or `policy.worker_results`. - When a gate request omits an explicit baseline reference and the tenant has no persisted baseline snapshot yet, the engine now auto-builds the first ledger export from completed persisted Policy results and auto-creates a baseline snapshot before materializing the target snapshot. Explicit baseline references remain strict: if the caller asks for a missing snapshot ID, the runtime fails instead of inventing one. - `StellaOps.Policy.Persistence` now applies startup migrations for the Policy schema on `policy-engine` boot, and `001_initial_schema.sql` is idempotent on reused local volumes so snapshot/export runtime convergence does not depend on a fresh database. @@ -868,7 +871,7 @@ stella exception status ### Governance Compatibility Endpoints -The console governance workspaces also depend on a tenant-scoped compatibility surface under `/api/v1/governance/*` that lives in the Policy gateway. +The console governance workspaces still target a tenant-scoped compatibility surface under `/api/v1/governance/*`, but that surface is now explicitly split between persisted read paths and truthful unsupported behavior. - `GET /api/v1/governance/trust-weights` - `PUT /api/v1/governance/trust-weights/{weightId}` @@ -884,10 +887,15 @@ The console governance workspaces also depend on a tenant-scoped compatibility s Contract requirements: - All requests are tenant-scoped and may include an optional `projectId`. - Console clients must resolve live tenant scope from the active session/context and must not rely on legacy placeholder aliases. -- Conflict dashboard/list responses remain deterministic so scratch rebuilds and replayed Playwright sweeps see stable cards, trend buckets, and action affordances. +- The merged `StellaOps.Policy.Engine` host serves sealed-mode status/toggle from persisted sealed-mode state and serves `risk-profiles` as configuration-backed read-only data. +- Compatibility routes without a durable backend (`trust-weights`, `staleness`, `conflicts`, governance audit/history, risk-budget dashboards, sealed-mode overrides, and risk-profile mutation/lifecycle actions) must return truthful `501 Not Implemented` responses rather than fabricate tenant-local state. +- The standalone `StellaOps.Policy.Gateway` host does not own governance state and therefore returns truthful `501` responses across this compatibility surface unless a future sprint wires a real backend. Implementation reference: +- `src/Policy/StellaOps.Policy.Engine/Endpoints/Gateway/GovernanceEndpoints.cs` +- `src/Policy/StellaOps.Policy.Engine/Endpoints/Gateway/GovernanceCompatibilityEndpoints.cs` - `src/Policy/StellaOps.Policy.Gateway/Endpoints/GovernanceCompatibilityEndpoints.cs` +- `src/Policy/StellaOps.Policy.Gateway/Endpoints/GovernanceEndpoints.cs` --- diff --git a/docs/modules/policy/contracts/reachability-input-contract.md b/docs/modules/policy/contracts/reachability-input-contract.md index f862ad13b..7992fc30d 100644 --- a/docs/modules/policy/contracts/reachability-input-contract.md +++ b/docs/modules/policy/contracts/reachability-input-contract.md @@ -287,6 +287,8 @@ public interface IReachabilityFactsJoiningService } ``` +The live Policy Engine host now resolves `IReachabilityFactsStore` through `SignalsBackedReachabilityFactsStore`, with the Signals client bound from the `ReachabilitySignals` configuration section. The overlay cache remains a local transport-pluggable cache in this slice. + ### 5.2 SPL Predicates Reachability is exposed in SPL (StellaOps Policy Language) via the `reachability` scope: diff --git a/docs/modules/reach-graph/architecture.md b/docs/modules/reach-graph/architecture.md index 7040643bd..653da0d2b 100644 --- a/docs/modules/reach-graph/architecture.md +++ b/docs/modules/reach-graph/architecture.md @@ -248,11 +248,11 @@ The unified query interface is backed by two adapters: - Uses BFS from entrypoints to target symbols - Returns `StaticReachabilityResult` with distance, path, and evidence URIs -2. **InMemorySignalsAdapter**: Implements `ISignalsAdapter` from `StellaOps.Reachability.Core` - - Queries runtime observation facts - - Supports observation window filtering - - Returns `RuntimeReachabilityResult` with hit count, contexts, and evidence URIs - - Note: Production deployments should integrate with the actual Signals runtime service +2. **SignalsHttpAdapter**: Implements `ISignalsAdapter` from `StellaOps.Reachability.Core` + - Queries runtime observation facts from the live Signals service over HTTP + - Resolves the base URL from `Signals:BaseUrl`, `STELLAOPS_SIGNALS_URL`, or the local `signals.stella-ops.local` alias + - Pulls `GET /signals/facts/{subjectKey}` and translates the stored reachability fact into `RuntimeReachabilityResult` + - The in-memory adapter remains test-only and is no longer wired into the live host ### Hybrid Query Flow diff --git a/docs/modules/registry/architecture.md b/docs/modules/registry/architecture.md index 3fb4905da..df4dceaf2 100644 --- a/docs/modules/registry/architecture.md +++ b/docs/modules/registry/architecture.md @@ -7,7 +7,7 @@ It is designed for offline/self-hosted operation and enforces plan/licence const The service is intentionally small: - One HTTP endpoint: `GET /token` -- Stateless authorization decisions based on (a) Authority-issued identity token claims and (b) local configuration +- Authorization decisions based on (a) Authority-issued identity token claims and (b) either persisted or static plan rules ## Primary responsibilities @@ -35,6 +35,12 @@ The service is intentionally small: - Licence revocation uses `stellaops:license` claim and configured `RevokedLicenses`. - Plan rules match repositories by wildcard pattern (`*`) and validate requested actions (`pull`, `push`, etc.) as a subset of allowed actions. +**Plan administration storage** +- The admin `IPlanRuleStore` is backed by PostgreSQL when `RegistryTokenService:Postgres:ConnectionString` is configured. +- Startup migrations run automatically on host startup for the registry-token schema. +- The in-memory store is restricted to `Testing` hosts only; live runtime composition requires the durable backend. +- The persistence schema stores plan rules plus audit history so plan CRUD, audit endpoints, and `/token` authorization survive process restarts. + **Token issuer** - Tokens are signed with an RSA private key loaded from `RegistryTokenService:Signing:KeyPath` (PEM or PFX). - `aud` defaults to the requested registry `service` value unless `Signing:Audience` is configured. @@ -81,6 +87,9 @@ Key sections are defined by `RegistryTokenServiceOptions`: - `Registry` (realm, allow-listed `service` values) - `Plans`, `DefaultPlan`, `RevokedLicenses` +Durable plan-rule persistence is configured separately under `RegistryTokenService:Postgres`. +When Postgres persistence is configured, the host may start without any statically configured `Plans`; persisted plan rules become the canonical source for admin CRUD and token issuance. + ## References - Operations/runbook: `docs/modules/registry/operations/token-service.md` diff --git a/docs/modules/replay/architecture.md b/docs/modules/replay/architecture.md index d508c1b44..1a8e2c2d0 100644 --- a/docs/modules/replay/architecture.md +++ b/docs/modules/replay/architecture.md @@ -261,7 +261,7 @@ Replay now follows the platform storage split used by Scanner: * `Storage:Driver=postgres` (default) for snapshot index/state (`replay.feed_snapshot_index`). * `Storage:ObjectStore:Driver=seed-fs` for snapshot blob payloads (`SeedFsFeedSnapshotBlobStore`). -* `inmemory` remains available only for explicit non-production/testing profiles. +* `inmemory` remains available only for explicit `Testing` profiles; `Development` no longer auto-falls back when PostgreSQL settings are missing. * `Storage:ObjectStore:Driver=rustfs` is explicitly rejected at startup; current runtime contract supports `seed-fs` only for blob storage. Verification evidence: diff --git a/docs/modules/router/architecture.md b/docs/modules/router/architecture.md index 5500a0c81..8766cf297 100644 --- a/docs/modules/router/architecture.md +++ b/docs/modules/router/architecture.md @@ -315,6 +315,7 @@ Request ─►│ ForwardedHeaders │ - route must be configured with `PreserveAuthHeaders=true`, and - route prefix must also be in the approved passthrough allow-list configured under `Gateway:Auth:ApprovedAuthPassthroughPrefixes`. - local frontdoor configs approve `/connect`, `/console`, `/authority`, `/doctor`, `/api`, `/policy/shadow`, and `/policy/simulations` so live policy compatibility endpoints can preserve DPoP/JWT passthrough without broadening unrelated routes. +- Gateway DPoP replay protection is durable in non-testing runtime: `StellaOps.Gateway.WebService` resolves `IDpopReplayCache` through messaging idempotency (`valkey` in compose, `postgres` when configured) and only permits `InMemoryDpopReplayCache` under the explicit `Testing` environment. When `Gateway:Auth:DpopEnabled=true` and no durable messaging idempotency backend is configured, the host fails fast instead of silently falling back to process-local replay state. - Tenant override attempts are logged with deterministic fields including route, actor, requested tenant, and resolved tenant. ### Connection State diff --git a/docs/modules/sbom-service/architecture.md b/docs/modules/sbom-service/architecture.md index 1e8dcf85d..489a4343c 100644 --- a/docs/modules/sbom-service/architecture.md +++ b/docs/modules/sbom-service/architecture.md @@ -1,176 +1,103 @@ -# SBOM Service architecture (2025Q4) +# SBOM Service Architecture -> Scope: canonical SBOM projections, lookup and timeline APIs, asset metadata overlays, and events feeding Advisory AI, Console, Graph, Policy, and Vuln Explorer. +> Scope: canonical SBOM projections, ledger history, lineage, registry-source state, and durable SBOM event backlogs consumed by Console, Policy, Concelier, Graph, and Findings. -## 1) Mission & boundaries -- Mission: serve deterministic, tenant-scoped SBOM projections (Link-Not-Merge v1) and related metadata for downstream reasoning and overlays. -- Boundaries: - - Does not perform scanning; consumes Scanner outputs or supplied SPDX/CycloneDX blobs. - - Does not author verdicts/policy; supplies evidence and projections to Policy/Concelier/Graph. - - Append-only SBOM versions; mutations happen via new versions, never in-place edits. - - Owns the SBOM lineage ledger for versioned uploads, diffs, and retention pruning. +## 1) Mission and boundaries +- Serve deterministic, tenant-scoped SBOM projections and ledger history. +- Accept scanner-produced SBOMs or operator-supplied SPDX/CycloneDX uploads. +- Own append-only SBOM versioning, lineage edges, retention, and related audit state. +- Do not perform scanning itself and do not author policy verdicts. -## 2) Project layout -- `src/SbomService/StellaOps.SbomService` — REST API + event emitters + orchestrator integration. -- Storage: PostgreSQL tables (proposed) - - `sbom_snapshots` (immutable versions; tenant + artifact + digest + createdAt) - - `sbom_projections` (materialised views keyed by snapshotId, entrypoint/service node flags) - - `sbom_assets` (asset metadata, criticality/owner/env/exposure; append-only history) - - `sbom_catalog` (console catalog surface; indexed by artifact, scope, license, assetTags.*, createdAt for deterministic pagination) - - `sbom_component_neighbors` (component lookup graph edges; indexed by purl+artifact for cursor pagination) -- `sbom_paths` (resolved dependency paths with runtime flags, blast-radius hints) -- `sbom_events` (outbox for event delivery + watermark/backfill tracking) +## 2) Runtime layout +- Host: `src/SbomService/StellaOps.SbomService` +- Persistence: `src/SbomService/__Libraries/StellaOps.SbomService.Persistence` +- Tests: `src/SbomService/__Tests/StellaOps.SbomService.Persistence.Tests` -### 2.1) SBOM + provenance spine (Nov 2026) +### 2.1 Durable state +- Canonical PostgreSQL tables now include: + - `sbom.projections`, `sbom.catalog`, `sbom.component_neighbors` + - `sbom.entrypoints`, `sbom.orchestrator_sources`, `sbom.orchestrator_control` + - `sbom.ledger_versions`, `sbom.ledger_audit`, `sbom.analysis_jobs`, `sbom.watermarks` + - `sbom.lineage_edges` + - `sbom.version_events`, `sbom.asset_events`, `sbom.inventory_events`, `sbom.resolver_candidates` + - `sbom.registry_sources`, `sbom.registry_source_runs` +- Startup migrations are embedded in the persistence assembly and run automatically on host startup. -The service now owns an idempotent spine that converts OCI images into SBOMs and provenance bundles with DSSE and in-toto. The flow is intentionally air-gap ready: +### 2.2 Truthful unsupported surface +- The live durable runtime no longer fabricates dependency-path data from in-memory seeds. +- Until a durable dependency-path backend exists: + - `GET /sbom/paths` returns `501 sbom_paths_unsupported` + - dependency-path portions of `GET /sbom/context` return `501 sbom_paths_unsupported` + - `POST /internal/sbom/inventory/backfill` returns `501 sbom_inventory_backfill_unsupported` + - `POST /internal/sbom/resolver-feed/backfill` returns `501 sbom_resolver_feed_backfill_unsupported` -- **Extract** OCI manifest/layers (hash becomes `contentAddress`). -- **Build SBOM** in CycloneDX 1.7 and/or SPDX 3.0.1; canonicalize JSON before hashing (`sbomHash`). -- **Sign** outputs as DSSE envelopes; predicate uses in-toto Statement with SLSA Provenance v1. -- **Publish** attestations optionally to a transparency backend: `rekor`, `local-merkle`, or `null` (no-op). Local Merkle log keeps proofs for later sync when online. +## 3) Primary APIs +- `POST /sbom/upload` validates and normalizes SPDX 2.3/3.0 or CycloneDX 1.4-1.7 and appends a new ledger version. +- `GET /sbom/versions?artifact=...` returns the durable timeline for an artifact. +- `GET /sbom/ledger/history`, `/point`, `/range`, `/diff`, `/lineage` expose ledger and lineage state. +- `GET /console/sboms` reads the durable catalog view. +- `GET /components/lookup?purl=...` reads the durable component-neighbor view. +- `POST /entrypoints` and `GET /entrypoints` manage tenant-scoped entrypoint overrides. +- `GET /sboms/{snapshotId}/projection` returns the stored Link-Not-Merge projection. -Minimal APIs exposed by SbomService (idempotent by hash): +## 4) Internal APIs +- `GET /internal/sbom/events` returns durable `sbom.version.created` backlog state. +- `POST /internal/sbom/events/backfill` replays stored projections into the durable event backlog. +- `GET /internal/sbom/asset-events` returns durable `sbom.asset.updated` backlog state. +- `GET /internal/jobengine/sources` and `POST /internal/jobengine/sources` manage ingest sources. +- `GET /internal/jobengine/control` and `POST /internal/jobengine/control` manage pause/throttle/backpressure state. +- `GET /internal/jobengine/watermarks` and `POST /internal/jobengine/watermarks` manage durable watermarks. +- `GET /internal/sbom/resolver-feed` and `GET /internal/sbom/resolver-feed/export` expose the persisted resolver candidate store. +- `GET /internal/sbom/ledger/audit` and `GET /internal/sbom/analysis/jobs` expose durable ledger audit and analysis job history. +- `POST /internal/sbom/retention/prune` applies durable retention policy. -- `POST /sbom/ingest` `{ imageDigest, sbom, format, dsseSignature? }` → `{ sbomId, status: stored|already_present, sbomHash }` keyed by `contentAddress + sbomHash`. -- `POST /attest/verify` `{ dsseEnvelope, expectedSubjects[] }` → `{ verified, predicateType, logIndex?, inclusionProof? }` and records attestation when verified. +## 5) Ingestion and ledger behavior +- Uploads are normalized into deterministic component lists before persistence. +- Each artifact keeps an append-only chain of `SbomLedgerVersion` records. +- Audit entries are written for ledger mutations. +- Analysis jobs are persisted immediately when upload-triggered work is queued. +- Lineage edges are persisted for parent, base-image, and shared-build relationships. +- Retention pruning removes old versions while preserving auditability. -Operational rules: +## 6) Events and derived state +- Durable event backlogs are stored in: + - `sbom.version_events` + - `sbom.asset_events` + - `sbom.inventory_events` + - `sbom.resolver_candidates` +- `sbom.version.created` and `sbom.asset.updated` are canonical durable outputs today. +- Inventory and resolver feeds remain durable stores, but automatic rebuild of those feeds is blocked on the missing durable dependency-path backend. -- Default media types: `application/vnd.cyclonedx+json`, `application/spdx+json`, `application/dsse+json`, `application/vnd.in-toto+json`. -- If the same SBOM/attestation arrives again, return HTTP 200 with `"status":"already_present"` and do not create a new version. -- Offline posture: no external calls required; Rekor publish remains optional and retryable when connectivity is restored. +## 7) Registry source management +- Registry source definitions and run history are durable PostgreSQL state. +- `RegistrySource` stores URL, filters, trigger mode, auth references, and tenant ownership. +- `RegistrySourceRun` stores discovery/scan counts, trigger metadata, and completion state. -## 3) APIs (first wave) -- `GET /sbom/paths?purl=...&artifact=...&scope=...&env=...` — returns ordered paths with runtime_flag/blast_radius and nearest-safe-version hint; supports `cursor` pagination. -- `GET /sbom/versions?artifact=...` – time-ordered SBOM version timeline for Advisory AI; include provenance and source bundle hash. -- `POST /sbom/upload` – BYOS upload endpoint; validates/normalizes SPDX 2.3/3.0.1 or CycloneDX 1.4–1.7 and registers a ledger version. -- `GET /sbom/ledger/history` – list version history for an artifact (cursor pagination). -- `GET /sbom/ledger/point` – resolve the SBOM version at a specific timestamp. -- `GET /sbom/ledger/range` – query versions within a time range. -- `GET /sbom/ledger/diff` – component/version/license diff between two versions. -- `GET /sbom/ledger/lineage` – parent/child lineage edges for an artifact chain. -- `GET /api/v1/lineage/compare?a=...&b=...&tenant=...` – canonical release-investigation comparison endpoint returning normalized component, VEX, and reachability deltas for deploy-diff. -- `POST /api/change-traces/build` – compatibility endpoint that materializes a release-investigation change trace from `fromDigest`, `toDigest`, and tenant context. -- `GET /api/change-traces/{traceId}` – stateless compatibility read endpoint; rehydrates the change trace from an encoded trace id and the current lineage compare result. -- `GET /console/sboms` – Console catalog with filters (artifact, license, scope, asset tags), cursor pagination, evaluation metadata, immutable JSON projection for drawer views. -- `GET /components/lookup?purl=...` – component neighborhood for global search/Graph overlays; returns caches hints + tenant enforcement. -- `POST /entrypoints` / `GET /entrypoints` – manage entrypoint/service node overrides feeding Cartographer relevance; deterministic defaults when unset. -- `GET /sboms/{snapshotId}/projection` – Link-Not-Merge v1 projection returning hashes plus asset metadata (criticality, owner, environment, exposure flags, tags) alongside package/component graph. -- `GET /internal/sbom/events` — internal diagnostics endpoint returning the in-memory event outbox for validation. -- `POST /internal/sbom/events/backfill` — replays existing projections into the event stream; deterministic ordering, clock abstraction for tests. -- `GET /internal/sbom/asset-events` — diagnostics endpoint returning emitted `sbom.asset.updated` envelopes for validation and air-gap parity checks. -- `GET/POST /internal/jobengine/sources` — list/register orchestrator ingest/index sources (deterministic seeds; idempotent on artifactDigest+sourceType). -- `GET/POST /internal/jobengine/control` — manage pause/throttle/backpressure signals per tenant; metrics emitted for control updates. -- `GET/POST /internal/jobengine/watermarks` — fetch/set backfill watermarks for reconciliation and deterministic replays. -- `GET /internal/sbom/resolver-feed` – list resolver candidates (artifact, purl, version, paths, scope, runtime_flag, nearest_safe_version). -- `POST /internal/sbom/resolver-feed/backfill` – clear and repopulate resolver feed from current projections. -- `GET /internal/sbom/resolver-feed/export` – NDJSON export of resolver candidates for air-gap delivery. -- `GET /internal/sbom/ledger/audit` – audit trail for ledger changes (created/pruned). -- `GET /internal/sbom/analysis/jobs` – list analysis jobs triggered by BYOS uploads. -- `POST /internal/sbom/retention/prune` – apply retention policy and emit audit entries. +## 8) Determinism and offline posture +- Stable ordering, UTC timestamps, and canonical hashing are required for all read models and ledger state. +- The service remains offline-friendly: no external calls are required for persistence or replay. +- `Testing` is the only supported environment for fixture-backed or in-memory fallback runtime composition when PostgreSQL is intentionally absent. +- Development, staging, and production runtimes now fail startup if `SbomService:PostgreSQL` is not configured. -## 3.1) Ledger + BYOS workflow (Sprint 4600) -- Uploads are validated, normalized, and stored as ledger versions chained per artifact identity. -- Diffs compare normalized component keys and surface version/license deltas with deterministic ordering. -- Lineage is derived from parent version references and emitted for Graph lineage edges. -- Lineage relationships include parent links plus build links (shared CI build IDs when provided). -- Retention policy prunes old versions while preserving audit entries and minimum keep counts. -- See `docs/modules/sbomservice/ledger-lineage.md` for request/response examples. -- See `docs/modules/sbomservice/byos-ingestion.md` for supported formats and troubleshooting. +## 9) Configuration +- `SbomService:PostgreSQL:ConnectionString` enables the live durable runtime. +- `SbomService:PostgreSQL:Schema` optionally overrides the default schema `sbom`. +- `SbomService:Ledger:*` controls retention: + - `MaxVersionsPerArtifact` + - `MaxAgeDays` + - `MinVersionsToKeep` +- `SbomService:RegistryHttp`, `SbomService:ScannerHttp`, and `SbomService:RegistrySourceQuery` configure registry discovery and scan emission behavior. -## 4) Ingestion & orchestrator integration -- Ingest sources: Scanner pipeline (preferred) or uploaded SPDX 2.3/3.0.1 and CycloneDX 1.4–1.7 bundles. -- Orchestrator: register SBOM ingest/index jobs; worker SDK emits artifact hash + job metadata; honor pause/throttle; report backpressure metrics; support watermark-based backfill for idempotent replays. -- Idempotency: combine `(tenant, artifactDigest, sbomVersion)` as primary key; duplicate ingests short-circuit. +## 10) Proof status +- Durable host build: `StellaOps.SbomService` builds cleanly with PostgreSQL-backed canonical stores. +- Durable persistence proof: `StellaOps.SbomService.Persistence.Tests` covers ledger, event store, registry-source persistence, and repository-backed timeline rebuild across service instances. -## 5) Events & streaming -- `sbom.version.created` — emitted per new SBOM snapshot; payload: tenant, artifact digest, sbomVersion, projection hash, source bundle hash, import provenance; replay/backfill via outbox with watermark. -- `sbom.asset.updated` — emitted when asset metadata changes; idempotent payload keyed by `(tenant, assetId, version)`. -- Inventory/resolver feeds — queue/topic delivering `(artifact, purl, version, paths, runtime_flag, scope, nearest_safe_version)` for Vuln Explorer/Findings Ledger. - - Current implementation uses an in-memory event store/publisher (with clock abstraction) plus `/internal/sbom/events` + `/internal/sbom/events/backfill` to validate envelopes until the PostgreSQL-backed outbox is wired. - - Entrypoint/service node overrides are exposed via `/entrypoints` (tenant-scoped) and should be mirrored into Cartographer relevance jobs when the outbox lands. +## 11) Open gaps +- Add a durable dependency-path backend so `/sbom/paths`, dependency-path context assembly, and inventory/resolver feed backfill can leave truthful `501` mode. +- Normalize the shared contract surface further if the persistence assembly keeps absorbing host-owned types. -## 5.1) Release Investigation Compatibility -- The Releases workspace consumes lineage compare as the source of truth for A/B deploy comparison. -- `/api/change-traces/*` exists as a compatibility layer for the web change-trace viewer and gateway routing. It does not persist trace documents; trace ids encode the tenant, digest pair, and byte-diff mode, and the service deterministically rebuilds the document on read. -- When no lineage comparison exists for the selected digests, the service returns `404` so the web workspace can surface an explicit recovery state instead of pretending data exists. - -## 6) Determinism & offline posture -- Stable ordering for projections and paths; timestamps in UTC ISO-8601; hash inputs canonicalised. -- Add-only evolution for schemas; LNM v1 fixtures published alongside API docs and replayable tests. -- Offline-friendly: uses mirrored packages, avoids external calls during projection; exports NDJSON bundles for air-gapped replay. - -## 7) Tenancy & security -- All APIs require tenant context (token claims or mTLS binding); collection filters must include tenant keys. -- Enforce least-privilege queries; avoid cross-tenant caches; log tenant IDs in structured logs. -- Input validation: schema-validate incoming SBOMs; reject oversized/unsupported media types early. - -## 8) Observability -- Metrics: `sbom_projection_seconds`, `sbom_projection_size_bytes`, `sbom_projection_queries_total`, `sbom_paths_latency_seconds`, `sbom_paths_cache_hit_ratio`, `sbom_events_backlog`, `sbom_ledger_uploads_total`, `sbom_ledger_diffs_total`, `sbom_ledger_retention_pruned_total`. -- Tracing: ActivitySource `StellaOps.SbomService` (entrypoints, component lookup, console catalog, projections, events). -- Traces: wrap ingest, projection build, and API handlers; propagate orchestrator job IDs. -- Logs: structured, include tenant + artifact digest + sbomVersion; classify ingest failures (schema, storage, orchestrator, validation). -- Alerts: backlog thresholds for outbox/event delivery; high latency on path/timeline endpoints. - -## 8.1) Registry Source Management (Sprint 012) - -The service manages container registry sources for automated image discovery and scanning: - -### Models -- `RegistrySource` — registry connection with URL, filters, schedule, credentials (via AuthRef). -- `RegistrySourceRun` — run history with status, discovered images, triggered scans, error details. -- `RegistrySourceStatus` — `Draft`, `Active`, `Paused`, `Error`, `Deleted`. -- `RegistrySourceProvider` — `Generic`, `Harbor`, `DockerHub`, `ACR`, `ECR`, `GCR`, `GHCR`. - -### APIs -- `GET/POST/PUT/DELETE /api/v1/registry-sources` — CRUD operations. -- `POST /api/v1/registry-sources/{id}/test` — test registry connection and credentials. -- `POST /api/v1/registry-sources/{id}/trigger` — manually trigger discovery and scanning. -- `POST /api/v1/registry-sources/{id}/pause` / `/resume` — pause/resume scheduled scans. -- `GET /api/v1/registry-sources/{id}/runs` — run history with health metrics. -- `GET /api/v1/registry-sources/{id}/discover/repositories` — discover repositories matching filters. -- `GET /api/v1/registry-sources/{id}/discover/tags/{repository}` — discover tags for a repository. -- `GET /api/v1/registry-sources/{id}/discover/images` — full image discovery. -- `POST /api/v1/registry-sources/{id}/discover-and-scan` — discover and submit scan jobs. - -### Webhook Ingestion -- `POST /api/v1/webhooks/registry/{sourceId}` — receive push notifications from registries. -- Supported providers: Harbor, DockerHub, ACR, ECR, GCR, GHCR. -- HMAC-SHA256 signature validation using webhook secret from AuthRef. -- Auto-detection of provider from request headers. - -### Discovery Service -- OCI Distribution Spec compliant repository/tag enumeration. -- Pagination via RFC 5988 Link headers. -- Allowlist/denylist filtering for repositories and tags (glob patterns). -- Manifest digest retrieval via HEAD requests. - -### Scan Job Emission -- Batch submission to Scanner service with rate limiting. -- Deduplication (skips if job already exists). -- Metadata includes source ID, trigger type, client request ID. - -### Configuration -- `SbomService:ScannerUrl` — Scanner service endpoint (default: `http://localhost:5100`). -- `SbomService:BatchScanSize` — max images per batch (default: 10). -- `SbomService:BatchScanDelayMs` — delay between batch submissions (default: 100ms). - -### Credentials -- All credentials via AuthRef URIs: `authref://{vault}/{path}#{key}`. -- Supports basic auth (`basic:user:pass`) and bearer tokens (`bearer:token`) for development. - -## 9) Configuration (PostgreSQL-backed catalog & lookup) -- Enable PostgreSQL storage for `/console/sboms` and `/components/lookup` by setting `SbomService:PostgreSQL:ConnectionString` (env: `SBOM_SbomService__PostgreSQL__ConnectionString`). -- Optional overrides: `SbomService:PostgreSQL:Schema`, `SbomService:PostgreSQL:CatalogTable`, `SbomService:PostgreSQL:ComponentLookupTable`; defaults are `sbom_service`, `sbom_catalog`, `sbom_component_neighbors`. -- When the connection string is absent the service falls back to fixture JSON or deterministic in-memory seeds to keep air-gapped workflows alive. -- Ledger retention settings (env prefix `SBOM_SbomService__Ledger__`): `MaxVersionsPerArtifact`, `MaxAgeDays`, `MinVersionsToKeep`. - -## 10) Open questions / dependencies -- Confirm orchestrator pause/backfill contract (shared with Runtime & Signals 140-series). -- Finalise storage table names and indexes (compound on tenant+artifactDigest+version, TTL for transient staging). -- Publish canonical LNM v1 fixtures and JSON schemas for projections and asset metadata. - -- See `docs/modules/sbomservice/api/projection-read.md` for `/sboms/{snapshotId}/projection` (LNM v1, tenant-scoped, hash-returning). -- See `docs/modules/sbomservice/lineage-ledger.md` for ledger endpoints and lineage relationships. -- See `docs/modules/sbomservice/retention-policy.md` for retention configuration and audit expectations. +## 12) Related docs +- `docs/modules/sbomservice/ledger-lineage.md` +- `docs/modules/sbomservice/byos-ingestion.md` +- `docs/modules/sbomservice/retention-policy.md` +- `docs/modules/sbomservice/api/projection-read.md` diff --git a/docs/modules/scanner/architecture.md b/docs/modules/scanner/architecture.md index 716d91bff..69518253e 100644 --- a/docs/modules/scanner/architecture.md +++ b/docs/modules/scanner/architecture.md @@ -236,6 +236,8 @@ See docs/modules/scanner/byos-ingestion.md for BYOS workflow, formats, and troub - `GET /api/v1/scans/{id}/manifest` - `GET /api/v1/scans/{id}/proofs` - `GET /api/v1/scans/{id}/proofs/{rootHash}` +- SBOM upload records are durable runtime state and are stored in PostgreSQL table `scanner.sbom_uploads`; the WebService must not rebind this path to an in-memory upload store. +- Policy snapshot and policy audit state are durable runtime state and are stored through the Policy PostgreSQL backend; the WebService must not rebind these paths to in-memory policy repositories. - The live manifest/proof and score-replay retrieval path is backed by PostgreSQL tables `scanner.scan_manifest` and `scanner.proof_bundle`. - `StellaOps.Scanner.WebService` must not bind these runtime paths to `InMemoryScanManifestRepository`, `InMemoryProofBundleRepository`, `TestManifestRepository`, or `TestProofBundleRepository`. - When singleton replay services need manifest/proof access, they must resolve the scoped PostgreSQL repositories through an adapter rather than bypassing persisted storage. @@ -408,6 +410,8 @@ When an SBOM path is provided, the worker runs the `service-security` stage to p Inputs are passed via scan metadata (`sbom.path` or `sbomPath`, plus `sbom.format`). The report is attached as a surface observation payload (`service-security.report`) and keyed in the analysis store for downstream policy and report assembly. See `src/Scanner/docs/service-security.md` for the policy schema and output formats. +Runtime reconciliation and downstream policy helpers now resolve scan metadata from persisted scan manifests rather than a standalone in-memory metadata store. This keeps metadata derivation restart-safe as long as the canonical manifest record exists. + ### 5.5.2 CBOM crypto analysis (Sprint 20260119_017) When an SBOM includes CycloneDX `cryptoProperties`, the worker runs the `crypto-analysis` stage to produce a crypto inventory and compliance findings for weak algorithms, short keys, deprecated protocol versions, certificate hygiene, and post-quantum readiness. The report is attached as a surface observation payload (`crypto-analysis.report`) and keyed in the analysis store for downstream evidence workflows. See `src/Scanner/docs/crypto-analysis.md` for the policy schema and inventory export formats. @@ -439,6 +443,8 @@ Scanner now exposes a deterministic VEX+reachability matrix filter for triage pr - API surface: `POST /api/v1/scans/vex-reachability/filter` accepts finding batches and returns annotated decisions plus action summary counts. - Determinism: batch order is preserved, rule IDs are explicit, and no network lookups are required for matrix evaluation. +The read-side VEX gate endpoints (`GET /api/v1/scans/{scanId}/gate-results`, `/gate-summary`, and `/blocked-findings`) no longer bind to a canonical in-memory store in live runtime. Until Scanner grows a durable VEX gate results backend, those endpoints return `501 Not Implemented` with `error = "vex_gate_results_unsupported"` instead of inventing transient state. + ### 5.5.7 Vulnerability-first triage clustering APIs (Sprint 20260208_063) Scanner triage now includes deterministic exploit-path clustering primitives for vulnerability-first triage workflows: @@ -694,6 +700,7 @@ ResolveEntrypoint(ImageConfig cfg, RootFs fs): - Reachability graphs/traces are folded into the manifest via `ReachabilityReplayWriter`; manifests and bundles hash with stable ordering for replay verification (`docs/replay/DETERMINISTIC_REPLAY.md`). - Worker sealed-mode intake reads `replay.bundle.uri` + `replay.bundle.sha256` (plus determinism feed/policy pins) from job metadata, persists bundle refs in analysis and surface manifest, and validates hashes before use. - Deterministic execution switches (`docs/modules/scanner/deterministic-execution.md`) must be enabled when generating replay bundles to keep hashes stable. +- Canonical scan submission/runtime state is durable in PostgreSQL rather than process-local memory: the live host persists scan status, replay bundle attachment, and entropy snapshots in `scanner.scan_runtime_state`, and `PersistedScanCoordinator` is now the authoritative runtime coordinator in `StellaOps.Scanner.WebService`. EntryTrace emits structured diagnostics and metrics so operators can quickly understand why resolution succeeded or degraded: diff --git a/docs/modules/signer/README.md b/docs/modules/signer/README.md index f6d87547d..00fdb97fd 100644 --- a/docs/modules/signer/README.md +++ b/docs/modules/signer/README.md @@ -10,10 +10,11 @@ - Docker image: `stellaops/signer:dev` - API base path: `/api/v1/signer/` - DSSE signing endpoint: `POST /api/v1/signer/sign/dsse` -- Database schemas: `signer`, `key_management` (isolated from Attestor schema by design) +- Database schema: `signer` (includes ceremony state plus key-management tables such as `trust_anchors`, `key_history`, and `key_audit_log`) +- Authentication: Authority resource-server auth in live hosts; stub bearer auth is isolated to `Testing` ## Why the move -Signer, Attestor, and Provenance form the trust domain -- the set of services responsible for cryptographic evidence production, transparency logging, and verification. Consolidating source ownership under `src/Attestor/` makes trust-boundary responsibilities explicit while preserving runtime isolation and database schema separation. +Signer, Attestor, and Provenance form the trust domain -- the set of services responsible for cryptographic evidence production, transparency logging, and verification. Consolidating source ownership under `src/Attestor/` makes trust-boundary responsibilities explicit while preserving runtime isolation between Signer state and Attestor evidence state. See the [Trust Domain Model ADR](../attestor/architecture.md#security-boundary-no-merge-decision-adr) for the no-merge rationale. diff --git a/docs/modules/timeline/README.md b/docs/modules/timeline/README.md index 6bf3e4a19..72cf8eecb 100644 --- a/docs/modules/timeline/README.md +++ b/docs/modules/timeline/README.md @@ -40,3 +40,8 @@ Timeline is the query and presentation layer for cross-service event correlation - Replay - deterministic replay of event sequences > **Note:** Timeline is the query/presentation service. TimelineIndexer (separate module) handles event ingestion and indexing. They are independently deployable services. + +## Runtime Contract + +- Outside `Testing`, Timeline ingestion must have at least one real transport enabled: `Ingestion:Nats:Enabled=true` or `Ingestion:Redis:Enabled=true`. +- The null ingestion subscriber is reserved for explicit test harnesses and is not a live fallback. diff --git a/docs/modules/timeline/architecture.md b/docs/modules/timeline/architecture.md index 76d753b39..5102dcbc8 100644 --- a/docs/modules/timeline/architecture.md +++ b/docs/modules/timeline/architecture.md @@ -124,6 +124,12 @@ TimelineIndexer was consolidated into the Timeline module (Sprint 210, 2026-03-0 - **Event indexing**: Writes indexed events to PostgreSQL via EfCore (compiled model preserved for migration identity). - **Authorization audit**: Provides audit sink for authorization events. +### Ingestion Transport Contract + +- Non-testing Timeline hosts must enable at least one real ingestion transport: `Ingestion:Nats:Enabled=true` or `Ingestion:Redis:Enabled=true`. +- `NullTimelineEventSubscriber` is a testing-only harness and is not registered in live hosts. +- If both transports are disabled outside `Testing`, startup fails fast with a configuration error instead of exposing an idle ingestion worker. + ### Deployable Services - **TimelineIndexer WebService** (`StellaOps.TimelineIndexer.WebService`): HTTP API for direct event submission and query. diff --git a/docs/qa/feature-checks/runs/web/evidence-presentation-ux/run-004/evidence/playwright-ui-evidence.txt b/docs/qa/feature-checks/runs/web/evidence-presentation-ux/run-004/evidence/playwright-ui-evidence.txt new file mode 100644 index 000000000..a6b54364b --- /dev/null +++ b/docs/qa/feature-checks/runs/web/evidence-presentation-ux/run-004/evidence/playwright-ui-evidence.txt @@ -0,0 +1,18 @@ +Tier2 strict UI evidence for evidence-presentation-ux +CapturedAtUtc: 2026-04-15T17:03:18Z +Playwright command: PLAYWRIGHT_BASE_URL=https://stella-ops.local npx playwright test --config playwright.config.ts tests/e2e/ui-truthful-state-cutover.recheck.spec.ts --workers=1 --reporter=list +Playwright test mapping: rechecks mounted truthful-state routes after the mock-data cutover +Routes: +- /evidence/exports?tab=profiles +- /evidence/exports?tab=runs +Assertions: +- heading "Export Center" is visible +- "Profile creation, editing, and deletion are not exposed by the current Export Center API." is visible +- runs tab reports browser-started exports plus completed StellaBundle exports +- fake seeded "Mock data" copy is absent +Command output snippet: +- Running 2 tests using 1 worker +- ✓ rechecks mounted truthful-state routes after the mock-data cutover (22.4s) +- ✓ rechecks policy governance conflicts without fabricated preview content (11.2s) +- 2 passed (34.9s) +Result: pass diff --git a/docs/qa/feature-checks/runs/web/evidence-presentation-ux/run-004/tier2-ui-check.json b/docs/qa/feature-checks/runs/web/evidence-presentation-ux/run-004/tier2-ui-check.json new file mode 100644 index 000000000..68501ad8a --- /dev/null +++ b/docs/qa/feature-checks/runs/web/evidence-presentation-ux/run-004/tier2-ui-check.json @@ -0,0 +1,32 @@ +{ + "type": "ui", + "module": "web", + "feature": "evidence-presentation-ux", + "runId": "run-004", + "baseUrl": "https://stella-ops.local", + "capturedAtUtc": "2026-04-15T17:03:18Z", + "playwrightSpec": "src/Web/StellaOps.Web/tests/e2e/ui-truthful-state-cutover.recheck.spec.ts", + "playwrightTests": [ + "rechecks mounted truthful-state routes after the mock-data cutover" + ], + "steps": [ + { + "description": "Open the mounted Export Center profiles route.", + "action": "navigate", + "target": "/evidence/exports?tab=profiles", + "expected": "Export Center renders truthful profile copy instead of seeded profile actions.", + "result": "pass", + "stepCapturedAtUtc": "2026-04-15T17:03:18Z" + }, + { + "description": "Open the mounted Export Center runs route.", + "action": "navigate", + "target": "/evidence/exports?tab=runs", + "expected": "The runs tab reports browser-started exports and completed StellaBundle exports without fake seeded jobs.", + "result": "pass", + "stepCapturedAtUtc": "2026-04-15T17:03:18Z" + } + ], + "evidence": "docs/qa/feature-checks/runs/web/evidence-presentation-ux/run-004/evidence/playwright-ui-evidence.txt", + "verdict": "pass" +} diff --git a/docs/quickstart.md b/docs/quickstart.md index 255f4d137..5e6ad347a 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -83,25 +83,11 @@ To skip builds and only start infrastructure: ```powershell docker compose -f devops/compose/docker-compose.stella-ops.yml ps ``` -3. Preview demo seeding: - ```powershell - dotnet run --project src/Cli/StellaOps.Cli/StellaOps.Cli.csproj -- ` - admin seed-demo --dry-run ` - --connection "Host=127.1.1.1;Port=5432;Database=stellaops_platform;Username=stellaops;Password=stellaops" - ``` -4. Seed demo data: - ```powershell - dotnet run --project src/Cli/StellaOps.Cli/StellaOps.Cli.csproj -- ` - admin seed-demo --confirm ` - --connection "Host=127.1.1.1;Port=5432;Database=stellaops_platform;Username=stellaops;Password=stellaops" - ``` -5. Open **https://stella-ops.local**. -6. Log in with the demo admin account: - - **Username**: `admin` - - **Password**: `Admin@Stella2026!` - - **Tenant**: demo-prod (selected automatically) +3. Open **https://stella-ops.local**. +4. Complete the setup wizard and create the first administrator account for this install. +5. Sign in with the credentials you just created. - Additional demo accounts: `operator`, `viewer`, `auditor`, `developer` (same password pattern). +Optional demo datasets are manual-only and not part of the default bootstrap path. If you explicitly need them for demo purposes, use the manual seeding section in [DEV_ENVIRONMENT_SETUP.md](dev/DEV_ENVIRONMENT_SETUP.md). ## What's running @@ -136,8 +122,7 @@ docker compose -f devops/compose/docker-compose.stella-ops.yml --profile sigstor | `health=starting` for RustFS during setup | Advisory startup lag | Wait 30-60 seconds and re-check `docker compose ... ps` | | `stellaops-dev-rekor` restarting without `--profile sigstore` | Optional profile container from older runs | Non-blocking for default setup; ignore or clean old container | | `SM remote service probe failed (localhost:56080)` in `stella --verbose ...` or crypto diagnostics | Optional China SM Remote plugin probe | Non-blocking for default crypto profile; ordinary CLI payload commands now suppress this startup noise | -| `admin seed-demo --confirm` fails with `scheduler_exceptions_tenant_isolation already exists` | Outdated Scheduler migration scripts | Pull latest code and rerun seeding | -| Seed endpoint still returns HTTP 500 after patching source | Running old container image | Rebuild/restart platform image and retest | +| Manual demo seed endpoint still returns HTTP 500 after patching source | Running old container image | Rebuild/restart platform image and retest | | Port conflicts | Local process already using mapped port | Override in `devops/compose/.env` (`devops/compose/env/stellaops.env.example`) | ## Next steps diff --git a/docs/setup/setup-wizard-capabilities.md b/docs/setup/setup-wizard-capabilities.md index 26a017bc7..34b5c6ab3 100644 --- a/docs/setup/setup-wizard-capabilities.md +++ b/docs/setup/setup-wizard-capabilities.md @@ -7,7 +7,7 @@ This document defines the functional requirements for the Stella Ops Setup Wizar The Setup Wizard provides a guided, step-by-step configuration experience that: - Validates infrastructure dependencies (PostgreSQL, Valkey) - Runs database migrations -- Bootstraps the initial admin and crypto profile +- Bootstraps the initial admin, crypto profile, and optional advisory/VEX source configuration - Exposes a truthful required-readiness summary for setup completion - Hands tenant onboarding to authenticated `/setup/*` and integration command surfaces instead of pretending they are bootstrap steps @@ -57,8 +57,9 @@ The system reaches "Production-Ready" state when: | `migrations` | Database Migrations | Yes | No | Infrastructure | | `admin` | Admin Bootstrap | Yes | No | Security | | `crypto` | Crypto Profile | Yes | No | Security | +| `sources` | Advisory & VEX Sources | No | Yes | Data | -Only these five core steps are current runtime setup step IDs. The integration and orchestration catalogs below are historical handoff targets and are no longer accepted by the current setup APIs or `stella setup` command group. +Current runtime setup step IDs now include the five required control-plane steps plus the optional `sources` step. The integration and orchestration catalogs below are historical handoff targets and are no longer accepted by the current setup APIs or `stella setup` command group. ### 3.2 Integration Handoffs (Not current setup steps) @@ -77,13 +78,11 @@ Only these five core steps are current runtime setup step IDs. The integration a |---------|------|----------|-----------|----------| | `environments` | Environment Definition | No | Yes | Orchestration | | `agents` | Agent Registration | No | Yes | Orchestration | -| `feeds` | Vulnerability Feeds | No | Yes | Data | - --- ## 4. Step Specifications -Sections 4.1-4.5 describe the current installation-scoped setup steps. Sections 4.6 and later remain useful as onboarding capability notes, but those inputs now belong to authenticated post-bootstrap surfaces rather than the setup wizard step catalog. +Sections 4.1-4.5 and 4.5.1 describe the current installation-scoped setup steps. Sections 4.6 and later remain useful as onboarding capability notes, but those inputs now belong to authenticated post-bootstrap surfaces rather than the setup wizard step catalog. ### 4.1 Database Setup (`database`) @@ -226,6 +225,39 @@ Sections 4.1-4.5 describe the current installation-scoped setup steps. Sections --- +### 4.5.1 Advisory & VEX Sources (`sources`) + +**Purpose:** Optionally enable advisory and VEX aggregation during bootstrap without forcing operators through the full integrations catalog on first run. + +**Inputs:** +| Field | Type | Required | Default | Description | +|-------|------|----------|---------|-------------| +| `mode` | enum | Yes | `mirror` | `mirror` enables StellaOps Mirror only; `manual` exposes explicit source selection | +| `mirror.url` | string | Required when `mode=mirror` | `https://mirror.stella-ops.org` | StellaOps Mirror base URL | +| `mirror.apiKey` | secret | No | - | Optional StellaOps Mirror API key | +| `enabled` | string[] | Required when `mode=manual` | `[]` | Advisory/VEX source IDs to enable | + +**Outputs:** +- Selected sources are persisted through Concelier durable source storage +- `mirror` mode enables only `stella-mirror` +- `manual` mode enables only the explicitly selected upstream sources +- Initial aggregation is scheduled for newly enabled sources + +**Validation:** +- Mirror URL must be an absolute URI +- Manual mode must select at least one real source +- `stella-mirror` is reserved for mirror mode and cannot be mixed into manual selections + +**Doctor Checks:** +- `check.sources.feeds.configured` +- `check.sources.feeds.connectivity` + +**Skip behavior:** +- Skipping this optional step leaves advisory and VEX aggregation off +- The integrations Advisory & VEX Sources page must surface that off-state and offer later enablement without rerunning bootstrap + +--- + ### 4.6 Vault Integration (`vault`) **Purpose:** Configure secrets management provider. @@ -481,7 +513,7 @@ The wizard stores user preferences in the settings store: "notifications": "slack" }, "completedAt": "2026-01-13T10:30:00Z", - "skippedSteps": ["identity", "feeds"] + "skippedSteps": ["identity", "sources"] } } ``` diff --git a/docs/setup/setup-wizard-ux.md b/docs/setup/setup-wizard-ux.md index 502f418d7..264a48180 100644 --- a/docs/setup/setup-wizard-ux.md +++ b/docs/setup/setup-wizard-ux.md @@ -28,17 +28,34 @@ design material later in this document. - The implemented UI bootstrap flow persists authoritative installation-scoped state in `platform.setup_sessions`. +- On a completed installation, anonymous entry to `/setup-wizard/*` no longer + pretends bootstrap is still open. The first setup-session create/read returns + `401`, then the same route can continue as an authenticated reconfiguration + session once the operator signs in. - Fresh compose bootstrap no longer pre-seeds `SetupComplete=true`; a clean local database now lands in the setup wizard until the control-plane steps are actually finalized. Legacy local volumes are auto-converged by Platform release migration `064_EnvironmentSettingsInstallationScopeConvergence.sql`. -- The current live step inventory is limited to the five control-plane steps - the running platform can truthfully validate and converge: - `database`, `cache`, `migrations`, `admin`, and `crypto`. -- The `admin` step now seeds the same local standard-provider and superuser - defaults into wizard draft state that it renders in the visible form, so an - operator can accept the prefilled values without retyping them and still get - a truthful backend apply. +- The current live step inventory includes the five required control-plane + steps the running platform can truthfully validate and converge plus one + optional data-onboarding step: + `database`, `valkey`, `migrations`, `admin`, `crypto`, and `sources`. +- The `sources` step defaults to StellaOps Mirror only. Operators can switch + to manual advisory/VEX feed selection or skip the step explicitly. +- Skipping `sources` leaves advisories and VEX off. The Advisory & VEX Sources + integrations page then surfaces that disabled state and offers a one-click + StellaOps Mirror enable path without rerunning bootstrap. +- Using `sources` now performs a live reachability check of the selected + mirror/source configuration before the step can complete. Operators must + fix the mirror URL or skip the step if the endpoint is unreachable. +- Local browser automation may ignore local dev certificates, but product + advisory aggregation still requires a hostname-valid certificate and will + report TLS/certificate failures directly in the setup step and integrations + UI. +- The `admin` step now seeds only non-secret local standard-provider and + superuser defaults into wizard draft state. Username and email can stay + prefilled, but the operator must deliberately enter the initial admin + password instead of inheriting a baked-in demo value. - Session reads now separate sanitized `draftValues` from `secretDrafts` metadata. Secret-bearing inputs such as admin or database passwords are retained only in protected server-side companion storage, can survive resume, @@ -50,8 +67,11 @@ design material later in this document. installation-scoped setup session APIs rather than a separate local state machine. - Repeatable tenant onboarding work such as integrations, notifications, - advisory sources, environments, agents, and branding lives on `/setup/*` - and other authenticated module surfaces, not inside the bootstrap wizard. + environments, agents, and branding lives on `/setup/*` and other + authenticated module surfaces, not inside the bootstrap wizard. +- Advisory/VEX onboarding is the one intentional exception: the wizard can + seed initial source configuration because it directly governs whether + aggregation starts at all on a fresh install. - Secret material is no longer an out-of-band prerequisite for GitLab-class UI onboarding. The Integrations Hub can stage credentials through the Secret Authority API and then bind the returned `authref://...` URI to the created @@ -678,6 +698,7 @@ When the system detects first-run (no database connection or admin user): | `migrations` | Database Migrations | Apply schema updates | | `admin` | Admin Bootstrap | Create administrator account | | `crypto` | Crypto Profile | Configure signing keys | +| `sources` | Advisory & VEX Sources | Enable StellaOps Mirror or manual feeds | | `vault` | Vault Integration | Configure secrets management | | `scm` | SCM Integration | Connect source control | | `notifications` | Notification Channels | Configure alerts and notifications | diff --git a/docs/technical/cicd/dsse-build-flow.md b/docs/technical/cicd/dsse-build-flow.md index b012b16be..1fdaa96bd 100644 --- a/docs/technical/cicd/dsse-build-flow.md +++ b/docs/technical/cicd/dsse-build-flow.md @@ -198,7 +198,7 @@ await File.WriteAllTextAsync("artifacts/attest-push.dsse.json", JsonSerializer.S ```yaml .attest-template: &attest - image: mcr.microsoft.com/dotnet/sdk:10.0-preview + image: mcr.microsoft.com/dotnet/sdk:10.0 before_script: - dotnet build src/StellaOps.Attestation/StellaOps.Attestation.csproj variables: