Stella Ops CLI architecture
Source ownership, 2026-10-03. PCD-253 withdrew the uncompiled
StellaOps.Cli.Tests/Commands/WatchlistCommandGoldenTests.csmock-formatting draft. It never called a CLI command; executable coverage stays insrc/Cli/__Tests/. See the per-file source disposition.
Verified against commit
942a18727c(2026-09-26, DOC-2 of SPRINT_20260926_001). Re-verify by reading the evidence paths listed in the Evidence section.
Scope. The
stella/stellaopscommand-line interface: solution layout, process model, configuration, Authority authentication, HTTP clients and egress policy, the command surface, restart-time plug-ins, exit codes, packaging, and the CI gates that keep documentation and code aligned. The authoritative command tree isCommandFactory.Createinsrc/Cli/StellaOps.Cli/Commands/CommandFactory.cs; when this page and the source disagree, the source wins.
CLI example gate. Living documentation is parsed against the released
CommandFactorytree and the manifest-backed restart-time plug-ins undersrc/Cli/plugins/cli/(CliDocumentationContractTests). Examples the parser rejects are explicitly classified as unshipped or stale inunshipped-command-examples.json; see the operator-readable policy. A baselined example is not runnable.
0) Mission and boundaries
The CLI drives Stella Ops workflows from a terminal or a CI job:
- scanning, SBOM handling and artifact inspection against the Scanner and SBOM owners;
- policy, gate, VEX, findings and risk operations;
- verification of attestations, DSSE envelopes, evidence bundles and signed manifests;
- air-gap and offline-kit administration;
- setup, tenancy, identity-provider and crypto/compliance settings for an installation.
Boundaries:
- Workflow signing is server-side (Signer and the Evidence-hosted Attestor surface). The local operations that sign are explicit commands (for example
crypto sign,deltasig sign,binary delta-sig attest,vuln-db export --sign vendor|tenant, and the local DORA RoI and NIS2 HMAC flows); each resolves keys through the cryptography registry or an explicit key input and fails closed without one. - Heavy work (scanning, matching, policy evaluation) runs in the services. The CLI is a client.
- The CLI owns no database and has no startup migrations. It is not a deployable service.
attest build records caller-reported SLSA v1 provenance through the existing Evidence sign, submit and exact-envelope export APIs. It keeps CI lineage separate from the authenticated producer and signing key, and refuses online success when the backend or retained bytes are unavailable. See the build-provenance contract; this source path does not prove live CI execution or authorize identity enrollment.
1) Solution layout
src/Cli/
|- StellaOps.Cli/ # net10.0 console app; assembly StellaOps.Cli.dll (`stella` / `stellaops`)
| |- Program.cs # DI composition, HTTP clients, invocation and exit-code precedence
| |- Commands/CommandFactory.cs # authoritative root command tree (CommandFactory.Create)
| |- Commands/*CommandGroup.cs # per-domain command groups
| |- Commands/CommandHandlers*.cs # shared handlers (CommandHandlers.Auth.cs holds auth helpers)
| |- Configuration/ # CliBootstrapper, StellaOpsCliOptions, AuthorityTokenUtilities,
| | # PolicyGatewayEndpoint, profiles, secret protectors
| |- Plugins/ # restart-only plug-in loader (CliCommandModuleLoader)
| |- Services/ # typed HTTP clients and local services
| |- Output/ # OutputFormat (table, json, yaml, sarif) and renderers
|- __Libraries/StellaOps.Cli.Plugins.*/ # restart-time plug-in assemblies
|- plugins/cli/<plug-in>/ # one *.manifest.json per shipped plug-in
|- __Tests/ # StellaOps.Cli.Tests plus focused test projects
# (AdviseParity, Commands.Setup, CompareOverlay, PluginIntegration,
# QaSeed, ScannerPlugins, UnknownsExport)
There is no StellaOps.Cli.Core project; configuration, HTTP and auth wiring live in StellaOps.Cli. The project targets .NET 10; Native AOT, single-file and trimming are not enabled in StellaOps.Cli.csproj.
Retired CLI source is frozen under src/__Obsoleted/Cli/ (the GroundTruth plug-in and command group, the air-gap sync group, the BinaryIndex ops group, the Postgres compact-VulnDB corpus source, and a Symbols plug-in predecessor). Those files are not compiled.
The CLI references implementation libraries of several other modules (see the ProjectReference list in StellaOps.Cli.csproj). Signer custody is consumed only through StellaOps.Signer.Contracts and the neutral StellaOps.Cryptography.* signing libraries; the CLI does not compile StellaOps.Signer.Core, Infrastructure, KeyManagement or Keyless.
2) Process model
Program.MainresetsEnvironment.ExitCode, builds configuration and options throughCliBootstrapper.Build(args), and records the global--tenantvalue for tenant resolution.- It composes a
ServiceCollection: air-gap egress policy (AddAirGapEgressPolicy), the crypto registry (AddStellaOpsCrypto, BouncyCastle Ed25519, and the regional providers compiled into the distribution throughCliRegionalCryptoComposition), logging, the Authority token client, and the typed HTTP clients (section 5). CommandFactory.Createbuilds the root command. The root has two global options,--verbose/-vand--tenant/-t, and setsTreatUnmatchedTokensAsErrors = true.CliCommandModuleLoaderloads the restart-time plug-ins (section 7) into the same root.CliInvocation.InvokeAsyncruns the parse result. A parse error returns exit code2(CliExitCodes.MissingRequiredOption). The process exit code isEnvironment.ExitCodewhen a handler set it, otherwise the command’s return value.
Startup Authority and crypto diagnostics are emitted only for verbose human-readable invocations (CliStartupDiagnosticsPolicy); structured output (--json, --raw, --format json) stays clean.
3) Configuration
Sources and precedence. CliBootstrapper uses StellaOpsConfigurationBootstrapper with binding section StellaOps. Sources, lowest to highest precedence: appsettings.json, appsettings.local.json, appsettings.yaml, appsettings.local.yaml (all optional, resolved from the current directory), command-line configuration arguments, then environment variables (no prefix). After binding, each option is resolved from an ordered list of keys; the first non-empty value wins.
| Setting | Keys (first match wins) |
|---|---|
| Backend (gateway) URL | STELLAOPS_BACKEND_URL, StellaOps:BackendUrl |
| Personal access token | API_KEY, StellaOps:ApiKey |
| Authority URL | STELLAOPS_AUTHORITY_URL, StellaOps:Authority:Url |
| Discovery document | STELLAOPS_AUTHORITY_METADATA_ADDRESS, STELLAOPS_AUTHORITY_METADATA_URL, StellaOps:Authority:MetadataAddress |
| Token endpoint override | STELLAOPS_AUTHORITY_TOKEN_ENDPOINT, StellaOps:Authority:TokenEndpoint |
| Client | STELLAOPS_AUTHORITY_CLIENT_ID (default stellaops-cli), STELLAOPS_AUTHORITY_CLIENT_SECRET |
| Operator login | STELLAOPS_AUTHORITY_USERNAME, STELLAOPS_AUTHORITY_PASSWORD |
| Identity provider and tenant for token requests | STELLAOPS_AUTHORITY_PROVIDER, STELLAOPS_AUTHORITY_TENANT |
| Default scope | STELLAOPS_AUTHORITY_SCOPE (unset by default: the CLI requests no scope and Authority grants the client’s ordinary login scopes; operation-bound permissions require an explicit scope request) |
| Token cache directory | STELLAOPS_AUTHORITY_TOKEN_CACHE_DIR (default ~/.stellaops/tokens) |
| Authority retries and offline cache | STELLAOPS_AUTHORITY_ENABLE_RETRIES, STELLAOPS_AUTHORITY_RETRY_DELAYS, STELLAOPS_AUTHORITY_ALLOW_OFFLINE_CACHE_FALLBACK, STELLAOPS_AUTHORITY_OFFLINE_CACHE_TOLERANCE |
| Service-specific URLs | STELLAOPS_ADVISORYAI_URL, STELLAOPS_VULNERABILITIES_URL, STELLAOPS_CONCELIER_URL, STELLAOPS_EVIDENCE_URL, STELLAOPS_POLICY_ENGINE_URL |
| Offline kits | STELLAOPS_OFFLINE_KITS_DIRECTORY, STELLAOPS_OFFLINE_MIRROR_URL |
| Plug-ins | STELLAOPS_CLI_PLUGIN_BASE_DIRECTORY, STELLAOPS_CLI_PLUGIN_DIRECTORY, STELLAOPS_CLI_PLUGIN_MANIFEST_PATTERN (default *.manifest.json) |
Each key also has the StellaOps:* configuration form listed in CliBootstrapper.cs.
Profiles. CliProfileManager keeps profile metadata in ~/.stellaops/profiles.json and profile secrets in ~/.stellaops/profiles.<name>.secrets.bin through the host secret protector (section 4.4).
Tenant. Every tenant the CLI sends, in the token request and in the X-StellaOps-TenantId header, comes from one rule, TenantProfileStore.ResolveTenant (section 4.2).
4) Authentication
4.1 Credential modes
- Personal access token.
API_KEY/StellaOps:ApiKeyis sent as a bearer token by the DI-registered clients (StellaOpsApiAuthMode.PersonalAccessToken); no Authority URL is required. - Operator login (password grant). With
STELLAOPS_AUTHORITY_USERNAMEandSTELLAOPS_AUTHORITY_PASSWORD, token requests use the OAuth2 password grant.auth loginprompts for missing credentials interactively. The default client is the seeded public clientstellaops-cli(StellaOpsCliAuthorityOptions.DefaultHumanClientId). - Client credentials. Without an operator login, token requests use the client-credentials grant with
STELLAOPS_AUTHORITY_CLIENT_ID/STELLAOPS_AUTHORITY_CLIENT_SECRET(for example the seededstellaops-cli-automationclient).
The token client (StellaOps.Auth.Client.StellaOpsTokenClient) implements only the password and client_credentials grants; device-code, PKCE and DPoP are not implemented in the CLI. The token endpoint comes from the Authority discovery document (cached by StellaOpsDiscoveryCache) unless STELLAOPS_AUTHORITY_TOKEN_ENDPOINT is set; then token requests go to that URL without fetching discovery. This lets a CLI outside the estate network work when discovery advertises an internal host.
4.2 Grant rule and tenant rule
AuthorityTokenUtilities is the single implementation used by every CLI client:
- Grant rule (
RequestTokenAsync): an operator login uses the password grant, otherwise client credentials, because the interactivestellaops-cliclient allows no client credentials. A command that names its own tenant (for example the--via-export-centerproduce runs) passes that tenant so the token matches its tenant header. Clients that call with several scopes cache one token per scope (GetCachedTokenAsync, keyed byBuildCacheKey: scope, provider, tenant and credential). With the base scope unset, Authority omitspolicy:publish,policy:promote,obs:incident,packs.approve,exceptions:approveandrelease:gate-bypassfrom password grants. These permissions belong to explicit operations with their required metadata or approval context; ordinary login does not request them. Explicit requests retain the existing checks. - Tenant rule (
TenantProfileStore.ResolveTenant, the only implementation): a command-scoped--tenant, thenSTELLAOPS_AUTHORITY_TENANT/StellaOps:Authority:Tenant, thenStellaOps:DefaultTenant, the global--tenant,STELLAOPS_TENANT, and the active profile. The first non-empty value wins and is lower-cased.ResolveAuthorityTenantis the rule without a command-scoped tenant; it is thetenanttoken-request parameter (and the shared token client’s default tenant). TheX-StellaOps-TenantIdheader of every client, DI-registered or not, and of every command handler uses the same rule, so without a command-scoped--tenantthe header names the token’s tenant. A command-scoped--tenantchanges the header only (a per-request tenant switch that the backend honours for global-admin principals;BackendOperationsClientwarns once when header and token tenant differ), except where the command passes it to the token request as well. With nothing configured no header is sent and the token’s tenant claim applies.STELLAOPS_AUTHORITY_PROVIDERis sent asauthority_provider. - DI registrations configure authentication only through
AuthorityTokenUtilities.ConfigureApiAuthentication(personal access token, then password, then client credentials).
These rules are pinned by CliClientTenantResolutionConformanceTests (every registration resolves the tenant through the rule), CliTenantHeaderConformanceTests (every source that sets the tenant header is classified; each client that resolves its own tenant sends the token’s tenant against a recorded network; no CLI source reads a tenant source outside TenantProfileStore), CliClientGrantParityTests (every client that requests its own token follows the grant rule; the shrink-only register of exceptions is empty) and CliRequestedScopeCatalogueConformanceTests (every scope the CLI requests exists in StellaOpsScopes; a scope outside the catalogue is refused by Authority with invalid_scope).
The standard local/dev lab uses STELLAOPS_AUTHORITY_PROVIDER=standard and STELLAOPS_AUTHORITY_TENANT=default. These are lab values, not product defaults.
4.3 Scopes
Each client requests the catalogue scope for its owner API. Examples:
| Client | Scope(s) |
|---|---|
SbomClient | sbom:read, sbom:write |
HubMirrorSeedClient | read advisory:read vex:read aoc:verify; write advisory:ingest vex:ingest |
CliLocalTransparencyRekorClient | attest:read |
| AI code guard client | integration:operate |
| Advisory AI chat client | advisory-ai:operate |
| Evidence bundle export | export.operator (create), export.viewer (status, download), evidence:read (portable release export) |
Release gate (PolicyGate.Evaluate, PolicyGate.Bootstrap, PolicyGate.Decision) | policy:run (gate evaluate), policy:run policy:write (gate evaluate --missing-baseline bootstrap: the gate refuses initial-baseline enrollment without write authority), policy:read (gate status), regardless of STELLAOPS_AUTHORITY_SCOPE |
Risk budget (Budget.Read, Budget.Consume) | policy:read (status, check, history, list), policy:operate (consume) |
registry scan | scanner:scan (submit, POST /api/v1/scans), scanner:read (status), minted for the command’s tenant |
registry list, images, show, cache reads, upstream list | registry:read, minted for the command’s tenant |
| Registry cache policy writes | registry:cache:admin |
| Registry upstream writes | registry.admin |
scan graph --upload | scanner.callgraph.ingest; stella.callgraph.v1 at POST /api/v1/scans/{scanId}/callgraphs |
The canonical scope catalogue is src/Authority/StellaOps.Authority/StellaOps.Auth.Abstractions/StellaOpsScopes.cs.
4.4 Token and secret storage
- Authority token cache.
auth logincaches the token through the token client’s file cache (FileTokenCache, registered byAddStellaOpsFileTokenCache) in the token cache directory: one JSON file per cache key, with file permissions restricted to the current user. These files are not encrypted by the secret protector. --no-save.auth login --no-save, orSTELLAOPS_LOGIN_NO_SAVE=1in a non-interactive shell, keeps the token in process memory only.- Profile secrets.
CliProfileManagerprotects profile secrets withSecretProtectorFactory: DPAPI on Windows, Keychain on macOS, libsecret on Linux, and a machine-id-derived AES-GCM fallback when libsecret is unavailable.STELLAOPS_REQUIRE_OS_SECRET_STORE=truerefuses the fallback. See credential storage. auth logoutremoves the cached token for the current credentials.
Service-client cache boundary (PCD-291). A prompted auth login does not enable the named clients used by gate, exception and release commands. They use configured authentication through ConfigureApiAuthentication: an API key, configured operator username/password, or confidential client credentials. StellaOpsBearerTokenHandler has its own client/scope/mode/tenant/credential cache namespace, separate from AuthorityTokenUtilities.BuildCacheKey. It neither consumes the prompted login entry nor exchanges its refresh token. Configure service credentials explicitly; auth logout clears the login entry rather than those independently scoped caches. The auth command guide and auth login --help describe this requirement.
5) HTTP clients and egress policy
Program.cs registers typed and named HttpClients. Each is wrapped by AddEgressPolicyGuard("stellaops-cli", <intent>), which checks the request against StellaOps.AirGap.Policy: in sealed mode a blocked destination fails with the shared AIRGAP_EGRESS_BLOCKED guidance instead of contacting the host. Command sources must not construct raw HttpClient instances (CliHttpClientsTests.Command_sources_do_not_construct_raw_http_clients).
Base-address resolution:
- Most clients use the backend (gateway) URL,
STELLAOPS_BACKEND_URL. - Authority clients (revocation, console, operator compliance, assurance readiness) use the Authority URL, or the backend URL when no Authority URL is set.
- Advisory AI chat uses
STELLAOPS_ADVISORYAI_URLwhen set, otherwise the backend URL. - Vulnerabilities-owner clients (binary facts, sources, mirror seed, build-id index export) resolve their base URL through their own
ResolveBaseUrlhelpers. - Policy-gateway calls (
gate evaluate,gate status,exception ...,score gate ...; thePolicyGateway,PolicyGate.EvaluateandPolicyGate.Decisionnamed clients) usePolicyGatewayEndpoint.Resolve:PolicyGateway:BaseUrl, thenSTELLAOPS_POLICY_ENGINE_URL, then the backend URL. There is no localhost default; with none set,gate evaluateexits2and namesSTELLAOPS_BACKEND_URL. - Local-transparency verification requires
STELLAOPS_EVIDENCE_URL(orStellaOps:EvidenceUrl) and maps it to/api/evidence/v1/(section 6.5). There is no fallback.
6) Command surface
The former admin policy and admin system groups and general admin users management verbs were withdrawn under PCD-292 because their routes were unserved. Local Authority recovery remains under admin users password-set and admin break-glass; admin diagnostics health reads Platform readiness. See the administrative reference.
PCD-293 withdrew sample configuration inventories (config feeds, config registry), tenant and audit administration (admin tenants, admin audit), and diagnostics connectivity/logs. config sources list reads the Vulnerabilities owner. The sample scan image and evidence audit groups were also withdrawn by the timestamp census; image inspect is the functional OCI inspector. There are no aliases for the retired paths.
6.1 Top-level groups
CommandFactory.Create registers the following top-level groups (names from each builder’s new Command(...)). Use --help on the root and on each group for verbs and flags; the detailed guides are under guides/.
- Scanning, SBOM and artifacts:
scanner,scan,image,registry,sbom,artifact,binary,deltasig,symbols,ruby,php,python,bun,plugin,guard,license. - Advisories, VEX and findings:
sources,vuln,vuln-db,vex,obs,observations,advisory,advise,advisoryai,search,decision,findings,db. - Policy, gating and risk:
policy,gate,score,risk,risk-profile,budget,exceptions,exception,unknowns,reachability,graph,witness,function-map,drift,change-trace,detscore. - Attestation, evidence and proof:
attest,attestor,verify,bundle,chain,proof,prove,replay,timeline,delta,evidence,forensic,seal,promotion,golden,verify-fix,ts. - Crypto, compliance and keys:
crypto,compliance,kms,key,issuer,trust-profile,assurance,incident,regulatory,tlpt,export. - Platform, tenancy and operations:
setup,auth,tenants,idp(aliasidentity-providers),admin,config,router,notify,doctor,self,tools,qa,watchlist,inventory,cvss. - Release orchestration:
release,deployment,agent. - Integrations and distribution:
scm,github,ci,api,sdk,devportal,mirror,airgap,offline.
scan graph is attached to scan after the root is built (ScanGraphCommandGroup).
6.2 Auth and profile
auth login(--forceignores the cache,--no-savekeeps the token in memory),auth status,auth whoami,auth logout.auth revoke export|verifyexports the Authority revocation bundle with its detached JWS, or verifies a bundle offline against a PEM key.auth token mint --service-account <id> --scope <scope> [--expires-in] [--tenant] [--reason] [--raw]andauth token delegate --to <id> ...issue service-account tokens.--rawprints only the token.
6.3 Scanning and SBOM
scansubcommands:run,upload,entrytrace,sarif,replay,layers,layer-sbom,recipe,diff(binary diff),delta,verify-patches,workers get,image(inspect,layers),submit-list, andgraph.scan graphoffers Go through the packagedstella-callgraph-goexecutable and Rust through the in-processRustCallGraphExtractor.
Scanner management uses scanner download (--channel, --output, --overwrite, --no-install), scanner workers get|set and scanner secrets bundle import|verify|list|info. Worker set takes --count and optional --pool; both worker commands support --format. Bundle import, verify and info use --bundle <directory>; import/list use --store for the local bundle store. These paths perform their owner/local artifact operations. The simulated scan download, scan workers set and scan secrets group are withdrawn and rejected by the parser.
scan workers get remains a read-only view of the local worker configuration. Both worker-get paths require an existing configuration to contain an object with a positive integer count and string pool; malformed or unreadable inputs and directory paths fail without invented configuration output. A missing file is explicitly unconfigured and uses the default count/pool. Local configuration reads/writes do not establish the state of deployed workers. scan run uses the same reader when --workers is absent and rejects invalid fallback input before resolving the scanner executor or backend. An explicit positive --workers value bypasses that local file lookup. Scanning-management source verified against commit 9930a0528e2e7e8e5e36d5cfbf1e895c814bd73c (PCD-390). Re-verify with pwsh ./tools/scripts/test-targeted-xunit.ps1 -Project src/Cli/__Tests/StellaOps.Cli.Tests/StellaOps.Cli.Tests.csproj -Class '*ScanningManagementCommandTests' -BuildProjectReferences -MaxThreads 1.
scan sarif --scan-id <id> [--output <file>] [--pretty] reads Scanner’s SARIF export with scanner:read. The CLI preserves the owner’s document and metadata. The export has no hardening, reachability-inclusion or minimum-severity switches (PCD-297).
sbomsubcommands:check,list,upload,show,compare,export,generate,attach,inspect, and the verification, license-check and NTIA-compliance subcommands fromSbomVerificationCommandGroup.sbom listcallsGET /api/v1/sbom/catalogand fails non-zero on any non-success response.- There is no
buildxgroup, noreport finalcommand, and no top-levelcomparegroup (Commands/Compare/CompareCommandBuilder.csis not registered in the root;sbom compare,decision compareare registered compare verbs; there is noscore compare).
AI code guard. guard run <path> is a bounded source collector and typed client for the Integrations-owned POST /api/v1/integrations/ai-code-guard/run contract. It requests integration:operate and propagates the effective tenant. JSON is the canonical response; text, SARIF and GitLab are projections. Pass exits 0; warnings, failures, pending or error responses, invalid input and unavailable service paths are non-zero. Collection prunes generated and dependency directories, skips reparse points, keeps paths under the requested root, and fails closed on traversal, file, policy or aggregate-payload bounds. guard status reports contract ownership and does not claim a live health check. --sealed fails closed because no verified offline analyzer bundle is registered.
6.4 Policy, advisories and data
- PCD-448 withdrew the top-level artifact block explanation command and both unsupported lookup branches. Policy’s cached read is keyed by decision ID; its durable digest read returns a status summary without an explanation, trace, evidence list or replay token. Evidence supplies no digest-to-verdict cache provider. The real command parser now returns usage-error exit code 2 without making requests or writing output. The original source and copied-helper tests are frozen under
src/__Obsoleted/Cli/; other owner command groups remain. See the contract boundary and recheck command. score replay|bundle|verify --scan <id>uses Scanner’s/api/v1/scans/{scanId}/score/{replay,bundle,verify}owner contract. DedicatedScanner.Score.Replay,Scanner.Score.BundleandScanner.Score.Verifyclients useBackendUrl, authentication, effective tenant and egress protection. Replay and verify requestscanner:read scanner:write; bundle requestsscanner:read. Replay retains the factor vectors, canonical input binding and verification status. Bundle returns metadata only. Verify accepts an expected root and optional bundle/canonical binding; HTTP 200valid:falseexits2, while unavailable providers and malformed responses fail without fabricating a result. The canonical payload option reads bounded UTF-8 JSON and preserves the exact text for owner hashing.score explain <digest>remains the Signals/api/v1/score/explain/{digest}read throughScore.Read(score.read). See the score replay guide. PCD-429 withdrew the unregisteredScoreCommandGroupduplicate and its shape-only tests; their frozen sources remain undersrc/__Obsoleted/Cli/. Its liveScoreExitCodesconstants were retained.budget status|consume|check|history|listuses Policy Engine’s/api/v1/policy/budgetcontract throughBudget.ReadandBudget.Consume, configured fromBackendUrlwith egress protection. Per-invocation authentication requests the operation scope; the command then applies its effective tenant, preserving a local--tenantoverride. Status preserves the owner’s named tier and update timestamp; check sends release category/context for owner scoring; consumption requires a release ID. History’s limit is text-only after a full window read. The owner’s hardcoded empty listing fails with inventory unavailable and cannot prove that no budgets exist. See the budget guide.config sources list|status [--format table|json] [--json]reads the Vulnerabilities owner’s source-generation state throughGET /api/vulnerabilities/v1/sources. API failures and malformed responses fail closed. The top-levelsourcesgroup keepsstatusonly; source administration is configuration-driven and has no CLI mutation verb.sources ingest --dry-runand theaoc verifygroup were withdrawn on 2026-10-01: they posted toapi/aoc/ingest/dry-runandapi/aoc/verify, routes no service maps. Their sources are frozen undersrc/__Obsoleted/Cli/StellaOps.Cli/PCD-153/.vex explain <vulnerability-id> --product-key <key> [--json]reads the issue’s live statements from the Vulnerabilities hub (GET /api/vulnerabilities/v1/issues/{id}/{productKey}/facts,advisory:read) and evaluates them with the shared consensus evaluator: global consensus, default issuer tiers, no tenant VEX decisions. Hub failures and contract violations exit 1 with nothing on standard output. InclusivelastAffectedis retained in statements, affected ranges and decision hashing; text identifies the endpoint as inclusive and never labels it as a fix. A malformed bound refuses the whole response. Thevex consensus,simulate,export,obs,providersandgate-scancommands andscan gate-policy|gate-resultswere withdrawn on 2026-09-30: they called routes no service serves, or a gate-results route that always answers 501. PCD-444’s bound carry is source-verified against87feffb908; re-verify withpwsh tools/scripts/test-targeted-xunit.ps1 -Project src/Cli/__Tests/StellaOps.Cli.Tests/StellaOps.Cli.Tests.csproj -Class "*VexExplainCommandTests" -BuildProjectReferences.excititor ...is contributed by the NonCore plug-in (section 7).db fetch|merge|exportis registered and posts to the backendjobs/{kind}route. No current service maps that route (the Concelier job surface is retired); these verbs are not verified against a live owner.decision verify|compareworks on local VEX decision documents: verify the DSSE signature (with--public-key) and optional Rekor inclusion, and compare two documents. Without--public-keya present signature is reported as not verified.decision exportwas withdrawn on 2026-09-30: itsapi/v1/decisions/exportroute is not served.inventory list|show|exportlists Graph asset inventory. With--input <asset-registry.json>the commands read only the pinnedasset-registry.v1file offline; otherwise they callPOST /graph/assets/query,GET /graph/assets/{assetId}andPOST /graph/assets/export. Missing configuration, runtime errors, schema or digest mismatches and repeated cursors fail closed.assurance packs list,assurance pack inspect <packId>(embedded descriptors, no network) andassurance pack status <packId> --tenant <tenant>(live Authority, ExportCenter and Notify readiness; fails closed with stable reason codes). Descriptors covernis2,dora,cra.product-securityandcra.technical-documentation. See stella assurance.qa seed <fixtureId> --manifest <fixture.json> --output <path>materialises the deterministicadvanced-assurance-goldenseed artifacts offline. It does not populate live databases.tenants locale list|get|setreads the Platform locale catalogue (GET /api/v1/platform/localization/locales) and reads or writes the actor’s language preference (/api/v1/platform/preferences/language), shared with the Web shell locale selector.unknowns export [--band] [--format json|csv|ndjson] [--schema-version] [--output]writes a deterministic envelope; the JSON contract issrc/Cli/StellaOps.Cli/Commands/Schemas/unknowns-export.schema.json.advise run|ask|chat-doctor|chat-settings|exportcalls Advisory AI (/v1/advisory-ai/pipeline/{task},/api/v1/chat/query,/api/v1/chat/doctor,/api/v1/chat/settings,/v1/advisory-ai/conversations). Model readiness is server-owned; the CLI needs no local model keys. Transport failures and malformed responses fail non-zero. The chat verbs (ask,chat-doctor,chat-settings get|update|clear,export) take the command-scoped--tenantof section 4.2 and offer no option that Advisory AI ignores: there is no--user(Advisory AI takes the user from the gateway envelope’s subject and stripsX-User-Id), noask --no-action/--evidence(the chat query contract has neither field) and nochat-settings get --scope(the settings read always returns the effective settings).
6.5 Verification
verifysubcommands includeattestation,image,offline,bundle,vex,patch,sbom,sar,offline-seed,release,release-manifest,product-csaf-advisory,nis2-effectiveness, and the compliance verifierscra-tech-file,dora-roi,dora-incident-report,dora-info-sharingandtlpt-pack.verify release-manifestandverify product-csaf-advisoryverify Signer DSSE envelopes offline against local PEM trust roots. Without--trust-root,verify release-manifestuses the pinnedmanufacturer-releasevendor roots, asself updatedoes.verify nis2-effectivenesschecks the local bundle schema, hashes, DSSE payload bytes and HMAC signature without network access.attest verifyaccepts--algorithm/-A(ES256,PS256,GOST12-256,GOST12-512,SM2) for regional DSSE envelopes. Omitting it keeps key-derived EC/RSA verification. Regional keys are PEM SubjectPublicKeyInfo blocks; SM2 uses the default user id1234567812345678. The command is verification-only.
stella attest verify \
--envelope ./scan-attestation.dsse.json \
--root ./sm2-public.pem \
--algorithm SM2 \
--format json
Regional builds compose extra providers at startup: StellaOpsEnableGOST=true adds BouncyCastle, OpenSSL, PKCS#11 and CryptoPro GOST providers (configuration sections StellaOps:Crypto:OpenSsl, StellaOps:Crypto:Pkcs11, StellaOps:Crypto:CryptoPro); StellaOpsEnableSM=true adds the SM software provider and the cn.sm.remote.http remote provider bound from StellaOps:Crypto:SmRemote (the SmRemote plug-in also binds StellaOps:Crypto:Profiles:sm-remote).
Local-transparency owner verification. When StellaOps:Transparency:Local:Enabled=true, decision verify --rekor calls the Evidence owner at STELLAOPS_EVIDENCE_URL + /api/evidence/v1/rekor/entries/{uuid}?refresh=true with API_KEY or an Authority token for attest:read. The CLI verifies the payload-to-leaf binding and Merkle path locally and reports checkpoint-signature validity separately. A missing URL, missing credential, unavailable owner, malformed response or invalid proof fails closed; the CLI never opens a database and has no fallback route. See the EVD-9 data-move procedure.
Attestor transparency controls. attestor transparency targets list|show|upsert|enable|disable, attestor transparency sync run|report and controlled-media exchange call api/evidence/v1/transparency/* on the backend URL. Authority token requests use attest:read for the six GET operations and attest:create for the eight POST operations, independently of the configured base scope. Owner unavailability, refused requests and empty required responses remain failures; target/report/batch lookups preserve the owner’s resource-not-found result. Target creation accepts only secret references in --auth-ref. Live sync is refused in sealed mode and requires --confirm-egress-window. airgap exchange transparency ... (also under attestor transparency exchange) is the controlled-media exchange path; the CLI writes the package bytes and a stellaops-transparency-package.json descriptor and generates no keys. These external controls use the Evidence host’s served paths directly. attestor transparency receipts list|show uses GET /api/evidence/v1/attestations and GET /api/evidence/v1/attestations/{uuid} on the backend URL, with the explicit attest:read scope independently of the configured base scope. Both commands preserve local receipts, additive external mirror receipts and proof diagnostics in JSON. An empty list is successful; a missing detail returns exit 4. Refused or unavailable owner calls return exit 1. --refresh requests a best-effort proof refresh for an existing receipt and does not guarantee fresh proof or create an unknown receipt. See the receipt reference for filters and paging. PCD-465 verifies the caller contract against owner source and capture; the hermetic tests do not establish deployed routing, proof validity or owner tenant isolation.
Receipt caller verification: commit 6aff2f8ece7ccdfaeee4a237c9f17a24713df050 (2026-10-07). Re-verify with pwsh ./tools/scripts/test-targeted-xunit.ps1 -Project src/Cli/__Tests/StellaOps.Cli.Tests/StellaOps.Cli.Tests.csproj -Class '*Receipt*ContractTests' -BuildProjectReferences.
6.6 Truthfulness requirements for evidence commands
Local replay proof. prove --image <digest> --bundle <directory> requires a supplied local manifest and compares actual VerdictBuilder evidence-hash replay with its expected CGS hash. The complete SHA-256 or SHA-512 image digest must match the manifest’s normalized image identity; a different image fails before emitting a proof. The command reads one structurally checked manifest, confines declared paths to the selected bundle root, rejects child filesystem links, leaves bundle files unchanged and makes no provider request. Its unsigned proof does not evaluate policy or establish signature validity. See the local prove reference. PCD-430 withdrew timestamp/latest selection; PCD-431 withdrew --snapshot/-s and the bundle-store adapter after owner review found no compatible verdict snapshot/store contract. Timeline events, Replay advisory/feed snapshots and the AuditPack producer remain independent. Both unsupported adapters, the original shared command/contracts and its shape tests are preserved as whole files under src/__Obsoleted/Cli/; the registered command now lives in ProveLocalCommandGroup.cs.
Evidence-oriented commands fail closed when the verifier, trust store, transparency log or service interface needed to prove a claim is unavailable. They must not print success, create placeholder bundles, or emit unsigned or synthetic DSSE envelopes. Current behaviour:
binary export-index --output-directory <new-directory>streams the hub’s build-id artifact, signs its digest/length descriptor with thegeneric-dssepurpose key and publishes the directory atomically. See export and enrollment.deltasig sign|verifyandbinary delta-sig attestresolve the enrolledgeneric-dssepurpose key through the cryptography library for the selected tenant; there is no raw-key override or fallback key. Localdeltasig extract|author|match|pack|inspectandbinary delta-sig diff|verify|gateuse theStellaOps.Vulnerabilities.BinaryArtifactsSDK and installed signed analyzer bundles.binary submit --signexits9and states that it did not sign; unsigned submission remains available without--sign.attest patchrequires--no-rekorand a local--key;--publish, Rekor publication and keyless signing return explicit failures.attest sign|linkemit standard DSSE envelopes over canonical in-toto statements with the enrolled tenantgeneric-dssepurpose key and the active profile’s configured provider. A canonical tenant UUID is required; optional--keyasserts the enrolled key ID and never selects a private-key path. Missing custody, unavailable providers and mismatched key IDs fail without an envelope. Onlydsseoutput is supported; keyless signing, Rekor submission, timestamp requests,--tsaand other formats fail before custody access or output.attest verify --require-timestampchecks timestamp metadata presence only. Thets rfc3161|verify|infogroup is a prototype, not a production TSA client. Signing-constructor verification: commit2f605fd6bc8933ee4ce4ecd0f4488b4e4329dca0(2026-10-07 UTC). Re-verify withpwsh ./tools/scripts/test-targeted-xunit.ps1 -Project src/Cli/__Tests/StellaOps.Cli.Tests/StellaOps.Cli.Tests.csproj -Class '*AttestationSigningCommandTests' -BuildProjectReferences -MaxThreads 1.attest fixchainaccepts analyzer identity only from trusted metadata (--analyzer-metadatawith--analyzer-metadata-digest sha256:<hex>) or the explicit manual fallback (--analyzer-source-digest,--analyzer-name,--analyzer-version); it never emitssha256:unknown.reachability explain|witness|guards,reachability graph list|show|sliceandreachability slice create|showreturn exit9and write nothing.reachability show|export(local files) andreachability trace(Scanner) are implemented.reachability witness-ops list|show|verifyuses the serving Scanner record contract with canonicalscanner:read, one supported list selector and the shared tenant resolver. Record GUID, witness hash and nested payload identity remain distinct; missing data and non-successful verification never become a successful verdict. Unsupported filters, paging, offline/key modes and export refuse before requests or output. The separate binarywitnessgroup is unchanged. See the witness-record guide for source qualification and later packaged/live limits.config signals inspect|list|summaryreturn exit9;config signals verify-chainverifies signed evidence files on disk.mirror createreturns exit9(mirror_create_not_implemented) and writes no files; usemirror seed export.sbom generateand golden-setverify-fixreturn non-zero instead of writing placeholder output until their owner contracts are wired.scan verify-patches --scan-id,witness generateand proof key-rotation commands fail non-zero when their backing service is not registered.- Identity watchlist definition CRUD and sample-pattern tests use the configured Evidence API under
/api/evidence/v1/watchlist, with resolved tenant and read/write authority. Missing configuration, denial, unavailable owner and invalid responses return non-zero. Updates retain stored tags and channel overrides. Alert-history commands are withdrawn; definitions do not establish active monitoring. See the definition guide. - Scanner runner execution treats a missing output artifact as a failed run even when the child process exits 0.
6.7 Offline kit, mirror and air gap
offline importandoffline statusare core. The NonCore plug-in addsoffline kit statusto the sameofflinecommand;offline kit pullandoffline kit importwere deleted.- The core
airgapgroup carriespull --run <id>,import --bundle <path>,seal,unseal,status,diffandtime-anchor. mirror seed export|validate|import|downloadproduce and move mirror bundles. Thefeedserfederation group was removed on 2026-09-26: its/api/v1/federation/*export, import and sites routes were served only by the frozen Concelier host and are retired at the gateway.- Mirror seed export and copy stage beside the destination. On a transient Windows access denial, the directory rename retries after up to seven waits totaling 2.575 seconds while staging exists and the target is absent; persistent denial fails without replacing an existing target (PCD-123, verified against
wt/pcd-123at6807ac2c6b).
6.8 vuln-db — compact offline vulnerability database
vuln-db export (Commands/VulnDb/VulnDbCommandGroup.cs) reads a retained, digest-addressed publication from the Vulnerabilities owner and writes the compact, offline-matchable database that sbom check consumes. It is the only verb; the group description reserves update and status.
Compact schema v8 preserves repository_url on affected rows. Component expansion retains the same decoded qualifier in both scoped and unscoped SBOM paths; matching compares it exactly, so a repository-qualified advisory cannot match a package with a different or unknown repository. Earlier compact exports lack that identity and are refused with a request to export a fresh v8 database. Repository identity participates in the projected content digest and is retained in JSON/SARIF findings and reconstructed PURLs used by VEX/reachability lookup.
The inclusive lastAffected bound travels from consensus-inputs v4 through compact affected-range expressions (<=) and mirror-seed applicability. It never enters the declared fixed-version list. Matcher-rows v5 and consensus-inputs v4 advance independently; unchanged exploit/reachability section formats retain their own window.
PCD-445 carries introducedExclusive into decision hashing, VEX explanation statements/ranges and mirror seeds. Compact schema 8 carries nullable comparison_scheme separately from the purl ecosystem. generic-range uses the shared neutral comparator; generic-exact is a complete literal, not an ordered singleton interval. Empty declared generic intervals are preserved. Mixed comparison schemes and conflicting endpoint/expression declarations refuse export. A database missing the current comparison column or current schema stamp requires a fresh export. Unsupported source comparison remains unknown without invented remediation, including beside a known-outside row. Declared generic operands bypass RPM NEVRA rewriting. Ordered endpoint atoms are validated before formatting, so embedded whitespace or operators cannot turn one source endpoint into two constraints. Retained OSV generic-exact also labels ECOSYSTEM ranges; those claims keep their existing ecosystem path and omit a newly inferred literal override pending the source producer’s separate correction.
PCD-445 source verified against
56357b8682799c029045a161491eabab422373e8(2026-10-08 UTC). Re-verify:pwsh tools/scripts/test-targeted-xunit.ps1 -Project src/Cli/__Tests/StellaOps.Cli.Tests/StellaOps.Cli.Tests.csproj -All -BuildProjectReferences.
PCD-334 source verified against
6d3401cdc5(2026-10-06). Re-verify:pwsh ./tools/scripts/test-targeted-xunit.ps1 -Project src/Cli/__Tests/StellaOps.Cli.Tests/StellaOps.Cli.Tests.csproj -Method "*LastAffected*" -BuildProjectReferences -MaxThreads 1.
PCD-332 source verified against
1e52c13050(2026-10-06). Re-verify:pwsh ./tools/scripts/test-targeted-xunit.ps1 -Project src/Cli/__Tests/StellaOps.Cli.Tests/StellaOps.Cli.Tests.csproj -Method "*RepositoryIdentity*" -BuildProjectReferences -MaxThreads 1.
| Option | Default | Meaning |
|---|---|---|
--output / -o | devops/offline/vuln-db, or STELLA_VULN_DB_EXPORT_DIR | Output root; a <db-version> subdirectory is created. |
--db-version | UTC timestamp | Version label and subdirectory name. |
--sources | all ecosystems | Comma-separated ecosystem allow-list. |
--max-memory-mb | auto | Memory envelope for the build. |
--format | sqlite | sqlite, bundle (adds a deterministic tar.zst) or corpus (raw matcher section). |
--include-sharealike-companion | off | Also emit the segregated CC-BY-SA companion database. |
--with-descriptions | refused | Fails loudly; see docs/legal/decisions/compact-vulndb-license-segregation-counsel-thread-reply.md. |
--sign | off | vendor signs the intelligence manifest with the vendor intelligence key (purpose vendor-intelligence); tenant signs it with the tenant’s EvidenceBundle purpose key and writes the tenant public JWK beside it. |
--vendor-key-id | required with --sign vendor | The vendor key’s root-set identifier (sha256: of its SubjectPublicKeyInfo). |
--vendor-key-file | provider-held key | A runtime-mounted vendor key file; needs --vendor-key-provider. |
--vendor-key-provider | profile order | The crypto provider that holds or loads the vendor key. |
--vendor-roots | STELLAOPS_VENDOR_TRUST_ROOTS | The vendor trust-root set the vendor-signed envelope must verify against before the export is kept. |
--from-hub | required | Base URL of the Vulnerabilities owner. |
--token | STELLA_VULN_DB_HUB_TOKEN | Bearer token; needs advisory:read aoc:verify. |
--generation | current publication | Require this sourceGeneration. |
The CLI downloads the publication sections by pinned byte ranges and checks each section’s size, SHA-256, format version and same-origin URL before projection. The export writes <output>/<db-version>/ (vuln-db.sqlite, manifest, optional companion, optional bundle), repoints <output>/latest, and fails if latest does not resolve to vuln-db.sqlite. Every export also writes intelligence-manifest.json, which lists each file with its size and SHA-256 and the hub snapshot (source generation, publication time and manifest digest) the export was built from (contract). With --sign, the DSSE envelope intelligence-manifest.dsse.json covers it (payload type application/vnd.stellaops.vuln-intelligence-manifest.v1+json). A vendor signature is verified against the vendor trust-root set before the export is kept; a tenant signature is attributable to the tenant only. The console summary prints the snapshot generation and publication time, and the signer mode and key id. With --format bundle the archive is written after signing and carries the databases, their manifests, the intelligence manifest and its envelope.
Exit codes: 0 success; 1 command or local export failure; 2 hub unreachable or non-success HTTP; 3 corpus contract refused; 4 empty corpus.
6.9 Crypto profiles, installation settings and tenant overrides
crypto profiles list|show|select manage the regional profile (international, fips, eidas, gost, sm). select persists the choice: with a backend URL it calls PUT /api/v1/admin/crypto-providers/preferences; without one it writes ~/.stellaops/appsettings.crypto.yaml (StellaOps:Crypto:Registry:ActiveProfile). In both modes it appends an entry to ~/.stellaops/audit/crypto-profile-select.jsonl.
| Exit code | Condition |
|---|---|
0 | Persistence succeeded. |
10 | Local YAML file read-only or permission denied. |
11 | Platform endpoint unreachable or timed out. |
12 | Platform returned 401/403. The hint text names stella auth login. |
13 | Unknown profile name. |
14 | Other persistence error. |
Installation-scoped settings mirror the setup wizard and require a Platform backend (GET /platform/envsettings/db/, PUT /platform/envsettings/db/{key} with UpdatedBy=stella-cli):
| Command | Key written or read |
|---|---|
| `stella crypto provider set <international | eidas |
stella crypto provider show | Crypto:Region |
stella crypto algorithm set --provider <provider> --signing <algorithm> | Crypto:<provider>:Algorithm |
stella crypto fips enable | Crypto:FipsMode=true after confirming Crypto:Region=international |
stella crypto fips disable | Crypto:FipsMode=false |
stella crypto status | Crypto:* plus NIS2/DORA/CRA enabled flags |
| `stella compliance nis2 enable | disable` |
| `stella compliance dora enable | disable` |
| `stella compliance cra enable | disable` |
stella compliance status | NIS2/DORA/CRA enabled flags |
Tenant-scoped variants call /api/v1/admin/crypto-providers/compliance-profile and /api/v1/admin/crypto-providers/compliance-overrides with the tenant header:
| Command | Platform state |
|---|---|
| `stella crypto profile set --tenant | fips |
stella crypto profile show --tenant <id> | active tenant profile row |
| `stella compliance nis2 enable | disable --tenant |
| `stella compliance dora enable | disable --tenant |
| `stella compliance cra enable | disable --tenant |
stella compliance status --tenant <id> | merged effective tenant pack state and source |
Authorization failures return 12; other Platform failures return 14.
6.10 Evidence bundle export and OCI referrers
evidence export <bundle-id> --output <file>,evidence export --release <release-id> --output <file>andevidence status <export-id> --bundle <bundle-id>use the Evidence API (scopes in section 4.3).evidence card exportis withdrawn.evidence verify-offline <bundle.tgz> --trust-root <jwks-or-spki-pem> [--output json]verifies an exported portable bundle with no network access: proof presence, the archive-local checks ofEvidencePortableBundleOfflineVerifier, the signature over the signed bundle payload against the declared trust root, and the identity read from that payload. Each failure category has its own exit code (0 verified, 1 integrity_failed, 2 input_unavailable, 3 proof_missing, 4 trust_root_missing, 5 trust_root_invalid, 6 signature_invalid, 7 signature_untrusted); see Evidence offline verification.evidence push-referrer --image --artifact-type --file [--annotation] [--offline]builds an OCI image manifest with asubjectand pushes it throughIOciRegistryClient;evidence list-referrers --image [--digest] [--artifact-type] [--format] [--offline]lists referrers. With--offline, push prints the manifest without contacting a registry and list returns simulated data; neither is evidence.
6.11 Release orchestration
release exposes the operator decision verbs approve, reject, promote, deploy, rollback and show; deployment exposes list, show, logs, events, watch; agent exposes list, show, token create and token list. Release creation/listing and environment lifecycle are deliberately Console/API-only for RC1: use /api/v1/release-orchestrator/releases, /api/v1/release-orchestrator/environments, or the authenticated Console setup/release pages. There is no stella release create|list or stella env group in RC1.
6.12 Utilities
api spec list|downloadconsumes the Gateway’s runtime OpenAPI composition. The client lists per-service status/spec envelopes from/api/openapi/aggregate; merged JSON/YAML downloads use/openapi.jsonand/openapi.yaml. A service download extracts only that registered service’s specification. Fresh retrieval and cache reuse are explicit in the result, and failed retrieval or verification preserves existing output. The same client servesadvisoryai sources prepare. This runtime composition is distinct from the curated staticsrc/Apicatalog; details and the loopback owner-contract rerun are in the API command guide.idp list|show|add|update|remove|test|enable|disable|applyadministers the Authority identity-provider store through/console/admin/identity-providers. The same group is registered at the root (aliasidentity-providers) and underconfig. Operator-configurable types areldap,oidcandsaml; secrets accept onlyenv:,file:,vault:orsecret:references.config show|listplus the settings groupsconfig notify|integrations|feeds|registry|sources|signals|idp. There is noconfig setorconfig get.self update --check|--dry-runverifies a signed local product update manifest and prints a read-only plan. Manifest/trust inputs are required; version and release content come only from the verified manifest. Trust roots come from--trust-root,--trusted-public-key-pemor theSTELLAOPS_PRODUCT_UPDATE_TRUST_ROOT/STELLAOPS_RELEASE_TRUST_ROOTvariables, or, when none is given, from the pinnedmanufacturer-releasevendor roots (STELLAOPS_VENDOR_TRUST_ROOTS, defaulttrust/vendor-roots/beside the binary;docs/contracts/vendor-trust-root-set-v1.md). A root in the manifest’s directory, itstrust-roots/folder, the Offline Update Kit or the working directory is never loaded, so a kit cannot vouch for itself. Optional--versionasserts that manifest version. Application and--forcefail nonzero before package or health claims; no installed/latest inventory or rollback is fabricated. There is no top-levelversioncommand. Manifest-check source verified against commite25dab6fa702d22df9ddcf0445310645cbbc2c11(PCD-389); trust-root resolution re-verified 2026-10-27 forSPRINT_20261027_005VTR-2 and 2026-10-28 for VTR-1. Re-verify withpwsh ./tools/scripts/test-targeted-xunit.ps1 -Project src/Cli/__Tests/StellaOps.Cli.Tests/StellaOps.Cli.Tests.csproj -Class '*AgentSelfUpdateVerifyTests' -BuildProjectReferences -MaxThreads 1.tools lint|benchmark|migrateare local policy and configuration utilities.kms export|importmove file-backed KMS key material; the passphrase comes from--passphrase,STELLAOPS_KMS_PASSPHRASE, or a prompt.ci init|list|validategenerate and list CI workflow templates for GitHub, GitLab and Gitea (CiCommandGroup.cs,CiTemplates.cs);ci validateonly checks that the path exists and does not parse the workflow.ci initselects the workflow steps with--template(gate,scan,verify, orfull; defaultgate); gate templates include build provenance. The installer uses the published CLI image by qualified sha256 digest. See the CI/CD gate flow.runtime policy testis contributed by the NonCore plug-in (section 7).
7) Restart-time plug-ins
CliCommandModuleLoader reads *.manifest.json files under <base>/plugins/cli/ and loads assemblies matching StellaOps.Cli.Plugin.*.dll / StellaOps.Cli.Plugins.*.dll. Plug-ins load at startup only; hot reload is not supported (RestartOnlyCliPluginGuard). Before each module registers, the loader snapshots the existing root tokens and removes any new root command whose name or alias already exists, so a plug-in cannot shadow a core command.
Shipped plug-ins (one manifest each under src/Cli/plugins/cli/): NonCore, Policy, Symbols, Timestamp and Verdict. Their policy, symbols, ts and verify roots duplicate core roots and are skipped. The Aoc and Vex plug-ins, whose aoc and vex roots were always skipped the same way, were withdrawn to src/__Obsoleted/Cli/; the core aoc group was withdrawn on 2026-10-01 as well. The effective additions come from NonCore: excititor (init, pull, resume, list-providers, show-provider, enable-provider, disable-provider, run-provider, update-provider, export, backfill-statements, verify, reconcile), runtime (runtime policy test), and offline kit status added to the core offline command.
src/Cli/__Libraries/StellaOps.Cli.Plugins.DeltaSig/ has no manifest and is not built by the Dockerfiles or build-cli.sh; it is not a shipped plug-in. The GroundTruth plug-in was withdrawn (frozen under src/__Obsoleted/Cli/).
8) Exit codes
Exit codes are family-specific; read the handler before asserting an exact code.
- Parser errors (unknown command or option, missing required argument):
2. Help and version:0. - Sign/verify/doctor families (
Commands/CliExitCodes.cs):0success,1input file not found,2missing required option,3service not configured,4signing failed,5verification failed,6policy violation,7file not found,8general error,9not implemented,99unexpected error,100doctor failed,101doctor warning. - Aggregation-only guard family: withdrawn on 2026-10-01 with
sources ingestandaoc verify; no command returns its11-18violation codes. - Gate evaluation: a structurally valid fail verdict returned as HTTP
403renders the result and exits1; an unreachable endpoint exits11. - Crypto profile and settings:
10-14(section 6.9). vuln-db:0-4(section 6.8).
9) Output
OutputFormat defines table (default), json, yaml and sarif; each command declares which of them it accepts. JSON output is intended for automation and golden tests. JSON documents go directly to standard output (Console.WriteLine or Console.Out), including JSON errors. They bypass the Spectre renderer, so redirecting output or using a narrow terminal does not insert line breaks into string values. Human-readable tables and status messages use the renderer.
10) Packaging and distribution
- Published CLI image.
.gitea/workflows/stella-cli-publish.yml(manualworkflow_dispatch) buildsdevops/stella-cli/Dockerfileand pushesregistry.stella-ops.org/stella-cli. The image is a framework-dependent publish ofStellaOps.Clion the .NET 10 ASP.NET runtime, runs as a non-root user, carries the BUSL-1.1 licence and third-party notices under/usr/share/doc/stella-cli/, and has thestellaentrypoint. It does not include the restart-time plug-in tree. - Source-free operator image with plug-ins.
devops/docker/Dockerfile.clipublishes the CLI, builds the seven plug-in projects, copiessrc/Cli/plugins/cli/to/app/plugins/cli, setsSTELLAOPS_CLI_PLUGIN_BASE_DIRECTORY=/app,STELLAOPS_CLI_PLUGIN_DIRECTORY=plugins/cli, the token cache/state/tokensand offline kits/state/offline-kits, and installsstellaandstellaopsentrypoints..gitea/workflows/cli-plugin-integration.ymlbuilds it for its help smoke checks. - Compose runner.
devops/compose/docker-compose.cli.yml(profiletools) runs${STELLAOPS_CLI_IMAGE:-stellaops/cli:dev}on the externalstellaopsnetwork with astella-cli-statevolume and a/workspacebind mount, forwardingAPI_KEY,STELLAOPS_BACKEND_URLand theSTELLAOPS_AUTHORITY_*variables. Wrapper scripts:devops/compose/scripts/stella-cli.shandstella-cli.ps1. - Binaries.
.gitea/scripts/build/build-cli.shbuilds per-RID CLI outputs (defaultlinux-x64,win-x64,osx-arm64) with the seven plug-ins, an optional SBOM and optional cosign signing.
See release and packaging and the distribution matrix.
11) Tests and CI gates
The CLI test project runs collections serially by default (PCD-299). Command tests share process-global console writers, Spectre Console and environment state. A class-local lock does not isolate those resources from other test classes. Three unfiltered default runs measure the serial execution cost in the sprint receipt.
src/Cli/__Tests/StellaOps.Cli.Testsholds unit, golden-output and conformance tests, includingCliDocumentationContractTests(the documentation example gate),CliHttpClientsTests, and the tenant, grant and scope conformance tests in section 4.2.src/Cli/__Tests/StellaOps.Cli.PluginIntegration.Testsruns in.gitea/workflows/cli-plugin-integration.ymltogether with the source-free image help checks..gitea/workflows/docs-cli-parity.ymlchecks that every crypto CLI invocation in the Doctor crypto articles resolves to a registered subcommand.
12) Retired surfaces
The following are removed from the command tree; do not document them as current:
- the embedded legacy alias table
cli-routes.json, its loader and the command-routing infrastructure (deleted under the pre-release zero-compatibility rule); topology,pack(Task Packs),system migrations-*,sbom parity-matrix,evidence card export;groundtruth(plug-in and core tree, BIN-10), the air-gap sync group and the BinaryIndex ops group (frozen undersrc/__Obsoleted/Cli/);offline kit pullandoffline kit import;- the unregistered plural
keys,issuer-keysandtrust-anchorssample groups and the unregisteredjobenginesample group.
Evidence
| Claim | Source |
|---|---|
Root command tree, global options, scan graph attachment, plug-in loading call | src/Cli/StellaOps.Cli/Commands/CommandFactory.cs (Create) |
| Process model, DI composition, HTTP clients, exit-code precedence | src/Cli/StellaOps.Cli/Program.cs |
| Parser-error exit code | src/Cli/StellaOps.Cli/Commands/CliInvocation.cs |
| Configuration sources and precedence | src/__Libraries/StellaOps.Configuration/StellaOpsConfigurationBootstrapper.cs, StellaOpsConfigurationOptions.cs |
| Configuration keys and defaults | src/Cli/StellaOps.Cli/Configuration/CliBootstrapper.cs, StellaOpsCliOptions.cs |
| Profiles and profile secrets | src/Cli/StellaOps.Cli/Configuration/CliProfile.cs, Configuration/Secrets/SecretProtectorFactory.cs |
| Grant rule, cache key, DI authentication | src/Cli/StellaOps.Cli/Configuration/AuthorityTokenUtilities.cs |
| Tenant rule (token and header) | src/Cli/StellaOps.Cli/Services/TenantProfileStore.cs |
| Token grants, configured token endpoint | src/Authority/StellaOps.Authority/StellaOps.Auth.Client/StellaOpsTokenClient.cs |
| Token file cache (JSON, owner-only permissions) | src/Authority/StellaOps.Authority/StellaOps.Auth.Client/FileTokenCache.cs, ServiceCollectionExtensions.cs |
auth login caching and --no-save | src/Cli/StellaOps.Cli/Commands/CommandHandlers.cs (HandleAuthLoginAsync) |
| Scope catalogue | src/Authority/StellaOps.Authority/StellaOps.Auth.Abstractions/StellaOpsScopes.cs |
| Client scopes | src/Cli/StellaOps.Cli/Services/SbomClient.cs, HubMirrorSeedClient.cs, CliLocalTransparencyRekorClient.cs |
| Registered score commands, Scanner DTOs and transport | src/Cli/StellaOps.Cli/Commands/ScoreReplayCommandGroup.cs, Program.cs, src/Scanner/StellaOps.Scanner.WebService/Endpoints/ScoreReplayEndpoints.cs; score replay guide |
| Conformance tests (tenant, grant, scope, raw HttpClient) | src/Cli/__Tests/StellaOps.Cli.Tests/Services/CliClientTenantResolutionConformanceTests.cs, CliTenantHeaderConformanceTests.cs, CliClientGrantParityTests.cs, CliRequestedScopeCatalogueConformanceTests.cs, CliHttpClientsTests.cs |
| Egress guard | src/Cli/StellaOps.Cli/Configuration/EgressPolicyHttpMessageHandler.cs, HttpClientBuilderExtensions.cs |
| Policy-gateway URL resolution | src/Cli/StellaOps.Cli/Configuration/PolicyGatewayEndpoint.cs |
| Local-transparency owner endpoint | src/Cli/StellaOps.Cli/Services/CliLocalTransparencyRekorClient.cs |
| Evidence host prefix for the Attestor surface | src/Evidence/StellaOps.Evidence.WebService/Program.cs (ConsolidatedRoutePrefix) |
| Attestor transparency client routes | src/Cli/StellaOps.Cli/Services/BackendOperationsClient.cs |
scan, sbom, db, auth, config, airgap, mirror subcommands | src/Cli/StellaOps.Cli/Commands/CommandFactory.cs, Commands/MirrorSeedCommandGroup.cs |
Unregistered compare builder | src/Cli/StellaOps.Cli/Commands/Compare/CompareCommandBuilder.cs |
vuln-db options and exit codes | src/Cli/StellaOps.Cli/Commands/VulnDb/VulnDbCommandGroup.cs, HubCorpusExporter.cs |
| Crypto profile persistence and settings | src/Cli/StellaOps.Cli/Commands/CryptoCommandGroup.cs, ComplianceCommandGroup.cs, Services/CryptoProfilePreferenceStore.cs, Services/PlatformEnvironmentSettingsClient.cs, Services/TenantCryptoComplianceClient.cs |
| Regional providers | src/Cli/StellaOps.Cli/CliRegionalCryptoComposition.cs, src/__Libraries/StellaOps.Cryptography.Plugin.SmRemote/DependencyInjection/ServiceCollectionExtensions.cs |
| OCI referrer commands | src/Cli/StellaOps.Cli/Commands/EvidenceReferrerCommands.cs |
| Release, deployment and agent verbs | src/Cli/StellaOps.Cli/Commands/ReleaseOperatorDecisionCommandGroup.cs, DeploymentCommandGroup.cs, AgentCommandGroup.cs |
| Identity-provider group and alias | src/Cli/StellaOps.Cli/Commands/IdentityProviderCommandGroup.cs |
| Exit-code constants | src/Cli/StellaOps.Cli/Commands/CliExitCodes.cs |
| Output formats | src/Cli/StellaOps.Cli/Output/OutputFormat.cs |
| Plug-in loader and duplicate-root removal | src/Cli/StellaOps.Cli/Plugins/CliCommandModuleLoader.cs |
| Shipped plug-in manifests | src/Cli/plugins/cli/*/*.manifest.json |
| NonCore plug-in commands | src/Cli/__Libraries/StellaOps.Cli.Plugins.NonCore/NonCoreCliCommandModule.cs |
| Published CLI image | devops/stella-cli/Dockerfile, .gitea/workflows/stella-cli-publish.yml |
| Operator image with plug-ins, compose runner | devops/docker/Dockerfile.cli, devops/compose/docker-compose.cli.yml, devops/compose/scripts/stella-cli.sh |
| Binary builds | .gitea/scripts/build/build-cli.sh |
| Documentation example gate | src/Cli/__Tests/StellaOps.Cli.Tests/Commands/CliDocumentationContractTests.cs, docs/modules/cli/unshipped-command-examples.json |
| Plug-in integration and doctor-crypto parity lanes | .gitea/workflows/cli-plugin-integration.yml, .gitea/workflows/docs-cli-parity.yml |
| Retired CLI source | src/__Obsoleted/Cli/ |
