Orchestrator Runbook (DOCS-ORCH-34-003)
Last updated: 2026-05-30
Audience: operators running the Stella Ops Orchestrator (scheduling and run execution). Purpose: pre-flight checks, day-to-day run/schedule operations, incident response, health/metrics, and offline/air-gap procedures for the Orchestrator surface.
Implementation note: the “Orchestrator” is delivered by the JobEngine module, whose web service is
StellaOps.Scheduler.WebService(service/router namescheduler). Operational HTTP routes are therefore served under/api/v1/scheduler/...for runs/schedules and under/api/v1/jobengine/...for the read-only Console compatibility surface. By default the web service runs in embedded worker mode (Scheduler:Worker:Embedded=true), so all six heavy worker background services (Planner, PlannerQueueDispatcher, Runner, PolicyRunDispatch, GraphBuild, GraphOverlay) run in-process; set it tofalseonly when scaling those workers in a separatescheduler-workerhost. The exception lifecycle workers (ExceptionLifecycleWorker,ExpiringNotificationWorker) and theSystemScheduleBootstrapalways run in the web process regardless of the embedded flag. (Source:Program.cs—AddSchedulerWorkervs. the unconditionalAddHostedServiceregistrations.)
Pre-flight
- Ensure PostgreSQL reachable and a queue transport (Redis or NATS) configured; liveness at
GET /healthzand readiness atGET /readyzreturn HTTP 200 ({ "status": "ok" }/{ "status": "ready" }, both anonymous). There is no aggregate/admin/healthendpoint and the probes do not report queue depth — useGET /api/v1/scheduler/runs/queue/lagfor depth. - Verify tenant allowlist and scopes configured in Authority. The orchestrator authorization scopes are
orch:read,orch:operate,orch:quota, andorch:backfill; the underlying scheduler surface also acceptsscheduler:read,scheduler:operate, andscheduler:admin. (There is noorchestrator:*scope.) Source symbols:StellaOpsScopes.OrchRead/OrchOperate/OrchQuota/OrchBackfill,StellaOpsScopes.SchedulerRead/SchedulerOperate/SchedulerAdmin. - Two-layer scope enforcement on run/schedule endpoints (important — easy to under-provision). Each run and schedule endpoint applies two checks: (1) an ASP.NET authorization policy mapped to the coarse scope —
SchedulerPolicies.Read → scheduler:read,SchedulerPolicies.Operate → scheduler:operate,SchedulerPolicies.Admin → scheduler:admin; and (2) an in-handlerIScopeAuthorizer.EnsureScopecheck for a fine-grained scope:scheduler.runs.read/.write/.manage/.previewandscheduler.schedules.read/.write. In production (TokenScopeAuthorizer, active whenScheduler:Authority:Enabled=true) the fine-grained scope must be present literally in the token — there is no hierarchical fallback, so a token carrying onlyscheduler:operateis rejected at the fine-grained gate. In Development/Testing header-fallback mode (HeaderScopeAuthorizer) the coarse scope does satisfy the fine-grained gate (scheduler.<svc>.<read|write|manage|preview>is allowed byscheduler:<read|operate|admin>). Issue Authority client scopes accordingly: a production operator token for run/schedule mutations needs bothscheduler:operateand the relevantscheduler.runs.*/scheduler.schedules.*scopes. (Source:RunEndpoints/ScheduleEndpointsEnsureScopeconstants;TokenScopeAuthorizervs.HeaderScopeAuthorizer; policies inProgram.cs.) These dotted fine-grained scopes are not declared in the canonicalStellaOpsScopes.cscatalog — they are literal strings enforced only by the scheduler handlers, so Authority client config must grant them explicitly. The JobEngine compatibility endpoints (/api/v1/jobengine/...) are the exception: they require only the coarsescheduler:readororch:readand have no additional fine-grained gate. - Storage connection string must be configured outside local-harness mode — resolved from the first of
Scheduler:Storage:ConnectionString,Scheduler:Storage:Postgres:Scheduler:ConnectionString(compose-nested), orPostgres:Scheduler:ConnectionString(legacy) — the service refuses to start otherwise. (Source:SchedulerStorageConfiguration.) - Plugin bundles present and signatures verified (scheduler job plugins are discovered from plugin DLLs at startup via
PluginHost.LoadPlugins).
Common operations
- Create a run:
POST /api/v1/scheduler/runs/(policyscheduler:operate+ fine-grainedscheduler.runs.manage). The body must reference an existingscheduleId(404 if it does not exist); onlytrigger: manualruns may be created through this endpoint. (Source:RunEndpoints.CreateRunAsync.) - Cancel a run:
POST /api/v1/scheduler/runs/{runId}/cancel(policyscheduler:operate+ fine-grainedscheduler.runs.write). Best-effort; returns HTTP 409 if the run is already terminal. (Source:RunEndpoints.CancelRunAsync.) - Retry a run:
POST /api/v1/scheduler/runs/{runId}/retry(policyscheduler:operate+ fine-grainedscheduler.runs.manage). Creates a new manual run withretryOfpointing at the original; the original must be terminal. - Stream status:
GET /api/v1/scheduler/runs/{runId}/stream— Server-Sent Events (text/event-stream), not WebSocket. Emitsinitial,queueLag,heartbeat,stateChanged,segmentProgress,deltaSummary, andcompletedevents (plus anotFoundevent if the run row disappears mid-stream). (Source:RunStreamCoordinator.) - List runs / read state:
GET /api/v1/scheduler/runs/(filters:scheduleId,state,createdAfter,limit,cursor,sort),GET /api/v1/scheduler/runs/{runId},GET /api/v1/scheduler/runs/{runId}/deltas— all read endpoints apply policyscheduler:read+ fine-grainedscheduler.runs.read. - Preview impact:
POST /api/v1/scheduler/runs/preview(policyscheduler:operate+ fine-grainedscheduler.runs.preview). Resolves the impacted image set for ascheduleIdor inlineselector— optionally narrowed byproductKeys/vulnerabilityIdsandusageOnly— and returns a bounded sample (sampleSizeclamped to 1–50). Read-only; does not create a run. (Source:RunEndpoints.PreviewImpactAsync.) - Manage schedules: under
/api/v1/scheduler/schedules— list (GET /), read (GET /{scheduleId}), create (POST /), update (PATCH /{scheduleId}), soft-delete (DELETE /{scheduleId}; system-managed schedules cannot be deleted), and pause/resume (POST /{scheduleId}/pauseand/resume). List/read apply policyscheduler:read+ fine-grainedscheduler.schedules.read; all mutations (create/update/delete/pause/resume) apply policyscheduler:operate+ fine-grainedscheduler.schedules.write. Pausing a schedule is the direct lever to stop its scheduled runs (distinct fromstella orch sources pause, which pauses a source). (Source:ScheduleEndpoints.) - Console job view (read-only):
GET /api/v1/jobengine/jobs,/api/v1/jobengine/jobs/{jobId},/api/v1/jobengine/jobs/{jobId}/detail, and DAG edges/api/v1/jobengine/dag/job/{jobId}/parentsand/children. These project scheduler runs into JobEngine job records; they requirescheduler:readororch:read. (Source:JobEngineJobEndpointExtensions.) - CLI:
stella orch ...covers exactly three subcommand groups —sources(list/show/test/pause/resume),backfill(start/status/list/cancel), andquotas(get/set/reset). (Source:CommandFactory.BuildOrchCommand.) There is nostella orch run start/streamsubcommand — drive runs via the HTTP API above. SBOM exploration is a separate top-level command (stella sbom ..., built byCommandFactory.BuildSbomCommand), not a child oforch.
Incident response
- Queue backlog: Check queue depth with
GET /api/v1/scheduler/runs/queue/lag(returns per-queuetransport/queue/depth, plustotalDepth/maxDepth) or thescheduler_queue_depthgauge. Scale workers or pause sources (stella orch sources pause <id>); verify no stuck plugin. - Repeated failures: Inspect the run record
errorfield and the run-evidence payload from/api/v1/jobengine/jobs/{jobId}/detail(queue idempotency key, worker lease proof, quota proof, dead-letter proof, release-script proof). Compare against the originating schedule/plugin version. - Plugin auth errors: rotate the relevant connector/source credentials in Authority/Console; the scheduler re-reads tenant-scoped credentials on the next run.
- Source/scheduler runaway: pause the offending source (
stella orch sources pause); confirm the queue drains via the queue-lag endpoint orscheduler_queue_depth.
Health checks
GET /healthz— liveness (anonymous, HTTP 200{ "status": "ok" }).GET /readyz— readiness (anonymous, HTTP 200{ "status": "ready" }).GET /api/v1/scheduler/runs/queue/lag— queue depth summary (policyscheduler:read+ fine-grainedscheduler.runs.read, same two-layer model as the other run reads).- Metrics (meter
StellaOps.Scheduler.Queue):scheduler_queue_enqueued_total,scheduler_queue_deduplicated_total,scheduler_queue_ack_total,scheduler_queue_retry_total,scheduler_queue_deadletter_total,scheduler_queue_depth(gauge), tagged bytransport/queue. - Metrics (meter
StellaOps.Scheduler.Worker):scheduler_run_duration_seconds,scheduler_runs_active,scheduler_planner_runs_total,scheduler_runner_segments_total,scheduler_runner_backlog,scheduler_graph_jobs_total, and related counters/histograms. - Logs/traces: the
StellaOps.Scheduler.WebServiceactivity source tags spans withtenant_id,schedule_id,run_id/job_id,http.method, andhttp.route(source:SchedulerTelemetryMiddleware).
Determinism/immutability
- Runs are append-only; do not mutate persisted run rows. Use new schedules/runs for fixes; retries create a fresh run linked via
retryOfrather than re-running the original in place. - Idempotency is carried on
RunReason.QueueIdempotencyKey(surfaced asidempotencyKeyin the JobEngine job detail). There is norunTokenorinputsHashconcept in the current implementation. - State machine:
Planning → Queued → Running → Completed | Error | Cancelled; terminal states are enforced byRunStateMachine(cancel/retry validate terminality).
Offline/air-gap
- Keep plugin bundles and schedule specs in sealed storage; no remote fetch.
- The graph-job backfill tool (
StellaOps.Scheduler.Tools→Scheduler.Backfill) ingests GraphBuildJob payloads from a local NDJSON file (--source,--batch,--dry-run,--timeout-seconds,--pg) — useful for seeding or replaying jobs offline. - Mutating run/schedule/source operations are written to the scheduler audit trail (
ISchedulerAuditService/AddAuditEmission); export audit data through the platform audit/export tooling for offline analysis. (There is no dedicated per-run NDJSON ledger export endpoint on the scheduler service itself.)
Quick checks
- [ ]
GET /healthzandGET /readyzreturn 200; queue depth normal (/api/v1/scheduler/runs/queue/lag). - [ ] Latest plugin bundle signatures valid.
- [ ] No secrets in logs (spot-check redaction).
- [ ] Error budget within SLO (see
docs/modules/telemetry/guides/metrics-and-slos.md).
