Stellar ControlMission control · by Stellar Systems v0.1.0

Operations

Running Procedures

Launch a run from a run request, what is checked at launch, the run log and its events, and how to follow the state of a run.

A run is one execution of a procedure. It is launched by a declarative run request that binds each role of the procedure to a target, in an environment, with values for its inputs. The request is resolved against the current compiled configuration into a self-contained resolved run, which the executor interprets while logging every transition. The log is the run: its state, its evidence and its report all derive from it.

The run request#

YAML
# examples/config/runs/hot-standby.yaml
run: Hot standby test
environment: AIT
targets: {sat: sim-1, psu: psu-sim-1}
inputs: {tcu: TCU2, bus_voltage: 28 V}
KeyRequiredMeaning
runyesThe procedure
librarywhen several libraries have a procedure of that nameThe library of the procedure (run::ambiguous-procedure otherwise)
environmentyesThe environment of the topology the run is engaged in
targetsyesThe target of each role: exactly one per role
inputswhen the procedure has inputsThe value of each input
plannoThe pass the run is bound to; set by the scheduler for a scheduled run
faultsnoFaults of the simulations to switch on during the run, simulated targets only: see Faults scheduled by a run

Input values#

Values are written as in YAML, in the type of the input:

Input typeValue
Quantity (f32 V)With its unit: 28 V, or 28000 mV, converted into the unit of the input
Number without unit3, 27.5
IntegerA whole number within the bounds of its type
booltrue, false
EnumA value of the enum: TCU2
bytesHexadecimal: 0a1b or 0x0a1b
file_id of <type>The on-board identifier of the file: 12
fileThe SHA-256 of a content stored with stellar put or POST /v1/uploads

Launching a run#

Shell
stellar run hot-standby.yaml            # launch, then follow the log to the end
stellar run hot-standby.yaml --detach   # print the run identifier and return

stellar run sends the request to the API (POST /v1/runs), prints the warnings of the reply, then follows the log until the run finishes. It exits with 0 when the procedure succeeds, 1 otherwise, which makes it usable in scripts. Like every command that talks to the API, it takes --api, --as, --role, --token and --api-ca (see the CLI Reference).

The same request, as JSON, launches a run through the API:

Shell
curl -X POST http://localhost:8080/v1/runs -H 'X-Stellar-User: alice' \
  -d '{"run": "Hot standby test", "environment": "AIT",
       "targets": {"sat": "sim-1", "psu": "psu-sim-1"},
       "inputs": {"tcu": "TCU2", "bus_voltage": "28 V"}}'
JSON
{"run": "01K6A3D3Y4Q8K1P9W2N6R5T7XZ", "ir": "5c1f…", "warnings": []}

The API resolves the request, stores the resolved run in stellar_ir under its hash (ir), submits it to the executor on stellar.run.submit with the identifier of the run as message id, and answers 202 Accepted. See the runs API.

What is checked at launch#

Resolution: 422#

The request is resolved against the current snapshot, with every error at once:

CodeRefused because
run::unknown-environmentThe environment does not exist
run::unknown-procedure, run::unknown-library, run::ambiguous-procedureThe procedure cannot be found, or not uniquely
run::not-allowedThe procedure is not allowed in this environment
run::draft-not-allowedThe library, or the catalogue of a target, is a draft in an environment without allow_draft
run::missing-target, run::unexpected-targetA role without target, or a target for no role
run::unknown-targetThe target does not exist
run::wrong-platformThe target does not implement the platform of its role, nor imports components of it
run::catalogue-versionThe target implements (or imports) another version of the platform than the one the library was compiled against
run::not-importedThe procedure uses a component of its role's package that the platform of the target does not import
run::target-not-in-environmentThe target is not engaged in this environment
run::missing-input, run::unexpected-input, run::invalid-inputAn input without value, an unknown input, a value of the wrong type or unit
run::unknown-link, run::link-componentA link of via or link[…] that the target does not have, or that does not carry the component
run::fault-target, run::fault-step, run::fault-not-simulatedA scheduled fault names no role of the run, or a step it never enters, or is requested in in_orbit: see Faults scheduled by a run
run::plan-requiredThe procedure sends a hazardous telecommand, the environment requires a plan, and the request has none
run::must-be-scheduledSame, with a plan: such a run starts only from a schedule (POST /v1/schedules)
run::parameter-undefinedA configure … for <mode> or param … in <mode> reads a parameter that has no value on the target in that mode (see Modes and Parameters)

Identity: 401#

The environment of the run decides who may launch it: anyone without human orchestration, a declared identity or a token with identity: declared, a verified OIDC token with identity: jwt (api::anonymous, auth::jwt-required, auth::invalid-token). See Identity and Roles.

Passes: 409#

CodeRefused because
run::unknown-pass (422)plan names no pass of a target of the run
run::pass-cancelledThe pass of plan is cancelled
run::not-bookedIn in_orbit, the pass of plan is not booked
run::before-aosThe pass has not started yet
run::windowThe run may end after LOS minus scheduler.margin
run::receive-onlyThe pass has no uplink and the run sends telecommands
run::no-linkA manual run in in_orbit while the default link of a target is not bound, or its gateway reports no link

A manual run in in_orbit that may outlast a pass in progress is accepted with a warning in warnings, which stellar run prints. See Passes and Scheduling.

At start, by the executor#

The executor logs started, then checks that every target is ready (its links bound, see Links and Bindings) and takes a lease on every target. A target not ready, or held by another run, ends the run at once, failed, with the reason (the holder is named). See Leases and Crash Recovery.

How a run proceeds#

sequenceDiagram
    participant O as Operator
    participant A as API
    participant E as Executor
    participant T as Targets
    O->>A: POST /v1/runs (request)
    A->>A: resolve, check policies and passes
    A->>E: stellar.run.submit {run, ir, by}
    A-->>O: 202 {run, ir, warnings}
    E->>E: started, readiness, leases
    loop each action
        E->>T: telecommands (acknowledgement chain)
        T-->>E: acknowledgements, samples
        E->>E: log the events
    end
    E->>E: finished, leases released, report archived
  • The actions of the procedure run in order; a failure skips the actions up to the next if failed block, which runs, and the procedure ends failed (see if failed).
  • Within a step, the first statement that fails ends the attempt; retries follow the retry policy.
  • Every telecommand carries the run and the step in its PENDING event, and the environment of the run.
  • At the end, the leases are released and the executor archives the report (see Evidence and Reports).

The run log#

Each event of the log is published on stellar.run.evt.<run_id> (stream RUNS), with its position seq from 1, its time at, and its kind event. Its message id <run_id>-<seq> makes a republished event a duplicate. Steps and procedures are designated by their path, their names separated by /, an unnamed inline step noted #<n>: Hot standby test / Check TCU / TCU answers.

EventFieldsMeaning
startedprocedure, library (name@version), environment, targets, ir, by, inputsThe run starts; inputs are the resolved values
procedure_startedpathA procedure, or sub-procedure, starts
procedure_finishedpath, okIt ends
step_startedpath, attempt, inputsAn attempt of a step starts, with its resolved inputs
telecommand_sentpath, tc, target, telecommand (component[instance].name), changes_state, retry_ofA telecommand is about to leave: its identifier is logged before
telecommand_finishedpath, tc, state, detail, acksIts final state and its acknowledgement chain
checkedpath, statement (expect, check, wait_until), condition, ok, reason, samplesA condition judged, with the samples it was judged on
loggedpath, message, samplesValues written by a log: the text with each value in its place (unknown when missing, (stale) when too old), and the samples read
askedpath, prompt, roleA question to the operator; role for a hazardous confirmation
answeredpath, accepted, by, value, roleIts answer; no by when acknowledged automatically
answer_refusedpath, by, reasonAn answer not taken
mode_changedpath, target, modeA configure succeeded: the target is now in mode (see Modes and Parameters)
decision_requiredpath, reasonThe run waits for replay, skip, fail or abort
suspendedpath, bySuspended by an operator; leases released
continuedpath, by, choiceResumed, with the choice of the operator
taken_overbyAnother executor instance took the run up
step_finishedpath, attempt, ok, reasonAn attempt ends
finishedok, reasonThe run ends
JSON
{"run": "01K6A3D3Y4Q8K1P9W2N6R5T7XZ", "seq": 7, "at": "2026-10-02T10:15:04.211Z",
 "event": "checked", "path": "Hot standby test / Power the PPU", "statement": "expect",
 "condition": "psu.output_enabled is true", "ok": true,
 "samples": [{"target": "psu-sim-1", "component": "psu", "measure": "output_enabled",
              "value": true, "time": "…", "ground_time": "…"}]}

by in started is the identity of the requester: the operator, schedule <id> for a run fired by the scheduler, alarm <key> for an alarm reaction.

Following a run#

Shell
stellar watch <run>       # replay the log, then follow it to the end
stellar status <run>      # the state, once

stellar watch recognizes a run identifier (a ULID) from a target name. It prints one line per event and, when the run waits for someone, the command expected:

text
2026-10-02T10:15:01Z  started « Hot standby test » in AIT (sat=sim-1, psu=psu-sim-1) by alice
2026-10-02T10:15:01Z  step Hot standby test / Power the PPU
2026-10-02T10:15:01Z    send psu.set_voltage to psu-sim-1
2026-10-02T10:15:02Z    → VERIFIED
2026-10-02T10:15:04Z    expect psu.output_enabled is true: PASSED
2026-10-02T10:15:04Z    PASSED
…
2026-10-02T10:15:21Z  finished: PASSED

Through the API, GET /v1/runs/{id}/events is a WebSocket that replays the log, follows it and closes after finished (see WebSocket Streams).

State of a run#

stellar status <run> and GET /v1/runs/{id} compute the state from the log:

FieldMeaning
procedureThe procedure
stepThe step running, or the last one
suspendedWhether it is suspended
pendingA question (kind: ask, with its prompt and the role awaited) or a decision (kind: decision) waiting for an operator
finished, reasonThe outcome once finished, and why it failed
text
run 01K6A3D3Y4Q8K1P9W2N6R5T7XZ « Blank firing »
step: Blank firing / Fire the blank
running
waiting for an answer (ask) on Blank firing / Fire the blank: Fire the blank on the TCU?

GET /v1/runs?limit=N lists the most recent runs, newest first (20 by default, 200 at most), with their environment, targets, requester, start and last event; the web console shows them.

From Python#

The Python SDK has an API client that launches a run, follows its events, answers its questions and fetches its report from a script: see Python SDK. stellar generate client writes typed functions for the procedures of a library, one per procedure, on top of that client: see Code Generators.

Next#

Stellar Control · v0.1.0

↑↓ to moveEnter to open