# Platform Service (StellaOps.Platform.WebService) ## Purpose Provide a single, deterministic aggregation layer for cross-service UX workflows (health, quotas, onboarding, preferences, global search) so the Console UI and CLI do not fan out to multiple modules directly. ## Non-goals - Replace module-owned APIs (Authority, Policy, Scanner, Orchestrator, etc.). - Ingest or mutate raw evidence or policy overlays. - Store high-volume evidence payloads (SBOMs, VEX, audit bundles). ## Responsibilities - Aggregate platform health and dependency status. - Aggregate quota usage across Authority, Gateway, Orchestrator, and storage backends. - Persist onboarding progress and tenant setup milestones. - Persist dashboard personalization and layout preferences. - Persist authenticated user language preference for shared Web/CLI locale selection. - Provide global search aggregation across entities. - Provide global context selectors (region/environment/time window) and per-user persistence for Pack 22 top-bar context. - Provide Pack 22 release read-model projections for list/detail/activity/approvals queue views. - Provide Pack 22 topology inventory read-model projections for regions/environments/targets/hosts/agents/promotion paths/workflows/gate profiles. - Surface platform metadata for UI bootstrapping (version, build, offline status). - Expose analytics lake aggregates for SBOM, vulnerability, and attestation reporting. ## API surface (v1) ### Health aggregation - GET `/api/v1/platform/health/summary` - GET `/api/v1/platform/health/dependencies` - GET `/api/v1/platform/health/incidents` - GET `/api/v1/platform/health/metrics` ### Quota aggregation - GET `/api/v1/platform/quotas/summary` - GET `/api/v1/platform/quotas/tenants/{tenantId}` - GET `/api/v1/platform/quotas/alerts` - POST `/api/v1/platform/quotas/alerts` ### Onboarding - GET `/api/v1/platform/onboarding/status` - POST `/api/v1/platform/onboarding/complete/{step}` - POST `/api/v1/platform/onboarding/skip` - GET `/api/v1/platform/tenants/{tenantId}/setup-status` ### Preferences - GET `/api/v1/platform/preferences/dashboard` - PUT `/api/v1/platform/preferences/dashboard` - GET `/api/v1/platform/preferences/language` - PUT `/api/v1/platform/preferences/language` - GET `/api/v1/platform/dashboard/profiles` - GET `/api/v1/platform/dashboard/profiles/{profileId}` - POST `/api/v1/platform/dashboard/profiles` ### Global search - GET `/api/v1/search` (alias to `/api/v1/platform/search`) - GET `/api/v1/platform/search` - Legacy notice: both endpoints now emit deprecation metadata (`Deprecation`, `Sunset`, `Link`, `Warning`) and are being replaced by Unified Search `POST /api/v1/search/query`. ### Metadata - GET `/api/v1/platform/metadata` - Response includes a capabilities list for UI bootstrapping; analytics capability is reported only when analytics storage is configured. ### Localization - GET `/platform/i18n/{locale}.json` (anonymous, cacheable UI translation bundle) - GET `/api/v1/platform/localization/bundles/{locale}` - GET `/api/v1/platform/localization/bundles/{locale}/{namespace}` - GET `/api/v1/platform/localization/locales` (catalog used by Web and CLI locale selectors) - PUT `/api/v1/platform/localization/bundles` - DELETE `/api/v1/platform/localization/strings/{locale}/{key}` - Backend locale resolution contract: `X-Locale` -> `Accept-Language` -> default locale. - Runtime bundle layering consumed by backend services: shared embedded `common` -> service embedded bundle -> Platform override bundle. - Platform ships locale-complete `ui` and `platform` namespace bundles for `en-US`, `de-DE`, `bg-BG`, `ru-RU`, `es-ES`, `fr-FR`, `uk-UA`, `zh-TW`, `zh-CN`; shared localization library now provides `common` bundles for the same locale set. - Bundled locales currently shipped: `en-US`, `de-DE`, `bg-BG`, `ru-RU`, `es-ES`, `fr-FR`, `uk-UA`, `zh-TW`, `zh-CN`. ### Release Orchestrator compatibility - Platform hosts `/api/v1/release-orchestrator/environments/*` as a compatibility facade for Console release-management flows. - Supported families include environment CRUD plus `/settings`, per-environment `/targets` CRUD with `/health-check`, and `/freeze-windows` CRUD. - The compatibility facade no longer uses runtime in-memory environment stores. Platform now binds those flows to the PostgreSQL-backed Release Orchestrator environment services when the `release` schema is configured locally, and otherwise proxies the owning Release Orchestrator WebApi over tenant/auth-aware HTTP. - Platform hosts `/api/v2/scripts*` against the real Release Orchestrator scripts backend on both runtime branches: direct library/schema binding when Platform has the scripts PostgreSQL connection, and an HTTP proxy to the owning Release Orchestrator WebApi when it does not. - The scripts facade no longer falls back to a local in-memory catalog; list/count/detail/version/validation/compatibility flows all resolve against the owning Release Orchestrator service or schema. ## API surface (v2) ### Global context - GET `/api/v2/context/regions` - GET `/api/v2/context/environments?regions=` - GET `/api/v2/context/preferences` - PUT `/api/v2/context/preferences` ### Releases read model - GET `/api/v2/releases` - GET `/api/v2/releases/{releaseId}` - GET `/api/v2/releases/activity` - GET `/api/v2/releases/approvals` ### Topology inventory read model - GET `/api/v2/topology/regions` - GET `/api/v2/topology/environments` - GET `/api/v2/topology/targets` - GET `/api/v2/topology/hosts` - GET `/api/v2/topology/agents` - GET `/api/v2/topology/promotion-paths` - GET `/api/v2/topology/workflows` - GET `/api/v2/topology/gate-profiles` - `/api/v2/topology/hosts` includes `ProbeStatus`, `ProbeType`, and `ProbeLastHeartbeat`. - Current host probe contract is projection-derived rather than live Signals state: `ProbeLastHeartbeat` mirrors the latest projected host sync timestamp, `ProbeStatus` is `active` when that heartbeat is within two minutes of the freshest host heartbeat in the tenant snapshot and `offline` otherwise, and missing heartbeat data yields `not_installed`. - Probe type mapping is deterministic: `winrm_host` -> `etw`; `docker_host`, `compose_host`, `ssh_host`, `ecs_service`, and `nomad_job` -> `ebpf`. ### Security read model - GET `/api/v2/security/findings` - GET `/api/v2/security/disposition` - GET `/api/v2/security/disposition/{findingId}` - GET `/api/v2/security/sbom-explorer` ### Integrations read model - GET `/api/v2/integrations/feeds` - GET `/api/v2/integrations/vex-sources` ### Analytics (SBOM lake) - GET `/api/analytics/suppliers` - GET `/api/analytics/licenses` - GET `/api/analytics/vulnerabilities` - GET `/api/analytics/backlog` - GET `/api/analytics/attestation-coverage` - GET `/api/analytics/trends/vulnerabilities` - GET `/api/analytics/trends/components` ### Legacy alias compatibility (`/api/v1/*`) - GET `/api/v1/context/regions` (alias of `/api/v2/context/regions`) - GET `/api/v1/releases` (alias of `/api/v2/releases`) - GET `/api/v1/topology/regions` (alias of `/api/v2/topology/regions`) - GET `/api/v1/security/findings` (alias of `/api/v2/security/findings`) - GET `/api/v1/integrations/feeds` (alias of `/api/v2/integrations/feeds`) - GET `/api/v1/integrations/vex-sources` (alias of `/api/v2/integrations/vex-sources`) - Alias usage telemetry is emitted as deterministic event keys (`alias__`) with tenant hash metadata only. ## Data model - `platform.dashboard_preferences` (dashboard layout, widgets, filters, optional user `locale` preference key) - `platform.dashboard_profiles` (saved profiles per tenant) - `platform.onboarding_state` (step state, timestamps, actor) - `platform.quota_alerts` (per-tenant quota alert thresholds) - `platform.search_history` (optional, user-scoped, append-only) - `platform.translations` (tenant + locale scoped translation override store) - `platform.context_regions` (global region selector inventory) - `platform.context_environments` (global environment selector inventory with region linkage) - `platform.ui_context_preferences` (tenant + actor scoped region/environment/time-window selections) - `release.release_read_model` (Pack 22 release list/detail projection root) - `release.release_activity_projection` (cross-release timeline projection with run/approval correlation keys) - `release.release_approvals_projection` (cross-release approval queue projection with blocker summaries) - `release.security_finding_projection` (Pack 22 consolidated findings projection with pivot/filter fields) - `release.security_disposition_projection` (read-only join projection for VEX + exception disposition state) - `release.security_sbom_component_projection` (component-level SBOM explorer table projection) - `release.security_sbom_graph_projection` (edge-level SBOM graph projection used by graph and diff modes) - `release.integration_feed_source_health` (advisory feed source health/freshness projection) - `release.integration_vex_source_health` (VEX source health/freshness projection with statement-format metadata) - `release.integration_source_sync_watermarks` (source family synchronization watermark projection state) - `release.topology_region_inventory` (region-level topology projection with deterministic ordering counts) - `release.topology_environment_inventory` (environment-level topology projection with region linkage and aggregate counters) - `release.topology_target_inventory` (target/component deployment inventory projection) - `release.topology_host_inventory` (host runtime inventory projection linked to targets and agents) - `release.topology_agent_inventory` (agent fleet projection with capability and assignment summaries) - `release.topology_promotion_path_inventory` (region-aware promotion-path projection with workflow and gate links) - `release.topology_workflow_inventory` (workflow template projection for topology routes) - `release.topology_gate_profile_inventory` (gate profile projection bound to region/environment inventory) - `release.topology_sync_watermarks` (projection synchronization watermark state for deterministic replay/cutover checks) - Schema reference: `docs/db/schemas/platform.sql` (PostgreSQL; in-memory stores used until storage driver switches). ## Dependencies - Authority (tenant/user identity, quotas, RBAC) - Gateway (rate-limit status and request telemetry) - Orchestrator (job quotas, SLO state) - Notifier (alert policies and delivery status) - Policy/Scanner/Registry/VexHub (search aggregation sources) ## Runtime boundary policy - Runtime read-model services (`/api/v2/releases`, `/api/v2/topology/*`, `/api/v2/security/*`, `/api/v2/integrations/*`) must depend only on explicit query contracts: - `IReleaseControlBundleStore` - `IPlatformContextQuery` - Current host probe enrichment is derived only from the topology projection timestamps already returned by `IReleaseControlBundleStore`. - Future live runtime probe or observed inventory enrichment for `/api/v2/topology/hosts` must still arrive through an explicit query contract. Read-model services must not reach into foreign persistence directly. - Foreign module persistence references are migration/admin-only and limited to explicit allowlist surfaces (`SeedEndpoints`, `MigrationModulePlugins`). - Runtime read endpoints must not inject foreign `*.Persistence*` types, `DbContext` from other modules, or migration runners directly. - Guard tests: `src/Platform/__Tests/StellaOps.Platform.WebService.Tests/PlatformRuntimeBoundaryGuardTests.cs`. ## Security and scopes - Health: `ops.health` (summary), `ops.admin` (metrics) - Quotas: `quota.read` (summary), `quota.admin` (alerts/config) - Onboarding: `onboarding.read`, `onboarding.write` - Preferences: `ui.preferences.read`, `ui.preferences.write` - Context: `platform.context.read`, `platform.context.write` - Releases read model: `orch:read` (`platform.releasecontrol.read` policy mapping in Platform service) - Topology read model: `orch:read` (`platform.topology.read` policy mapping in Platform service) - Security read model: `findings:read` (`platform.security.read` policy mapping in Platform service) - Integrations feed read model: `advisory:read` (`platform.integrations.read` policy mapping in Platform service) - Integrations VEX source read model: `vex:read` (`platform.integrations.vex.read` policy mapping in Platform service) - Search: `search.read` plus downstream service scopes (`findings:read`, `policy:read`, etc.) - Metadata: `platform.metadata.read` - Analytics: `analytics.read` ## Determinism and offline posture - Stable ordering with explicit sort keys and deterministic tiebreakers. - All timestamps in UTC ISO-8601. - Cache last-known snapshots for offline rendering with "data as of" markers. ## Analytics ingestion configuration Analytics ingestion runs inside the Platform WebService and subscribes to Scanner, Concelier, and Attestor streams. Configure ingestion with `Platform:AnalyticsIngestion`: ```yaml Platform: AnalyticsIngestion: Enabled: true PostgresConnectionString: "" # optional; defaults to Platform:Storage AllowedTenants: ["tenant-a"] Streams: ScannerStream: "orchestrator:events" ConcelierObservationStream: "concelier:advisory.observation.updated:v1" ConcelierLinksetStream: "concelier:advisory.linkset.updated:v1" AttestorStream: "attestor:events" StartFromBeginning: false Cas: RootPath: "/var/lib/stellaops/cas" DefaultBucket: "attestations" Attestations: BundleUriTemplate: "bundle:{digest}" ``` `BundleUriTemplate` supports `{digest}` and `{hash}` placeholders. The `bundle:` scheme maps to `cas:///{digest}` by default. Verify offline bundles with `stella bundle verify` before ingestion. ## Analytics maintenance configuration Analytics rollups + materialized view refreshes are driven by `PlatformAnalyticsMaintenanceService` when analytics storage is configured. Use `BackfillDays` to recompute recent rollups on the first maintenance run (set to `0` to disable). ```yaml Platform: Storage: PostgresConnectionString: "Host=...;Database=...;Username=...;Password=..." AnalyticsMaintenance: Enabled: true RunOnStartup: true IntervalMinutes: 1440 ComputeDailyRollups: true RefreshMaterializedViews: true BackfillDays: 7 ``` ## Observability - Metrics: `platform.aggregate.latency_ms`, `platform.aggregate.errors_total`, `platform.aggregate.cache_hits_total` - Logs include `traceId`, `tenantId`, `operation`, and cache-hit indicators. ## Gateway exposure The Platform Service is exposed via Gateway and registered through Router discovery. It does not expose direct ingress outside Gateway in production. ## Setup Wizard The Platform Service owns the installation-scoped setup wizard used by `/setup-wizard/wizard` for first-run control-plane bootstrap and later reconfiguration checks. Current runtime behavior: - Authoritative wizard state is persisted in `platform.setup_sessions` via migration `063_PlatformSetupSessions.sql`. - Installation-scoped environment settings and the `SetupComplete` marker now converge through `platform.environment_settings` keyed only by `key`. Migration `064_EnvironmentSettingsInstallationScopeConvergence.sql` upgrades older compose-created tables that still used the legacy `(tenant_id, key)` primary key. - The persisted store keeps only non-sensitive draft configuration plus step state, timestamps, and check results. Secret material is still expected to be staged through a secret authority rather than stored in wizard session state. - 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`. - 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. - `probe` is diagnostic only. Only `apply` can move a step into the converged state. ### API surface (v1) #### Sessions - `GET /api/v1/setup/sessions` - Get the current installation-scoped setup session - `GET /api/v1/setup/sessions/current` - Alias for the current installation-scoped session - `GET /api/v1/setup/sessions/{sessionId}` - Read a specific persisted session by ID - `POST /api/v1/setup/sessions` - Create a new session or force a restart - `POST /api/v1/setup/sessions/resume` - Resume the current session or create one - `PUT /api/v1/setup/sessions/{sessionId}/config` - Persist non-sensitive draft values - `POST /api/v1/setup/sessions/{sessionId}/finalize` - Finalize the current session with convergence checks - `POST /api/v1/setup/sessions/finalize` - Compatibility finalize path #### Steps - `POST /api/v1/setup/sessions/{sessionId}/steps/{stepId}/probe` - Run a diagnostic probe without completing the step - `POST /api/v1/setup/sessions/{sessionId}/steps/{stepId}/apply` - Apply the current step and persist the new state - `POST /api/v1/setup/sessions/{sessionId}/steps/{stepId}/execute` - Compatibility mutation wrapper (`dryRun=true` maps to probe) - `POST /api/v1/setup/sessions/{sessionId}/steps/{stepId}/reset` - Reset a step to pending - `POST /api/v1/setup/sessions/{sessionId}/steps/{stepId}/skip` - Compatibility endpoint retained for older clients; the current control-plane steps are all required - `GET /api/v1/setup/sessions/{sessionId}/steps/{stepId}/checks` - Read current check results - `POST /api/v1/setup/sessions/{sessionId}/steps/{stepId}/checks/run` - Re-run checks for a step - `POST /api/v1/setup/sessions/{sessionId}/steps/{stepId}/prerequisites` - Evaluate prerequisites for a step - `POST /api/v1/setup/steps/{stepId}/test-connection` - Compatibility probe endpoint used by older clients #### Definitions - `GET /api/v1/setup/definitions/steps` - List the current live step definitions ### Setup step identifiers | 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. | | `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. | 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` are no longer valid setup targets. Platform returns explicit handoff guidance to the authenticated onboarding surfaces instead. ### Setup session states | Status | Description | |--------|-------------| | `NotStarted` | Setup not begun | | `InProgress` | Setup in progress | | `Completed` | All steps completed | | `CompletedPartial` | Required steps completed, optional skipped | | `Failed` | Required step failed | | `Abandoned` | Setup abandoned by user | ### Setup step states | Status | Description | |--------|-------------| | `Pending` | Not yet started | | `Passed` | Completed successfully | | `Failed` | Validation failed | | `Skipped` | Explicitly skipped (optional steps only) | | `Current` | Reserved contract value for active-step projections | | `Blocked` | Reserved contract value for dependency failures | ### Anonymous posture - Before installation setup is marked complete, setup session APIs may resolve 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. - `Finalize` succeeds only after every required control-plane step has converged. ### Security and scopes - Read: `platform.setup.read` - Write: `platform.setup.write` - Admin: `platform.setup.admin` ### Offline posture - Sessions include `DataAsOfUtc` for offline rendering with stale indicators - Step results cached with Doctor check pass/fail status - Suggested fixes generated for failed checks ### Related documentation - UX flow specification: `docs/setup/setup-wizard-ux.md` - CLI guide: `docs/modules/cli/guides/setup-guide.md` - Local operator runbook: `docs/INSTALL_GUIDE.md` - Live proof: `src/Web/StellaOps.Web/scripts/live-setup-wizard-state-truth-check.mjs`