RC1 capability status matrix

Verified against commit 7eb178e99e on 2026-08-17 (UTC). Every row below was checked against src/, the staged plugin/compose set, or a dated live probe on that tree. Rows that could only be established against live state carry the date and the command to re-verify. Nothing here is carried forward from another document.

Partial re-verification 2026-09-14 (SPRINT_20260722_025 OK-10). The four rows in Offline and air-gap that named export-web, export-worker or airgap-controller were re-probed against the running estate after the 2026-09-13 consolidation window and carry that later date inline. The rest of this page is still 2026-08-17 evidence and has not been re-run — read a row’s own date, not this header’s.

This is the single page that answers “what does RC1 actually ship today?” It exists so that a reader does not have to reconcile the marketing pages, the FAQ, the install guide’s caveats and the per-module architecture status tables — and so that nobody has to reverse-engineer readiness from source-path commentary.

This page supersedes the status claims in FEATURE_MATRIX.md(rev 6.1, 6 Feb 2026). That page marks capabilities available or planned with no evidence and no verification date. Where the two disagree, this page is correct and that one is stale — the disagreements are listed in What the previous matrix got wrong, because a reader who has already seen those claims deserves to know which ones moved.


How to read this page

The evidence test

Every row was decided by one question, and it is deliberately not the easy one:

Is the component constructed by a running host, and does the path execute end to end?

Not “does a project exist”. Not “does a document describe it”. That test is used because the softer ones have already failed here, repeatedly and expensively: SecurityGate was documented as the gate contract and was never registered; ScriptExecutor is documented as executing scripts in isolated Docker containers and is referenced by nothing outside its own library; BootstrapService promised zero-touch agent deployment and was consumed only by its own test. A matrix compiled from documentation would have marked all three as shipped.

The status vocabulary, and what falsifies each

StatusMeansFalsified by
shippedA deployed host’s composition root constructs it, and the path runs end to end — route mapped, CLI command registered, job registered — with no off-by-default gate.Only test call sites; the route or command is never mapped; the enabling flag has no live call site; the plugin bundle is not mounted by any host.
partialWired and reachable, but a named material part of the advertised capability is missing, or it measures below the claim. The missing part is stated on the row — a partial with no named gap is not a status, it is a hedge.Finding the named part actually wired (→ shipped), or finding nothing wired at all (→ documentation-only).
experimentalReachable only behind an off-by-default flag, or explicitly restricted to non-production by its own code.The flag defaulting on, or a production host constructing it.
documentation-onlyDescribed in docs; nothing a running host reaches constructs it. The type may exist and compile.A single production call site.
plannedNo implementation.Any implementation in src/.

shipped is about the code path, not about your estate. A row can be shipped and still be unreachable on a given installation because the operator has not configured a prerequisite. Where that gap is material it is named in the limitation column.

What this page deliberately does not do


Release and licensing

CapabilityStatusEvidenceLimitation
RC1 release bundle (images published)planneddevops/release/bundle/ ships a complete, testable bundle: docker-compose.yml, install.sh/install.ps1, release-manifest.yaml.The v1.0.0-RC1 images are not in the registry. Measured 2026-07-28 against registry.stella-ops.org over the OCI Distribution API: no stellaops/ namespace, every product image path 404 NAME_UNKNOWN, and release-manifest.yaml carries digest: null for all 48 product images with a null signature: block. install.sh fails closed on the checksum mismatch before it reaches the pull. Currency note: external-registry state is not re-probed by this page (offline-first posture, AGENTS.md §2.8). Re-verify with GET /v2/_catalog?n=1000 and a tag probe for v1.0.0-RC1. Source: INSTALL_GUIDE.md.
Install from source (contributor path)shippedINSTALL_GUIDE.md— automated and manual paths, both current.Explicitly not an offered install path; it is the contributor workflow. It is, however, the only way to reach a running console until the images land.
Plan tiers (Free / Pro / Enterprise)documentation-onlydevops/release/bundle/README.md §“Raising the limits”, verified against src/.There is one artifact set for everyone and the limits are not enforced by software. Nothing counts environments or scans; there is no token, command, or console screen that raises them. The 3-environment / 999-new-digest-scan limits are a licence grant (LICENSE), not a product gate. An earlier draft described stella set-jwt; that command does not exist, and there are no quota counters, no free-tier token validation, and no metrics for either anywhere in src/.
timeline-web is reproducible from the repopartialThe service runs. Live 2026-08-17: stellaops-timeline-web is on stellaops/timeline-web:bl2-9796c887-20260724.The committed compose disagrees with itself, so a recreate can resolve a tag that is not built here. docker-compose.stella-services.yml:2666 pins :dev (the legacy monolith compose that carried the same pin was deleted 2026-09-01, P8-12); docker-compose.timeline.yml:217 defaults ${TIMELINE_IMAGE_TAG:-bl2-9796c887-20260724} — which is what is actually running. A recreate off the services file would therefore resolve :dev, which no build in this repo produces. Awaiting a separate repair window. Re-verify: docker inspect stellaops-timeline-web --format '{{.Config.Image}}'.
Release identity by digestpartialDigest enforcement is real in two places: src/RuntimeInstrumentation/…/ContainerImageDigestResolver.cs fails closed on tag-only runtime status, and ReleaseOrchestrator has a content-by-digest subsystem (DeploymentContentByDigestEndpoints, OrchestratorImageFetcher).Not enforced for image deployment. Live 2026-08-17: devops/compose/docker-compose.stella-services.yml has 51 image: lines and zero pinned by @sha256:; every service is stellaops/<svc>:dev. The CLI release helper is explicit that it pushes tags only. Re-verify: grep -cE "^\s+image:.*@sha256:" devops/compose/docker-compose.stella-services.yml.

Deployment targets

The product is positioned non-Kubernetes-first, and the code agrees: TargetType (src/ReleaseOrchestrator/__Libraries/StellaOps.ReleaseOrchestrator.Environment/Models/Target.cs) has seven members and none is Kubernetes.

TargetStatusEvidenceLimitation
Docker hostshipped…Deployment/Executor/TargetExecutor.cs routes TargetType.DockerHostAgentCapability.Docker; agent registers it at src/ReleaseOrchestrator/__Agents/StellaOps.Agent.Host/Program.cs (RegisterBuiltIn("docker", …)). In the shipped capability list.Live-proven end to end (a real digest deployed to a real target).
Docker ComposeshippedTargetExecutor.cs (ComposeHostCompose); RegisterBuiltIn("compose", …). In the shipped capability list.none
SSH (agentless, Linux)shippedRegisterBuiltIn("ssh", SshCapability); deploy-time secret resolver registered in ReleaseOrchestrator.WebApi/Program.cs.“Agentless” is target-side only — a StellaOps agent-core still runs somewhere, and that service sits behind the non-default agents compose profile. Live-verified (marker file written on a real SSH host).
WinRM (agentless, Windows)shippedRegisterBuiltIn("winrm", WinRmCapability); IWinRmSecretResolver registered in WebApi/Program.cs.none. Live-verified twice.
AnsibleshippedTargetType.AnsibleHost; RegisterBuiltIn("ansible", AnsibleCapability).Enabled in devops/compose/docker-compose.customer-deploy-fidelity.yml, absent from the base compose default. Not mentioned by the previous matrix at all.
AWS ECSpartialRegistered in the agent catalog (RegisterBuiltIn("ecs", …)) and routed by TargetExecutor.cs.Not in any shipped enabled-capability set. docker-compose.stella-services.yml ships Agent__Capabilities="docker,compose,ssh,winrm,runtime.observe.container", and the code default is narrower still — a stock agent cannot accept an ECS task. No live-execution evidence exists, and the Console topology wizard has no ECS authoring form.
HashiCorp NomadpartialRegisterBuiltIn("nomad", …); routed by TargetExecutor.cs.Identical to ECS: registered, not enabled in any shipped capability list, no live evidence, no Console authoring form.
KubernetesplannedNo implementation; TargetType has no Kubernetes member.Deliberate — consistent with the non-Kubernetes-first positioning. Nothing is being withheld.
Docker SwarmplannedNo Swarm symbols in src/ReleaseOrchestrator.none

Promotion, gates and progressive delivery

CapabilityStatusEvidenceLimitation
Approval gateshippedApprovalEndpoints.cs (approve/reject/batch); PromotionRequestProcessor and ApprovalPolicyGatePlugin in the live gate registry; DB-backed policies.Default mode is the fail-safe RequireManual.
RollbackshippedPOST /{id}/rollbackWebApiDeploymentRollbackStarter; automatic path DeploymentAutoRollback constructed in DeployOrchestrator.cs, default on.none
Decision recordsshippedAddDeploymentDecisionArtifact(); GateDecisionSigner; hash-chained ledger taken as a required service on approve/reject/deploy/rollback; deployment attestation submitted to Attestor by default.Two operator prerequisites: AuditChainCheckpointService is a no-op when no checkpoint key is configured (it logs a warning and the chain head goes unsigned), and with Scanner:BaseUri unset the decision referrer falls back to a local filesystem store instead of travelling as an OCI referrer.
Freeze windowspartialCRUD routes on the environment endpoints; IFreezeWindowService registered; deploy-time enforcement reached from DeploymentEndpoints.csDeploymentClassGate.CheckFreezeAsync (fail-closed: an unavailable service blocks).Enforced only for environments whose ProtectionClass == Protected. DeploymentClassGate returns Pass immediately for Open and Gated, so a freeze window on a Standard/Open environment is stored, listed, and never enforced. Separately, the promotion-time FreezeWindowGate is documentation-only: it is constructed only by its own test, and the live gate chain contains SbomReadiness, ApprovalPolicy, Reachability and Attestation — no freeze gate.
Policy gatepartialThe live gate is HttpPolicyEngineGateClient calling POST /api/policy/packs/{packId}/revisions/{version}/evaluate — Stella policy packs, Enabled=true and FailClosed=true by default.Two material gaps. (1) The gate is skipped entirely when no policy pack is bound for the target environment and no DefaultPackId is set — and DefaultPackId is null by default. (2) It is not OPA/Rego. Rego lives on a different service and surface: PolicyInteropService imports Rego into roughly six native gate types and evaluates those natively. AddEmbeddedOpaEvaluator has zero call sites, so no OPA runtime executes arbitrary Rego, and the release orchestrator never calls the interop surface at all.
Rolling / canary / blue-greenpartialIDeploymentStrategyFactoryDeploymentStrategyFactory; strategies in …Deployment/Orchestrator/RollingDeploymentStrategy.cs.They batch targets, not traffic. The code says so itself (DeploymentStrategyPlanner.NoTrafficManagementCaveat; BlueGreenDeploymentStrategy’s own doc comment: “Traffic switching is handled externally after deployment completes.”). With a single target all three collapse to one all-at-once batch.
A/B releasesdocumentation-only…ReleaseOrchestrator.Progressive/ (AbRelease, RolloutController, CanaryController, TrafficRouterRegistry) is referenced by exactly one .csprojits own test project. The WebApi does not reference it.Full implementation, zero production call sites. This is the ScriptExecutor pattern again.
Traffic routing (Nginx / HAProxy / Traefik / ALB)documentation-onlyNo load-balancer adapter is registered by any host. The only Nginx adapter in the tree (NginxRouter, NginxConfigGenerator, NginxReloader) lives inside the unreferenced Progressive library.The registry itself is live and is called on the deploy path, so the failure is honest rather than silent: default terminal evidence is Mode="standby_only", Status="not_shifted", Reason="traffic_adapter_missing". Proven by the repo’s own QA run — a real blue-green deploy requesting the webhook adapter failed with traffic_adapter_executor_missing.
Traffic adapters that do exist (local-file, webhook)experimentalLocalFileDeploymentTrafficAdapter and WebhookDeploymentTrafficAdapter in ReleaseOrchestrator.WebApi/Services/DeploymentTrafficAdapters.cs.Each is behind an explicit …:Enabled flag that defaults false. Neither is a load balancer; the local-file adapter is a lab fixture.

Workflow and scripting

CapabilityStatusEvidenceLimitation
Component DAG executionpartialComponentDeploymentPlanner performs real topological ordering into dependency waves, on the live deploy path.The only DAG that executes is release-component dependency ordering inside one deployment. There is no general-purpose workflow DAG in the release orchestrator.
Step registrypartialIStepProviderRegistryStepProviderRegistry, reached from WebApi/Program.cs via AddReleaseOrchestratorPlugin(); enumerated live by a hosted monitor.Registration and health-monitoring work; execution does not. IStepExecutorStepExecutor is registered but has zero resolve sites in the repository. No endpoint, service or job ever runs a registered step.
Workflow templatesplannedNo template symbols in the Workflow WebService or Engine.The previous matrix already marked this planned, correctly.
Script steps (stored / validated)partialScriptsEndpoints.cs maps list/get/create/update/delete/validate/versions/check-compatibility.There is no execute route. Of the seven declared ScriptLanguage values (C#, Python, Java, Go, Bash, TypeScript, PowerShell), only three validators are registered (C#, Python, TypeScript). A Bash script can be stored but is never syntax-validated.
Script execution in isolated containersdocumentation-only…Scripts/Execution/ScriptExecutor.cs is documented as “Executes scripts in isolated Docker containers”. It is not registered by its own ServiceCollectionExtensions, and its only call site in the tree is ScriptDebugger, itself unregistered.The isolation claim is unsupportable because nothing constructs the class, so no isolation is ever exercised. ScriptSandbox, RuntimeImageManager, ExecutionMonitor and LanguageServerPool are unregistered for the same reason.
build.script execution pluginexperimentalBuildScriptExecutionPluginContainerExecutionPluginBackend, whose dependencies are registered in Agent.Host/Program.cs.Not a built-in: it arrives only through RegisterExternalExecutionPlugins, which returns early when no plugin paths are configured, and it appears only in lab overlays. Its isolation posture is genuinely designed (network disabled by default, read-only mounts, resource limits, signature verification), but src/ReleaseOrchestrator/AGENTS.md states that live signed-image execution remains an unverified Phase-3 gate.

Integrations and connectors

CapabilityStatusEvidenceLimitation
Registry connectors (12)shippedLive GET /api/v1/integrations/providers returns 12 registry descriptors: acr, artifactory, dockerhub, ecr, gcr, ghcr, gitlab-cr, harbor, nexus, oci-distribution, quay, zot. Signed bundle under devops/plugins/integrations/recommended/, loaded under process isolation; the host’s reported count matches the manifest.All 12 declare test-connection / health-check / discovery — read and enumerate, not push.
SCM connectorspartialLive provider list contains exactly one SCM entry: gitHubApp. IntegrationService.GetSupportedProviders() enumerates the loaded plugin set, not a static catalog.GitHub App is read/test-only and has no webhooks. GitHubAppConnectorPlugin declares only TestConnection and HealthCheck — not even discovery. GitLab and Gitea are documentation-only: the projects exist and compile, but no bundle is staged and the live provider list does not contain them. No Bitbucket or Azure DevOps anywhere.
SCM webhooksdocumentation-onlyThere is no SCM webhook receiver in src/Integrations/. Scanner’s /api/v1/webhooks/{github,gitlab,…} routes were deliberately quarantined in 2026-07 to a deterministic 501.A live gateway probe returns 503 target-microservice-unavailable for webhooks — a phantom service, not a deregistered one. Write-back is quarantined too: the GitHub/GitLab annotation clients were made internal, and stella github upload-sarif exits with a clean “not available in this release”.
CI connectorsdocumentation-onlyLive provider list returns zero entries of type cicd. GitLabCiConnectorPlugin and JenkinsConnectorPlugin exist but are in no staged bundle; the integrations-scm-ci overlay mounts a directory that does not exist.The sanctioned CI path is CLI-in-CI plus recipe YAML under docs/technical/cicd/ — not a connector. The Gitea and Jenkins containers in docker-compose.integrations.yml are local test fixtures for connectors that are not loaded.
Secrets — HashiCorp VaultshippedLive provider list returns one secretsManager entry: vault. VaultAuthRefResolver is additionally wired unconditionally in Program.cs.none
Secrets — cloud-managed (AWS/Azure/GCP)plannedNo implementation exists anywhere in src/Integrations/. The only other secrets plugin is Consul (also self-hosted, also unstaged).Consistent with the on-prem/sovereign-first rule — a cloud backend registered by default would itself be a defect. The previous matrix marks AWS Secrets Manager planned, which is accurate.
Notification channelspartialChannelAdapterFactory.AddChannelAdapters(), invoked by the live notifier worker, registers six adapters: webhook, email, chat-webhook (serving both Slack and Teams), PagerDuty, OpsGenie, plus in-app inbox. The live notify.channel_type enum matches exactly.No channel has ever delivered successfully on the live estate. Live 2026-08-17: one configured channel (an email fixture flagged local-queued-only), the worker logs created 0 deliveries on every event, and notify.deliveries holds exactly one row, status failed. Discord and Telegram are a different, unstaged plugin family. Two dormant duplicates worth knowing: SlackChannelAdapter and CliChannelAdapter exist but are registered nowhere — reading them is reading dead code.
Advisory feed connectorsshipped (8)src/Vulnerabilities/__Libraries/StellaOps.Vulnerabilities.Ingestion/ServiceCollectionExtensions.cs registers ten IVulnSourceConnectors, consumed by the live worker’s IngestScheduler. Live docker logs stellaops-vulnerabilities-worker shows completed cycles with real counts.Eight are upstream advisory feeds — nvd, cve, osv, kev, redhat-csaf, suse-csaf, alpine-secdb, debian-security-tracker. The other two registrations are not feeds: a distro binary corpus and the offline mirror importer. EPSS runs in the same cycle but is a fact source, not on the connector SPI. See What the previous matrix got wrong — the “33+ connectors” claim counts a plane that no longer runs.
Stella Registry (the product’s own OCI registry)partialstellaops-registry-web and stellaops-registry-token are running (registry-web up since 2026-08-16T11:16Z) and enforcing: GET http://127.1.0.49/v2/401 with a WWW-Authenticate: Bearer realm=… challenge. It owns stellaops_registry with both schemas converged on startup.Implemented and serving, but not yet the estate’s registry. docker-compose.registry.yml is not in the default compose include: chain, and registry.stella-ops.local still resolves to the interim zot — the network aliases are split (zot holds registry.stella-ops.local; registry-web holds stella-registry.stella-ops.local). The cutover was refused at its gate. Truth doc: modules/registry/registry-topology.md, which is current. Re-verify: docker inspect stellaops-zot stellaops-registry-web --format '{{json .NetworkSettings.Networks}}'.

Scanning and SBOM

CapabilityStatusEvidenceLimitation
Language analyzerspartial14 IScannerLanguageAnalyzerPlugin implementations exist and 14 bundles are staged under devops/plugins/scanner/analyzers/.The default profile stages 5 of them, and that is the only analyzer mount on the live worker: dotnet, golang, java, node, python (devops/plugins/scanner/base/). Ruby, Bun, Deno, PHP and Rust are not deployed by default — they need the e2e analyzer-coverage overlay. Conversely, four real analyzers the previous matrix never mentions do exist: ccpp, dart, elixir, swift. Bun and Deno are genuinely separate analyzers, not Node aliases — though Bun declares only pkg:npm/ prefixes, so it is a distinct analyzer over the npm ecosystem rather than a distinct ecosystem.
“Native binaries” analyzershipped (different host)NativeAnalyzerPlugin implements INativeAnalyzerPlugin — a different interface — and is constructed by the CLI host (src/Cli/StellaOps.Cli/Program.cs, AddNativeAnalyzer).Not a language analyzer, no manifest, no staged bundle, and the scanner worker does not reference it. Counting it among “11 language analyzers” conflates two hosts.
OS package analyzerspartialThree staged bundles, matching the live container’s pinned plugin directories: os.apk (pkg:alpine/), os.dpkg (pkg:deb/), os.rpm (pkg:rpm/).pacman has a plugin class but no staged bundle anywhere — it is not deployable. Seven further OS plugin classes exist in source and are likewise unstaged (Portage, Homebrew, macOS bundle, pkgutil, Chocolatey, MSI, WinSxS). Honest count: 3 shipped, 1 claimed-but-unstaged, 7 source-only.
Versioned purl from manifest-only projectspartial (documented limit)NodeLanguageAnalyzer builds a purl only inside if (resolvedVersion is not null); the no-lock branch records the component with purl: null, version: null. Same shape in the PHP composer discovery and in Bun.A lock file is required for a versioned purl on node, php and ruby. A manifest-only project yields a component an advisory range cannot match. This is correct behaviour — a version range is not a version — but it is a real coverage limit.
SBOM ingestion formatsshippedFormat detection covers CycloneDX (bomFormat) and SPDX (spdxVersion / JSON-LD @context); SbomFormat = CycloneDx | Spdx | Unknown.No Trivy-native parser. A Trivy-produced SBOM is ingested through its CycloneDX or SPDX output like any other third-party generator. Emitting the Trivy DB format was retired by owner ruling in 2026-08.

Estate condition, not a capability status — and it is deliberately not given one of the five statuses, because it is about this stack today rather than about the code. As of 2026-08-17 the live scanner worker loads zero analyzers: all 8 staged bundles fail the pre-activation coherence check with contract_assembly.mismatch (“Analyzer bundle contract ‘StellaOps.Scanner.Analyzers.Lang’ does not match the worker host”), ScannerAnalyzerHost.LoadAllAsync() throws, and a scan job has been retrying stage: execute-analyzers in a loop. This is bundle/host build skew from a concurrent rebuild — fixed by a coherent build-service-publish.sh scanner-worker, not by writing code. It is recorded because a page that says analyzers ship owes the reader the fact that they are not currently running. Re-verify: docker logs stellaops-scanner-worker 2>&1 | grep contract_assembly.mismatch


Reachability

The reachability numbers below are measured, reproducible offline from a clean checkout with one command, and published in full at benchmarks/reachability-corpus-method.md. This page cites that measurement rather than restating it.

CapabilityStatusEvidenceLimitation
Layer 1 — static call graphshippedCallGraphServiceCollectionExtensions.AddCallGraphServices, constructed by Scanner.Worker/Program.cs. Registered extractors: dotnet, java, node, javascript, php, python, ruby, rust, go, plus a binary extractor.Nine languages plus native, not eleven. The Bun and Deno extractors are written and implement the interface but are registered nowhere — Bun’s only call sites are its own tests, Deno’s extractor has zero call sites outside its own file. The scanner dossier already concedes this (“Extractor registration pending follow-up sprint”). Eight entrypoint classifiers are live for the same reason.
Layer 2 — binary loader resolutionshippedBinaryCallGraphExtractor registered with Language = "native", plus BinaryEntrypointClassifier.none found
Layer 3 — runtime signal correlation (eBPF)documentation-onlysrc/RuntimeInstrumentation/ has a Program.cs host but no Dockerfile, no build-script entry, and no compose service; its only cross-tree consumer is a test project.No image is ever built and nothing running constructs it. The running stellaops-agent-core is not this component — the build script maps agent-core to the ReleaseOrchestrator deployment executor. The three-layer story is two layers on a running estate.
Reachability accuracypartial11 of 16 exact matches (0.6875) on the sixteen-case ground-truth corpus — two services per language, eight languages, four verdict classes, corpus content digest sha256:1c71eb7d….Recall and “observed-reachable” claims are supportable; precision is not, and the restriction is retained deliberately. See the note below.
Negative verdict (not-observed)partial3 of 5, precision 1.0000 over three predictions, recall 0.6000. The semantic is decided and recorded in modules/scanner/reachability-sink-strategy.md§7.0a: not-observed now asserts that the analyzer searched and that the search was capable of exhibiting the symbol had it been called.Do not quote the 1.0000 precision. It stands on three predictions, and it became perfect by converting a single false positive. The not-present row in the same published table is a vacuous 1.0000 from zero predictions — the cautionary example sits two lines away. The restriction lifts when the negative class carries enough support to be a rate, not when it stops being zero.
Negative-class regression gateshipped.gitea/workflows/reachability-negative-class.yml is a blocking lane; the evaluator exits 3 when the negative class loses ground-truth support and prints its precision/recall/support on every run.Deliberately not folded into .NET PR Tests, which is dispatch-only and report-only, so a gate there would gate nothing. It exists because deleting the negative cases raises headline accuracy from 0.6875 to 0.7273 while measuring strictly less — a regression here would look like an improvement in every number except the one the gate checks.

Two movements landed on 2026-08-16 and this page keeps them separate, because conflating them would overstate what the analyzer did. 8/16 → 10/16 was the analyzer changing against a fixed answer key (the negative-class semantic: an entrypoint witness and a package coverage witness, both failing closed to unknown). 10/16 → 11/16 was the answer key changing against a fixed analyzer — one case, svc-03, retiered R3 → R0 after the R3 definition was settled; no analyzer prediction changed. The corpus content digest moved on the second and not the first, which is how a reader tells them apart without trusting our account of it.

Two limits that remain, both named rather than argued away: the .NET lightweight lane attributes no external node to a package, so no coverage witness can exist there (its paired case proves the same symbol fails to bind even when it is called), and the Java lane is a bytecode analyzer that produces an empty graph on a source-only tree — so this corpus measures nothing about Java accuracy.


Vulnerability data and EPSS

CapabilityStatusEvidenceLimitation
Vulnerability hub (live data plane)shippedsrc/Vulnerabilities/ has been the live plane since 2026-08-04. The old plane’s schemas are dropped, its services stopped, and its gateway routes removed.src/Concelier/ still exists because production projects still compile against parts of it. It backs nothing that runs — there is no Concelier container in the estate, running or stopped.
Scanner EPSS (scanner-owned tables)shipped26 relations / 3,663 MB, 24 consecutive daily SUCCEEDED ingests, epss_current ~360 k rows.Two of the 26 relations are inert (epss_signal_config and epss_signal, 88 kB combined), and the reason is a defect rather than disuse: EpssSignalJob throws 42P01: relation "scanner.vuln_instance_triage" does not exist on every run. That job is disabled under the hub flag and dies with the planned drop.
Hub EPSS ingestshippedLive 2026-08-16 22:51Z: facts.epss_current = 360,399 rows, facts.epss_import carries model_date=2026-08-16. GET /api/vulnerabilities/v1/epss/model-date returns a non-null date.EPSS is serialized behind the whole ingest cycle, so its effective period is max(4 h, cycle duration) — measured 3 h 23 m of latency ahead of EPSS on this estate. An operator watching facts.epss_current sit at 0 for two hours is seeing the design, not a failure. EPSS also writes no generation row, so it never appears in GET /sources.
Scanner reads EPSS from the hubpartialThe forcing function fired on scanner-worker. Live 2026-08-17T00:33Z the container carries Epss__Source=hub and its log line reads “Epss:Source=hub — EPSS ingest/enrichment/signal jobs NOT registered”, with the three job-start lines absent. Re-verify: docker logs stellaops-scanner-worker 2>&1 | grep "Epss:Source=hub".Only one of the two scanner roles has cut over. scanner-web carries no Epss__* environment and still serves its own tables — live GET /api/v1/epss/status returns latestModelDate: 2026-08-15 while the hub is on 2026-08-16. It is blocked on an Authority identity decision, not on a window: scanner-web’s client is DPoP sender-constrained and holds only signer:sign, and StellaOps.Auth.Client implements no DPoP, so it can mint no token at all. The 26 scanner-owned relations remain hot and undropped.

Cryptography

There are two unrelated crypto plugin trees, and conflating them is the single easiest mistake to make here. DSSE signing resolves providers from src/__Libraries/StellaOps.Cryptography.Plugin.*; the src/Cryptography/Plugin.* tree is credential-store AEAD and, in its own words, “does NOT feed signing”. Registration for all regional signing is RegionalCryptoRegistration, called by four hosts (Attestor, Signer, Scanner WebService, ReleaseOrchestrator WebApi).

ProfileStatusEvidenceLimitation
International (ES256 / Ed25519)shippedHostCryptoProfiles.Normalize(null)international. Live 2026-08-17: STELLAOPS_CRYPTO_PROFILE is unset on all four signing hosts.This is standard NIST crypto, and it is what every live signing host runs.
GOSTexperimentalReal algorithm, not a shim: OpenSslGostSigner uses BouncyCastle ECGost3410Signer with GOST digests and GOST3410-2012-256/512 curves.The software provider is permitted only in Development/Testing; Staging/Production throws fail-closed unless a certified CryptoPro/PKCS#11 backend is confirmed — and that confirmation accepts a profile-name substring (hsm/cryptopro) as the operator’s assertion, which is a naming convention, not a probe. Not exercised on the live stack.
SM / ShangMiexperimentalReal algorithm: SmSoftCryptoProvider uses SM2Signer and an SM3 hasher.The production path does not perform SM2 locally — it delegates over HTTP to a remote OSCCA HSM vendor adapter that must be deployed separately, and no such container exists on the live stack. Software SM2/SM3 is Dev/Testing only.
eIDASdocumentation-only (as a distinct profile)RegionalCryptoRegistration registers nothing for eidas and returns an empty provider list. HostCryptoProfiles says so in its own comment: “served by the international ECDSA stack today”.A distinct EidasCryptoProvider does exist, but its only non-test registration is in the CLI behind #if STELLAOPS_ENABLE_EIDAS, a compile-time symbol no build or release script in the repository defines. In every deployed host, “eIDAS” is ES256 with a different label.
FIPSdocumentation-only (as a distinct profile)No FIPS signing provider exists in the signing tree at all. The Plugin.Fips project is in the unrelated AEAD tree and does not feed DSSE.A config enum value with no distinct signing provider.

Compliance exports

Live catalog probe 2026-08-17: 8 assurance profiles, all reporting state: "blocked"on this estate. Re-verify with GET /v1/exports/assurance/profiles. “Blocked” here means the tenant has not satisfied the export’s prerequisites — it is the fail-closed machinery working, not a missing exporter.

ExportStatusEvidenceLimitation
NIS2 (statement-of-applicability, effectiveness-report)partialAddNis2SoaExportServices + MapNis2SoaExportEndpoints in ExportCenter.WebService/Program.cs; both profiles live in the catalog.Blocked live with reasonCodes: ["signing-provider-missing"] and ["effectiveness-signing-material-missing","source-dashboard-required"]. Precision on the fail-closed behaviour: the host crashes on boot only when a signing key id is declared without key material; if nothing is configured at all the host starts and the endpoint fails at request time. The operator prerequisite is STELLAOPS_EXPORTCENTER_NIS2_SOA_SIGNING_KEY_PEM_FILE.
CRA (technical-file, conformity-dossier)partialAssurance run endpoint mapped; adapter reachable. Its produce overlay is in the live compose set and its signing env is applied.Still blocked, with four reason codes — intake-key-expired-or-missing, rotation-roles-missing, security-mailbox-unverified, signing-provider-missing. The gate is multi-factor tenant state, not just signing config; the overlay satisfies only the signing factor.
DORA (roi, major-incident-report, info-sharing, tlpt-evidence-pack)partialFour profiles in the live catalog, backed by AssuranceBundleProduceService.All four blocked; live env shows the DORA signing keys empty. Blocked on owner-approval references, register/schema-mapping sources and signing material — genuine source-contract inputs, not only keys.
Standards mapping (ISO 27001, IEC 62443-4-1/-4-2, EN 303645, JRC-CRA)partialStandardsMappingTargets carries real target specs, YAML mappings, a deterministic archive and a bundle verifier.A separate surface from the assurance profiles. The adapter is not in the static export-adapter list, and no host registration was found; it is reachable only via the mounted-bundle path, whose own comment says no such bundle ships today.
SOC 2 / PCI DSS / FedRAMP / ENISAplannedNo adapter, no profile, no route anywhere in src/.Absent entirely.

Offline and air-gap

CapabilityStatusEvidenceLimitation
Egress guard (sealed mode)partialEgressPolicy.EnsureAllowed throws AirGapEgressBlockedException and is registered by nine real hosts (Authority, Policy Engine, Policy Gateway, ExportCenter, Signals, Registry token service, Notifier worker, CLI, Concelier WebService). It is a real blocker, not a status flag.It is cooperative. It guards only calls routed through the egress client factory. The Roslyn analyzer that would enforce that repo-wide is referenced by exactly one project — its own test project — and is in no Directory.Build.props, so nothing prevents a service from using a plain HttpClient and bypassing it. Default mode is Unsealed.
Sealed-posture custodianpartialplatform.environment_state with its store and endpoints in Platform; table converged by migration. Since OK-5 (2026-09-11) Platform is the only custodian — the AirGap Controller host that used to hold posture is retired. GET /api/v1/platform/environment-state/airgap-seal/estate answers 401 unauthenticated (live 2026-09-14), so the surface is served and auth-gated.Live 2026-09-14 the table is still empty (0 rows) — the custodian exists, and no sealed posture is declared on this estate, so every posture indicator reads not-declared rather than unsealed. Re-verify: docker exec stellaops-postgres psql -U stellaops -d stellaops_platform -c "SELECT * FROM platform.environment_state;"
Air-gap sealed-mode APIpartialThe airgap-controller deployable is retired (OK-5, 2026-09-11; stellaops-airgap-controller last exited 2026-09-11 and is not in the running set). /system/airgap/{status,seal,unseal,verify} is still mapped and still served: live probe 2026-09-14 returns 401 and the error body now names "service":"policy-engine", which is the enforcement half of the surface. Posture custody itself is Platform’s (row above). Re-verify: curl -sk -o /dev/null -w '%{http_code}' https://127.1.0.1/system/airgap/status.There is no HTTP import route on this surface. The airgap:import scope and policy are registered but attached to no endpoint here. Bundle import is served by OfflineKit at /api/offlinekit/v1 (401 live, same probe date) and by the CLI; the /system/airgap group does not carry it.
Offline bundle build and serveshippedServed by offlinekit-web + offlinekit-workersince the 2026-09-13 consolidation window (SPRINT_20260722_025 OK-10). The three predecessors — export-web, export-worker, airgap-controller — are stopped (docker ps -a: Exited(0)) and frozen at src/__Obsoleted/. Live 2026-09-14: /api/offlinekit/v1/exports/profiles401, /api/offlinekit/v1/status401; the retired bare path /v1/exports404.The artifacts themselves are readiness-gated — see the compliance rows. The bare collection path /api/offlinekit/v1/exports also returns 404 and that is correct: the group maps /profiles and /runs beneath it and no collection root, so a 404 there is not evidence the plane is down.
src/OfflineKit/ hostsshippedReversed 2026-09-13. Both hosts run: stellaops-offlinekit-web and stellaops-offlinekit-worker are in the running set (40 stellaops-* services, live 2026-09-14), and devops/compose/docker-compose.offlinekit-consolidated.yml is in the live compose chain (docker inspect stellaops-offlinekit-web --format '{{index .Config.Labels "com.docker.compose.project.config_files"}}').The earlier “documentation-only” verdict was correct when written and is kept here because the reasoning still matters: the OfflineKit Application library was live inside export-web before either host existed, so “the tree is not live” and “the hosts are not live” were always different claims. Both are now false.
Compact offline vuln-DB exportshippedstella vuln-db export registered in the CLI command factory.Resolves the “two exporters” trap: there is one authoritative implementation (CompactVulnDbExporter), and the CLI is an explicit thin delegate. The second surface is an invocation surface — a Concelier job — and no Concelier container runs, so that path is dead. The CLI is the only live path.
Bundle verification on importshippedstella offline import has --verify-dsse and --verify-rekor both defaulting true; stella bundle verify performs checksums, DSSE signatures, Rekor proofs and payload-type checks.Verification is CLI-side; there is no server-side import endpoint to enforce it.

Platform posture

CapabilityStatusEvidenceLimitation
Database-per-service (ADR-039)partialLive 2026-08-17: 9 databases. The REVOKE CONNECT boundary is real and positively verifiedhas_database_privilege('policy','stellaops_vuln','CONNECT')f, and on its own database → t. Six services own a database with their own role (policy, vuln, registry, timeline, signals, replay).Transitional, not achieved, and the boundary is bypassed as deployed. 33 of 42 services share stellaops_platform as the superuser role stellaops, and a superuser ignores ACLs — proven by connecting as stellaops to stellaops_policy successfully. Authority owns a database but connects as stellaops too. Re-verify: docker exec stellaops-postgres psql -U stellaops -d postgres -tAc "SELECT datname FROM pg_database WHERE datistemplate=false ORDER BY datname;"
Auto-migration on startupshippedEach service auto-migrates its own schema from embedded SQL; there is no manual init step in steady state.Manual postgres-init/ scripts remain first-run bootstrap fallbacks only, not the migration authority.

Surfaces — CLI, API, Console

SurfaceStatusEvidenceLimitation
CLI (stella)partialsrc/Cli/StellaOps.Cli/Commands/CommandFactory.cs registers 108 top-level command groups.95 are real; 13 are placeholder to some degree. Three are fully fail-closed placeholders where every leaf returns NotImplemented: issuer, watchlist(its availability check is a hardcoded return false) and verify-fix. Worst partial: reachability — 16 of 19 leaves are placeholders; only show, export and trace work. The pattern is pinned by CliPlaceholderFailClosedTests, and the placeholders refuse to fabricate output rather than faking success. Two traps: DbCommandGroup and TrustCommandGroup are never called (there is no stella trust, only trust-profile), so their placeholders are unreachable dead code; and of 8 shipped CLI plugin drops, 5 are silently dropped as name conflicts.
HTTP API / gatewayshipped201 declared routes in the live gateway table (devops/compose/router-gateway-local.json, bind-mounted over the service’s own appsettings.json). ~185 reach live published surface.16 are dead declarations whose paths exist nowhere in the running system — /api/v1/{reachability,triage,trust,snapshots,determinization,verify,offline-kit,secrets,advisories}, /api/vuln-explorer, /api/verdicts, /api/doctor, /api/scheduler, /scheduler, /api/fix-verification, /vuln/ledger/events. Zero routes fail because a target is down.
OpenAPI aggregateshipped/.well-known/openapi/openapi.json returns 200, OpenAPI 3.1.0; /api/openapi/aggregate reports 32 services polled, 30 ok.Do not quote its path count as an endpoint count. It publishes both gateway-facing and upstream forms of the same path, so the raw figure overstates distinct surface by roughly the alias factor.
Console (UI)shippedsrc/Web/StellaOps.Web/src/app/app.routes.ts. 16 real top-level feature areas: dashboard, environments, releases, developer, security, triage, evidence, compliance, inventory, ops, setup, settings, docs, console-admin, notifications, setup-wizard.Count the feature areas, not the route table: there are 96 top-level route objects but 71 are redirect-only aliases. /platform, /platform-ops, /release-control, /evidence-audit, /evidence-packs, /security-risk, /analyze, /admin and /assurance all look like feature areas and resolve to 100%-redirect files with zero components. Zero routes are feature-flag gated — every guard is infrastructure or RBAC. One placeholder area: mission-control, whose two non-redirect children are static card lists with empty class bodies. triage is real but ships a permanent in-product banner stating that its workflow requires a server-side capability that is not implemented yet. A dev-only lens harness ships in production builds with no flag gate.

If you re-verify the API rows yourself, the obvious oracle is inverted — check this first. The gateway has catch-all routes, so 503 target-microservice-unavailable is the default for an undeclared /api/v1/* path, not evidence that a declared target is down. 404 endpoint-not-found is the real “declared route, live target, unpublished surface” signal. And a 401 proves the route is served end to end — endpoint resolution runs before authentication, so the response body names the answering upstream and its version. Anything outside /api falls through to static files and returns the SPA with HTTP 200, so there is no top-level 404 at all. Reading a 503 as “the service is down” is the easiest wrong conclusion to reach on this stack.


What the previous matrix got wrong

Listed because FEATURE_MATRIX.mdrev 6.1 has been the answer for six months, and a reader who trusted it deserves the specific corrections rather than a general warning. Each was measured against src/ or a live probe on 2026-08-17.

Previous claimMeasured
“Concelier provides 33+ vulnerability feed connectors”8 upstream feeds on the live plane. The 33+ figure counts src/Concelier/, which holds 54 connector projects and no running container — nothing loads them. Its 31-row connector operations matrix describes a plane that serves no traffic; GHSA is listed “stable” and the live hub has no GHSA connector at all.
“A/B Releases — traffic splitting”The whole Progressive library is referenced only by its own test project.
“Blue-Green — zero-downtime switch”Performs no traffic switch; its own doc comment defers cutover to “external”.
“Traffic Routing Plugins — Nginx/HAProxy/Traefik/ALB adapters”No load-balancer adapter is reachable from any running host. The parenthetical “live load-balancer data-plane proof pending” understates it — there is no adapter whose data plane could be proven.
“GitLab Integration — SCM + webhooks”False on both halves: GitLab SCM is loaded by no running host, and no SCM webhook surface exists in the product. The GitHub row is half true — the plugin is real and loaded; the webhooks are not.
“CI/CD Gates — GitLab/GitHub/Jenkins”Zero CI connectors are registered in any running host.
“Language Analyzers (All 11)”Wrong in both directions: “native binaries” is a different interface on a different host, four real analyzers (ccpp, dart, elixir, swift) are omitted, and the default profile stages 5.
“OS Package Analyzers: apk, apt, yum, dnf, rpm, pacmanPacman is staged nowhere and cannot run.
“Runtime Signal Correlation — Zastava integration”No image is built, no compose service exists; only a test project consumes it.
“Script Steps (Bash/C#)”No execute route exists, the executor is unregistered, and Bash is never even syntax-validated.
“Freeze Window Gate” (promotion)Constructed only by its own test; absent from the live gate chain.
“Policy Gate (OPA/Rego)”The live gate is Stella policy packs, skipped when no pack is bound. No OPA runtime executes arbitrary Rego anywhere.
Plan Limits table (Free / Pro / Enterprise)There is one artifact set and the limits are not enforced by software. Presenting three tiers implies a product gate that does not exist.
Digest-first release identityTrue for runtime linkage and orchestrator content fetch; false at the deployment surface — 0 of 51 compose images are digest-pinned.

Rows that could not be verified, and why

Recorded rather than omitted, because a matrix that silently drops what it could not check is the failure mode this page exists to end.


Keeping this page honest