- Created SignerEndpointsTests to validate the SignDsse and VerifyReferrers endpoints. - Implemented StubBearerAuthenticationDefaults and StubBearerAuthenticationHandler for token-based authentication. - Developed ConcelierExporterClient for managing Trivy DB settings and export operations. - Added TrivyDbSettingsPageComponent for UI interactions with Trivy DB settings, including form handling and export triggering. - Implemented styles and HTML structure for Trivy DB settings page. - Created NotifySmokeCheck tool for validating Redis event streams and Notify deliveries.
		
			
				
	
	
	
		
			35 KiB
		
	
	
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	# API & CLI Reference
Purpose – give operators and integrators a single, authoritative spec for REST/GRPC calls and first‑party CLI tools (stella-cli, zastava, stella).
Everything here is source‑of‑truth for generated Swagger/OpenAPI and the --help screens in the CLIs.
## 0 Quick Glance
| Area | Call / Flag | Notes | 
|---|---|---|
| Scan entry | POST /scan | Accepts SBOM or image; sub‑5 s target | 
| Delta check | POST /layers/missing | <20 ms reply; powers delta SBOM feature | 
| Rate‑limit / quota | — | Headers X‑Stella‑Quota‑Remaining,X‑Stella‑Reseton every response | 
| Policy I/O | GET /policy/export,POST /policy/import | YAML now; Rego coming | 
| Policy lint | POST /policy/validate | Returns 200 OK if ruleset passes | 
| Auth | POST /connect/token(OpenIddict) | Client‑credentials preferred | 
| Health | GET /healthz | Simple liveness probe | 
| Attestation * | POST /attest(TODO Q1‑2026) | SLSA provenance + Rekor log | 
| CLI flags | --sbom-type--delta--policy-file | Added to stella | 
* Marked TODO → delivered after sixth month (kept on Feature Matrix “To Do” list).
## 1 Authentication
Stella Ops uses OAuth 2.0 / OIDC (token endpoint mounted via OpenIddict).
POST /connect/token
Content‑Type: application/x-www-form-urlencoded
grant_type=client_credentials&
client_id=ci‑bot&
client_secret=REDACTED&
scope=stella.api
Successful response:
{
  "access_token": "eyJraWQi...",
  "token_type": "Bearer",
  "expires_in": 3600
}
Tip
– pass the token via
Authorization: Bearer <token>on every call.
## 2 REST API
### 2.0 Obtain / Refresh Offline‑Token
POST /token/offline
Authorization: Bearer <admin‑token>
| Body field | Required | Example | Notes | 
|---|---|---|---|
| expiresDays | no | 30 | Max 90 days | 
{
  "jwt": "eyJhbGciOiJSUzI1NiIsInR5cCI6...",
  "expires": "2025‑08‑17T00:00:00Z"
}
Token is signed with the backend’s private key and already contains
"maxScansPerDay": {{ quota_token }}.
### 2.1 Scan – Upload SBOM or Image
POST /scan
| Param / Header | In | Required | Description | 
|---|---|---|---|
| X‑Stella‑Sbom‑Type | header | no | trivy-json-v2,spdx-json,cyclonedx-json; omitted ➞ auto‑detect | 
| ?threshold | query | no | low,medium,high,critical; default critical | 
| body | body | yes | Either SBOM JSON or Docker image tarball/upload URL | 
Every successful /scan response now includes:
| Header | Example | 
|---|---|
| X‑Stella‑Quota‑Remaining | 129 | 
| X‑Stella‑Reset | 2025‑07‑18T23:59:59Z | 
| X‑Stella‑Token‑Expires | 2025‑08‑17T00:00:00Z | 
Response 200 (scan completed):
{
  "digest": "sha256:…",
  "summary": {
    "Critical": 0,
    "High": 3,
    "Medium": 12,
    "Low": 41
  },
  "policyStatus": "pass",
  "quota": {
    "remaining": 131,
    "reset": "2025-07-18T00:00:00Z"
  }
}
Response 202 – queued; polling URL in Location header.
### 2.2 Delta SBOM – Layer Cache Check
POST /layers/missing
Content‑Type: application/json
Authorization: Bearer <token>
{
  "layers": [
    "sha256:d38b...",
    "sha256:af45..."
  ]
}
Response 200 — <20 ms target:
{
  "missing": [
    "sha256:af45..."
  ]
}
Client then generates SBOM only for the missing layers and re‑posts /scan.
### 2.3 Policy Endpoints (preview feature flag: scanner.features.enablePolicyPreview)
All policy APIs require scanner.reports scope (or anonymous access while auth is disabled).
Fetch schema
GET /api/v1/policy/schema
Authorization: Bearer <token>
Accept: application/schema+json
Returns the embedded policy-schema@1 JSON schema used by the binder.
Run diagnostics
POST /api/v1/policy/diagnostics
Content-Type: application/json
Authorization: Bearer <token>
{
  "policy": {
    "format": "yaml",
    "actor": "cli",
    "description": "dev override",
    "content": "version: \"1.0\"\nrules:\n  - name: Quiet Dev\n    environments: [dev]\n    action:\n      type: ignore\n      justification: dev waiver\n"
  }
}
Response 200:
{
  "success": false,
  "version": "1.0",
  "ruleCount": 1,
  "errorCount": 0,
  "warningCount": 1,
  "generatedAt": "2025-10-19T03:25:14.112Z",
  "issues": [
    { "code": "policy.rule.quiet.missing_vex", "message": "Quiet flag ignored: rule must specify requireVex justifications.", "severity": "Warning", "path": "$.rules[0]" }
  ],
  "recommendations": [
    "Review policy warnings and ensure intentional overrides are documented."
  ]
}
success is false when blocking issues remain; recommendations aggregate YAML ignore rules, VEX include/exclude hints, and vendor precedence guidance.
Preview impact
POST /api/v1/policy/preview
Authorization: Bearer <token>
Content-Type: application/json
{
  "imageDigest": "sha256:abc123",
  "findings": [
    { "id": "finding-1", "severity": "Critical", "source": "NVD" }
  ],
  "policy": {
    "format": "yaml",
    "content": "version: \"1.0\"\nrules:\n  - name: Block Critical\n    severity: [Critical]\n    action: block\n"
  }
}
Response 200:
{
  "success": true,
  "policyDigest": "9c5e...",
  "revisionId": "preview",
  "changed": 1,
  "diffs": [
    {
      "findingId": "finding-1",
      "baseline": {"findingId": "finding-1", "status": "Pass"},
      "projected": {
        "findingId": "finding-1",
        "status": "Blocked",
        "ruleName": "Block Critical",
        "ruleAction": "Block",
        "score": 5.0,
        "configVersion": "1.0",
        "inputs": {"severityWeight": 5.0}
      },
      "changed": true
    }
  ],
  "issues": []
}
- Provide policyto preview staged changes; omit it to compare against the active snapshot.
- Baseline verdicts are optional; when omitted, the API synthesises pass baselines before computing diffs.
- Quieted verdicts include quietedByandquietflags; score inputs now surface reachability/vendor trust weights (reachability.*,trustWeight.*).
OpenAPI: the full API document (including these endpoints) is exposed at /openapi/v1.json and can be fetched for tooling or contract regeneration.
### 2.4 Scanner – Queue a Scan Job (SP9 milestone)
POST /api/v1/scans
Authorization: Bearer <token with scanner.scans.enqueue>
Content-Type: application/json
{
  "image": {
    "reference": "registry.example.com/acme/app:1.2.3"
  },
  "force": false,
  "clientRequestId": "ci-build-1845",
  "metadata": {
    "pipeline": "github",
    "trigger": "pull-request"
  }
}
| Field | Required | Notes | 
|---|---|---|
| image.reference | no* | Full repo/tag ( registry/repo:tag). Provide eitherreferenceordigest(sha256:…). | 
| image.digest | no* | OCI digest (e.g. sha256:…). | 
| force | no | trueforces a re-run even if an identical scan (scanId) already exists. Default false. | 
| clientRequestId | no | Free-form string surfaced in audit logs. | 
| metadata | no | Optional string map stored with the job and surfaced in observability feeds. | 
* At least one of image.reference or image.digest must be supplied.
Response 202 – job accepted (idempotent):
HTTP/1.1 202 Accepted
Location: /api/v1/scans/2f6c17f9b3f548e2a28b9c412f4d63f8
{
  "scanId": "2f6c17f9b3f548e2a28b9c412f4d63f8",
  "status": "Pending",
  "location": "/api/v1/scans/2f6c17f9b3f548e2a28b9c412f4d63f8",
  "created": true
}
- scanIdis deterministic – resubmitting an identical payload returns the same identifier with- "created": false.
- API is cancellation-aware; aborting the HTTP request cancels the submission attempt.
- Required scope: scanner.scans.enqueue.
Response 400 – validation problem (Content-Type: application/problem+json) when both image.reference and image.digest are blank.
### 2.5 Scanner – Fetch Scan Status
GET /api/v1/scans/{scanId}
Authorization: Bearer <token with scanner.scans.read>
Accept: application/json
Response 200:
{
  "scanId": "2f6c17f9b3f548e2a28b9c412f4d63f8",
  "status": "Pending",
  "image": {
    "reference": "registry.example.com/acme/app:1.2.3",
    "digest": null
  },
  "createdAt": "2025-10-18T20:15:12.482Z",
  "updatedAt": "2025-10-18T20:15:12.482Z",
  "failureReason": null
}
Statuses: Pending, Running, Succeeded, Failed, Cancelled.
### 2.6 Scanner – Stream Progress (SSE / JSONL)
GET /api/v1/scans/{scanId}/events?format=sse|jsonl
Authorization: Bearer <token with scanner.scans.read>
Accept: text/event-stream
When format is omitted the endpoint emits Server-Sent Events (SSE). Specify format=jsonl to receive newline-delimited JSON (application/x-ndjson). Response headers include Cache-Control: no-store and X-Accel-Buffering: no so intermediaries avoid buffering the stream.
SSE frame (default):
id: 1
event: pending
data: {"scanId":"2f6c17f9b3f548e2a28b9c412f4d63f8","sequence":1,"state":"Pending","message":"queued","timestamp":"2025-10-19T03:12:45.118Z","correlationId":"2f6c17f9b3f548e2a28b9c412f4d63f8:0001","data":{"force":false,"meta.pipeline":"github"}}
JSONL frame (format=jsonl):
{"scanId":"2f6c17f9b3f548e2a28b9c412f4d63f8","sequence":1,"state":"Pending","message":"queued","timestamp":"2025-10-19T03:12:45.118Z","correlationId":"2f6c17f9b3f548e2a28b9c412f4d63f8:0001","data":{"force":false,"meta.pipeline":"github"}}
- sequenceis monotonic starting at- 1.
- correlationIdis deterministic (- {scanId}:{sequence:0000}) unless a custom identifier is supplied by the publisher.
- timestampis ISO‑8601 UTC with millisecond precision, ensuring deterministic ordering for consumers.
- The stream completes when the client disconnects or the coordinator stops publishing events.
### 2.7 Scanner – Assemble Report (Signed Envelope)
POST /api/v1/reports
Authorization: Bearer <token with scanner.reports>
Content-Type: application/json
Request body mirrors policy preview inputs (image digest plus findings). The service evaluates the active policy snapshot, assembles a verdict, and signs the canonical report payload.
Response 200:
{
  "report": {
    "reportId": "report-3def5f362aa475ef14b6",
    "imageDigest": "sha256:deadbeef",
    "verdict": "blocked",
    "policy": { "revisionId": "rev-1", "digest": "27d2ec2b34feedc304fc564d252ecee1c8fa14ea581a5ff5c1ea8963313d5c8d" },
    "summary": { "total": 1, "blocked": 1, "warned": 0, "ignored": 0, "quieted": 0 },
    "verdicts": [
      {
        "findingId": "finding-1",
        "status": "Blocked",
        "ruleName": "Block Critical",
        "ruleAction": "Block",
        "score": 40.5,
        "configVersion": "1.0",
        "inputs": {
          "reachabilityWeight": 0.45,
          "baseScore": 40.5,
          "severityWeight": 90,
          "trustWeight": 1,
          "trustWeight.NVD": 1,
          "reachability.runtime": 0.45
        },
        "quiet": false,
        "sourceTrust": "NVD",
        "reachability": "runtime"
      }
    ],
    "issues": []
  },
  "dsse": {
    "payloadType": "application/vnd.stellaops.report+json",
    "payload": "<base64 canonical report>",
    "signatures": [
      {
        "keyId": "scanner-report-signing",
        "algorithm": "hs256",
        "signature": "<base64 signature>"
      }
    ]
  }
}
- The reportobject omits null fields and is deterministic (ISO timestamps, sorted keys).
- dssefollows the DSSE (Dead Simple Signing Envelope) shape;- payloadis the canonical UTF-8 JSON and- signatures[0].signatureis the base64 HMAC/Ed25519 value depending on configuration.
- A runnable sample envelope is available at samples/api/reports/report-sample.dsse.jsonfor tooling tests or signature verification.
Response 404 – application/problem+json payload with type https://stellaops.org/problems/not-found when the scan identifier is unknown.
Tip
– poll
Locationfrom the submission call untilstatustransitions away fromPending/Running.
# Example import payload (YAML)
version: "1.0"
rules:
  - name: Ignore Low dev
    severity: [Low, None]
    environments: [dev, staging]
    action: ignore
Validation errors come back as:
{
  "errors": [
    {
      "path": "$.rules[0].severity",
      "msg": "Invalid level 'None'"
    }
  ]
}
# Preview response excerpt
{
  "success": true,
  "policyDigest": "9c5e...",
  "revisionId": "rev-12",
  "changed": 1,
  "diffs": [
    {
      "baseline": {"findingId": "finding-1", "status": "pass"},
      "projected": {"findingId": "finding-1", "status": "blocked", "ruleName": "Block Critical"},
      "changed": true
    }
  ]
}
### 2.4 Attestation (Planned – Q1‑2026)
POST /attest
| Param | Purpose | 
|---|---|
| body (JSON) | SLSA v1.0 provenance doc | 
| Signed + stored in local Rekor mirror | 
Returns 202 Accepted and Location: /attest/{id} for async verify.
### 2.8 Runtime – Ingest Observer Events (SCANNER-RUNTIME-12-301)
POST /api/v1/runtime/events
Authorization: Bearer <token with scanner.runtime.ingest>
Content-Type: application/json
| Requirement | Details | 
|---|---|
| Auth scope | scanner.runtime.ingest | 
| Batch size | ≤ 256 envelopes ( scanner.runtime.maxBatchSize, configurable) | 
| Payload cap | ≤ 1 MiB serialized JSON ( scanner.runtime.maxPayloadBytes) | 
| Rate limits | Per-tenant and per-node token buckets (default 200 events/s tenant, 50 events/s node, burst 200) – excess returns 429 with Retry-After. | 
| TTL | Runtime events retained 45 days by default ( scanner.runtime.eventTtlDays). | 
Request body
{
  "batchId": "node-a-2025-10-20T15:03:12Z",
  "events": [
    {
      "schemaVersion": "zastava.runtime.event@v1",
      "event": {
        "eventId": "evt-2f9c02b8",
        "when": "2025-10-20T15:03:08Z",
        "kind": "ContainerStart",
        "tenant": "tenant-alpha",
        "node": "cluster-a/node-01",
        "runtime": { "engine": "containerd", "version": "1.7.19" },
        "workload": {
          "platform": "kubernetes",
          "namespace": "payments",
          "pod": "api-7c9fbbd8b7-ktd84",
          "container": "api",
          "containerId": "containerd://bead5...",
          "imageRef": "ghcr.io/acme/api@sha256:deadbeef"
        },
        "process": { "pid": 12345, "entrypoint": ["/start.sh", "--serve"] },
        "loadedLibs": [
          { "path": "/lib/x86_64-linux-gnu/libssl.so.3", "inode": 123456, "sha256": "abc123..." }
        ],
        "posture": { "imageSigned": true, "sbomReferrer": "present" },
        "delta": { "baselineImageDigest": "sha256:deadbeef" },
        "evidence": [ { "signal": "proc.maps", "value": "libssl.so.3@0x7f..." } ],
        "annotations": { "observerVersion": "1.0.0" }
      }
    }
  ]
}
Responses
| Code | Body | Notes | 
|---|---|---|
| 202 Accepted | { "accepted": 128, "duplicates": 2 } | Batch persisted; duplicates are ignored via unique eventId. | 
| 400 Bad Request | Problem+JSON | Validation failures – empty batch, duplicate IDs, unsupported schema version, payload too large. | 
| 429 Too Many Requests | Problem+JSON | Per-tenant/node rate limit exceeded; Retry-Afterheader emitted in seconds. | 
Persisted documents capture the canonical envelope (payload field), tenant/node metadata, and set an automatic TTL on expiresAt. Observers should retry rejected batches with exponential backoff honouring the provided Retry-After hint.
## 3 StellaOps CLI (stellaops-cli)
The new CLI is built on System.CommandLine 2.0.0‑beta5 and mirrors the Concelier backend REST API.
Configuration follows the same precedence chain everywhere:
- Environment variables (e.g. API_KEY,STELLAOPS_BACKEND_URL,StellaOps:ApiKey)
- appsettings.json→- appsettings.local.json
- appsettings.yaml→- appsettings.local.yaml
- Defaults (ApiKey = "",BackendUrl = "", cache folders under the current working directory)
Authority auth client resilience settings
| Setting | Environment variable | Default | Purpose | 
|---|---|---|---|
| StellaOps:Authority:Resilience:EnableRetries | STELLAOPS_AUTHORITY_ENABLE_RETRIES | true | Toggle Polly wait-and-retry handlers for discovery/token calls | 
| StellaOps:Authority:Resilience:RetryDelays | STELLAOPS_AUTHORITY_RETRY_DELAYS | 1s,2s,5s | Comma/space-separated backoff sequence (HH:MM:SS) | 
| StellaOps:Authority:Resilience:AllowOfflineCacheFallback | STELLAOPS_AUTHORITY_ALLOW_OFFLINE_CACHE_FALLBACK | true | Reuse cached discovery/JWKS metadata when Authority is temporarily unreachable | 
| StellaOps:Authority:Resilience:OfflineCacheTolerance | STELLAOPS_AUTHORITY_OFFLINE_CACHE_TOLERANCE | 00:10:00 | Additional tolerance window added to the discovery/JWKS cache lifetime | 
See docs/dev/32_AUTH_CLIENT_GUIDE.md for recommended profiles (online vs. air-gapped) and testing guidance.
| Command | Purpose | Key Flags / Arguments | Notes | 
|---|---|---|---|
| stellaops-cli scanner download | Fetch and install scanner container | --channel <stable|beta|nightly>(defaultstable)--output <path>--overwrite--no-install | Saves artefact under ScannerCacheDirectory, verifies digest/signature, and executesdocker loadunless--no-installis supplied. | 
| stellaops-cli scan run | Execute scanner container against a directory (auto-upload) | --target <directory>(required)--runner <docker|dotnet|self>(default from config)--entry <image-or-entrypoint>[scanner-args...] | Runs the scanner, writes results into ResultsDirectory, emits a structuredscan-run-*.jsonmetadata file, and automatically uploads the artefact when the exit code is0. | 
| stellaops-cli scan upload | Re-upload existing scan artefact | --file <path> | Useful for retries when automatic upload fails or when operating offline. | 
| stellaops-cli db fetch | Trigger connector jobs | --source <id>(e.g.redhat,osv)--stage <fetch|parse|map>(defaultfetch)`--mode <resume | init | 
| stellaops-cli db merge | Run canonical merge reconcile | — | Calls POST /jobs/merge:reconcile; exit code0on acceptance,1on failures/conflicts | 
| stellaops-cli db export | Kick JSON / Trivy exports | --format <json|trivy-db>(defaultjson)--delta--publish-full/--publish-delta--bundle-full/--bundle-delta | Sets { delta = true }parameter when requested and can override ORAS/bundle toggles per run | 
| stellaops-cli auth <login|logout|status|whoami> | Manage cached tokens for StellaOps Authority | auth login --force(ignore cache)auth statusauth whoami | Uses StellaOps.Auth.Client; honoursStellaOps:Authority:*configuration, stores tokens under~/.stellaops/tokensby default, andwhoamiprints subject/scope/expiry | 
| stellaops-cli auth revoke export | Export the Authority revocation bundle | --output <directory>(defaults to CWD) | Writes revocation-bundle.json,.json.jws, and.json.sha256; verifies the digest locally and includes key metadata in the log summary. | 
| stellaops-cli auth revoke verify | Validate a revocation bundle offline | --bundle <path>--signature <path>--key <path>--verbose | Verifies detached JWS signatures, reports the computed SHA-256, and can fall back to cached JWKS when --keyis omitted. | 
| stellaops-cli offline kit pull | Download the latest offline kit bundle and manifest | --bundle-id <id>(optional)--destination <dir>--overwrite--no-resume | Streams the bundle + manifest from the configured mirror/backend, resumes interrupted downloads, verifies SHA-256, and writes signatures plus a .metadata.jsonmanifest alongside the artefacts. | 
| stellaops-cli offline kit import | Upload an offline kit bundle to the backend | <bundle.tgz>(argument)--manifest <path>--bundle-signature <path>--manifest-signature <path> | Validates digests when metadata is present, then posts multipart payloads to POST /api/offline-kit/import; logs the submitted import ID/status for air-gapped rollout tracking. | 
| stellaops-cli offline kit status | Display imported offline kit details | --json | Shows bundle id/kind, captured/imported timestamps, digests, and component versions; --jsonemits machine-readable output for scripting. | 
| stellaops-cli config show | Display resolved configuration | — | Masks secret values; helpful for air‑gapped installs | 
| stellaops-cli runtime policy test | Ask Scanner.WebService for runtime verdicts (Webhook parity) | --image/-i <digest>(repeatable, comma/space lists supported)--file/-f <path>--namespace/--ns <name>--label/-l key=value(repeatable)--json | Posts to POST /api/v1/scanner/policy/runtime, deduplicates image digests, and prints TTL/policy revision plus per-image columns for signed state, SBOM referrers, quieted-by metadata, confidence, and Rekor attestation (uuid + verified flag). Accepts newline/whitespace-delimited stdin when piped;--jsonemits the raw response without additional logging. | 
When running on an interactive terminal without explicit override flags, the CLI uses Spectre.Console prompts to let you choose per-run ORAS/offline bundle behaviour.
Runtime verdict output reflects the SCANNER-RUNTIME-12-302 contract sign-off (quieted provenance, confidence band, attestation verification). CLI-RUNTIME-13-008 now mirrors those fields in both table and --json formats.
Startup diagnostics
- stellaops-clinow loads Authority plug-in manifests during startup (respecting- Authority:Plugins:*) and surfaces analyzer warnings when a plug-in weakens the baseline password policy (minimum length 12 and all character classes required).
- Follow the log entry’s config path and raise passwordPolicy.minimumLengthto at least 12 while keepingrequireUppercase,requireLowercase,requireDigit, andrequireSymbolset totrueto clear the warning; weakened overrides are treated as actionable security deviations.
Logging & exit codes
- Structured logging via Microsoft.Extensions.Loggingwith single-line console output (timestamps in UTC).
- --verbose / -vraises log level to- Debug.
- Command exit codes bubble up: backend conflict → 1, cancelled viaCTRL+C→130, scanner exit codes propagate as-is.
Artifact validation
- Downloads are verified against the X-StellaOps-Digestheader (SHA-256). WhenStellaOps:ScannerSignaturePublicKeyPathpoints to a PEM-encoded RSA key, the optionalX-StellaOps-Signatureheader is validated as well.
- Metadata for each bundle is written alongside the artefact (*.metadata.json) with digest, signature, source URL, and timestamps.
- Retry behaviour is controlled via StellaOps:ScannerDownloadAttempts(default 3 with exponential backoff).
- Successful scan runexecutions create timestamped JSON artefacts insideResultsDirectoryplus ascan-run-*.jsonmetadata envelope documenting the runner, arguments, timing, and stdout/stderr. The artefact is posted back to Concelier automatically.
Trivy DB export metadata (metadata.json)
stellaops-cli db export --format trivy-db (and the backing POST /jobs/export:trivy-db) always emits a metadata.json document in the OCI layout root. Operators consuming the bundle or delta updates should inspect the following fields:
| Field | Type | Purpose | 
|---|---|---|
| mode | full|delta | Indicates whether the current run rebuilt the entire database ( full) or only the changed files (delta). | 
| baseExportId | string? | Export ID of the last full baseline that the delta builds upon. Only present for mode = delta. | 
| baseManifestDigest | string? | SHA-256 digest of the manifest belonging to the baseline OCI layout. | 
| resetBaseline | boolean | truewhen the exporter rotated the baseline (e.g., repo change, delta chain reset). Treat as a full refresh. | 
| treeDigest | string | Canonical SHA-256 digest of the JSON tree used to build the database. | 
| treeBytes | number | Total bytes across exported JSON files. | 
| advisoryCount | number | Count of advisories included in the export. | 
| exporterVersion | string | Version stamp of StellaOps.Concelier.Exporter.TrivyDb. | 
| builder | object? | Raw metadata emitted by trivy-db build(version, update cadence, etc.). | 
| delta.changedFiles[] | array | Present when mode = delta. Each entry lists{ "path": "<relative json>", "length": <bytes>, "digest": "sha256:..." }. | 
| delta.removedPaths[] | array | Paths that existed in the previous manifest but were removed in the new run. | 
When the planner opts for a delta run, the exporter copies unmodified blobs from the baseline layout identified by baseManifestDigest. Consumers that cache OCI blobs only need to fetch the changedFiles and the new manifest/metadata unless resetBaseline is true.
When pushing to ORAS, set concelier:exporters:trivyDb:oras:publishFull / publishDelta to control whether full or delta runs are copied to the registry. Offline bundles follow the analogous includeFull / includeDelta switches under offlineBundle.
Example configuration (appsettings.yaml):
concelier:
  exporters:
    trivyDb:
      oras:
        enabled: true
        publishFull: true
        publishDelta: false
      offlineBundle:
        enabled: true
        includeFull: true
        includeDelta: false
Authentication
- API key is sent as Authorization: Bearer <token>automatically when configured.
- Anonymous operation is permitted only when Concelier runs with
authority.allowAnonymousFallback: true. This flag is temporary—plan to disable it before 2025-12-31 UTC so bearer tokens become mandatory.
Authority-backed auth workflow:
- Configure Authority settings via config or env vars (see sample below). Minimum fields: Url,ClientId, and eitherClientSecret(client credentials) orUsername/Password(password grant).
- Run stellaops-cli auth loginto acquire and cache a token. Use--forceif you need to ignore an existing cache entry.
- Execute CLI commands as normal—the backend client injects the cached bearer token automatically and retries on transient 401/403 responses with operator guidance.
- Inspect the cache with stellaops-cli auth status(shows expiry, scope, mode) or clear it viastellaops-cli auth logout.
- Run stellaops-cli auth whoamito dump token subject, audience, issuer, scopes, and remaining lifetime (verbose mode prints additional claims).
- Expect Concelier to emit audit logs for each /jobs*request showingsubject,clientId,scopes,status, and whether network bypass rules were applied.
Tokens live in ~/.stellaops/tokens unless StellaOps:Authority:TokenCacheDirectory overrides it. Cached tokens are reused offline until they expire; the CLI surfaces clear errors if refresh fails.
For offline workflows, configure StellaOps:Offline:KitsDirectory (or STELLAOPS_OFFLINE_KITS_DIR) to control where bundles, manifests, and metadata are stored, and StellaOps:Offline:KitMirror (or STELLAOPS_OFFLINE_MIRROR_URL) to override the download base URL when pulling from a mirror.
Configuration file template
{
  "StellaOps": {
    "ApiKey": "your-api-token",
    "BackendUrl": "https://concelier.example.org",
    "ScannerCacheDirectory": "scanners",
    "ResultsDirectory": "results",
    "DefaultRunner": "docker",
    "ScannerSignaturePublicKeyPath": "",
    "ScannerDownloadAttempts": 3,
    "Offline": {
      "KitsDirectory": "offline-kits",
      "KitMirror": "https://get.stella-ops.org/ouk/"
    },
    "Authority": {
      "Url": "https://authority.example.org",
      "ClientId": "concelier-cli",
      "ClientSecret": "REDACTED",
      "Username": "",
      "Password": "",
      "Scope": "concelier.jobs.trigger",
      "TokenCacheDirectory": ""
    }
  }
}
Drop appsettings.local.json or .yaml beside the binary to override per environment.
### 2.5 Misc Endpoints
| Path | Method | Description | 
|---|---|---|
| /healthz | GET | Liveness; returns "ok" | 
| /metrics | GET | Prometheus exposition (OTel) | 
| /version | GET | Git SHA + build date | 
### 2.6 Authority Admin APIs
Administrative endpoints live under /internal/* on the Authority host and require the bootstrap API key (x-stellaops-bootstrap-key). Responses are deterministic and audited via AuthEventRecord.
| Path | Method | Description | 
|---|---|---|
| /internal/revocations/export | GET | Returns the revocation bundle (JSON + detached JWS + digest). Mirrors the output of stellaops-cli auth revoke export. | 
| /internal/signing/rotate | POST | Promotes a new signing key and marks the previous key as retired without restarting the service. | 
Rotate request body
{
  "keyId": "authority-signing-2025",
  "location": "../certificates/authority-signing-2025.pem",
  "source": "file",
  "provider": "default"
}
The API responds with the active kid, previous key (if any), and the set of retired key identifiers. Always export a fresh revocation bundle after rotation so downstream mirrors receive signatures from the new key.
## 3 First‑Party CLI Tools
### 3.1 stella
Package SBOM + Scan + Exit code – designed for CI.
Usage: stella [OPTIONS] IMAGE_OR_SBOM
| Flag / Option | Default | Description | 
|---|---|---|
| --server | http://localhost:8080 | API root | 
| --token | env STELLA_TOKEN | Bearer token | 
| --sbom-type | auto | Force trivy-json-v2/spdx-json/cyclonedx-json | 
| --delta | false | Enable delta layer optimisation | 
| --policy-file | none | Override server rules with local YAML/Rego | 
| --threshold | critical | Fail build if ≥ level found | 
| --output-json | none | Write raw scan result to file | 
| --wait-quota | true | If 429 received, automatically wait Retry‑Afterand retry once. | 
Exit codes
| Code | Meaning | 
|---|---|
| 0 | Scan OK, policy passed | 
| 1 | Vulnerabilities ≥ threshold OR policy block | 
| 2 | Internal error (network etc.) | 
### 3.2 stella‑zastava
Daemon / K8s DaemonSet – watch container runtime, push SBOMs.
Core flags (excerpt):
| Flag | Purpose | 
|---|---|
| --mode | listen(default) /enforce | 
| --filter-image | Regex; ignore infra/busybox images | 
| --threads | Worker pool size | 
### 3.3 stellopsctl
Admin utility – policy snapshots, feed status, user CRUD.
Examples:
stellopsctl policy export > policies/backup-2025-07-14.yaml
stellopsctl feed refresh  # force OSV merge
stellopsctl user add dev-team --role developer
## 4 Error Model
Uniform problem‑details object (RFC 7807):
{
  "type": "https://stella-ops.org/probs/validation",
  "title": "Invalid request",
  "status": 400,
  "detail": "Layer digest malformed",
  "traceId": "00-7c39..."
}
## 5 Rate Limits
Default 40 requests / second / token.
429 responses include Retry-After seconds header.
## 6 FAQ & Tips
- Skip SBOM generation in CI – supply a pre‑built SBOM and add ?sbom-only=trueto/scanfor <1 s path.
- Air‑gapped? – point --servertohttp://oukgw:8080inside the Offline Update Kit.
- YAML vs Rego – YAML simpler; Rego unlocks time‑based logic (see samples).
- Cosign verify plug‑ins – enable SCANNER_VERIFY_SIG=trueenv to refuse unsigned plug‑ins.
## 7 Planned Changes (Beyond 6 Months)
These stay in Feature Matrix → To Do until design is frozen.
| Epic / Feature | API Impact Sketch | 
|---|---|
| SLSA L1‑L3 attestation | /attest(see §2.4) | 
| Rekor transparency log | /rekor/log/{id}(GET) | 
| Plug‑in Marketplace metadata | /plugins/market(catalog) | 
| Horizontal scaling controls | POST /cluster/node(add/remove) | 
| Windows agent support | Update LSAPI to PDE, no API change | 
## 8 References
- OpenAPI YAML → /openapi/v1.yaml(served by backend)
- OAuth2 spec: https://datatracker.ietf.org/doc/html/rfc6749
- SLSA spec: https://slsa.dev/spec/v1.0
## 9 Changelog (truncated)
- 2025‑07‑14 – added delta SBOM, policy import/export, CLI --sbom-type.
- 2025‑07‑12 – initial public reference.