P02 — operator-facing documentation (REQ-091, REQ-092, REQ-093; gate C-22). docs/cli.md (REQ-091): - Full CLI command/flag reference: every command/subcommand with synopsis, flag tables (name/type/default/description), one-line examples. - Global flags (--json, --system, --config, --no-deprecation-warnings). - Output modes (text/json/watch), env vars, exit codes. - Deprecated surface callout boxes (daemon, cert, node-join-mTLS, HCL jobspec) pointing to v0.11 removal. docs/jobspec.md (REQ-092): - Markdown frontmatter schema reference: all top-level keys, block reference (runtime/ports/env-secrets/volumes/restart/update/service/ health/lifecycle/constraints/affinity/tasks), kinds matrix (Job/Service/DaemonSet required vs allowed), CEL subset grammar, body byte-exact preservation (R-015), deprecated HCL callout. docs/ingress.md (REQ-093): - Traefik ingress reference: service->Traefik mapping (D-175), R-007 socket-vs-TCP-bind semantics, generated YAML shape (routers/services/ healthCheck), atomic reload (C-10), drain (weight:0), TLS (certResolver, trust domain, step-ca), worked-example pointer to examples/full-stack/, v0.11 forward limitations. All factual claims grounded in live codebase (gate C-22). Cross-links verified to resolve. ---ci--- project: orca phase: 2 milestone: v0.10 status: execute ---/ci---
13 KiB
Orca Jobspec Reference
This document is the complete reference for the Orca jobspec format — the Markdown-with-frontmatter specification that describes workloads.
Canonical format (v0.9): Orca uses Markdown with YAML frontmatter as the canonical jobspec format (R-013/R-014). The legacy HCL format is supported via an adapter during the migration window but is deprecated (see HCL jobspec below).
File formats
The orca job run command dispatches by file extension:
| Extension | Parser | Body |
|---|---|---|
.md |
ParseMarkdown (canonical) |
Verbatim after closing --- (R-015 byte-exact) |
.yaml / .yml |
parseYAMLFile |
Whole file as frontmatter; body empty |
.hcl |
ParseHCL (legacy adapter) |
Empty (deprecated) |
Minimal example
---
kind: Job
name: my-job
runtime:
one_of: process
command: /bin/echo hello
---
# My Job
This body is preserved byte-exact and carried to the target node.
Top-level keys
| Key | Type | Default | Required | Notes |
|---|---|---|---|---|
orca-spec-version |
string | "" |
no | Free-form version tag (e.g. "1") |
kind |
enum | — | yes | One of Job, Service, DaemonSet |
name |
string | — | yes | Workload name (trimmed, non-empty) |
count |
int | 1 |
no | Job: must be 1; Service: ≥1; DaemonSet: not allowed |
runtime |
block | nil | see kinds | Runtime block (or per-task runtimes in a task group) |
ports |
block list | nil | Service: yes | Array of port mappings |
env |
block map | nil | no | Environment variables |
secrets |
inline/block list | nil | no | Secret names (resolution in v0.11) |
volumes |
block list | nil | no | Volume mounts |
restart |
block | nil | Service/DaemonSet: yes | Restart policy |
update |
block | nil | Service: yes | Update strategy |
service |
block | nil | no | Traefik route definition (implied for Service; not allowed for Job/DaemonSet) |
health |
block | nil | Service: yes | Health check |
lifecycle |
block | nil | no | Pre-stop / post-start hooks |
constraints |
list | nil | no | CEL expressions (node selection) |
affinity |
block list | nil | no | Co-location / anti-affinity rules |
tasks |
block list | nil | no | Task group (multi-process alloc) |
timeout |
duration string | "" |
no | Job timeout |
schedule |
block | nil | DaemonSet: yes | Schedule mode |
Kinds
Job
A one-shot batch task. Runs once and exits.
countmust be 1 (or unset). UseServicefor replicas.serviceblock is not allowed (no Traefik route for Jobs).restartoptional (defaults tonever/on-failure).timeoutoptional.
Example:
---
kind: Job
name: data-migration
runtime:
one_of: process
command: /usr/bin/python3 migrate.py
timeout: 300s
env:
DB_URL: postgres://localhost/mydb
---
Service
A long-running, load-balanced workload with a Traefik route.
count≥ 1 (number of replicas).portsrequired (at least one).restartrequired;modeone ofservice,on-failure,never.updaterequired;strategyone ofrolling,canary,blue-green.runtimerequired (or a task group with per-task runtimes).healthrequired (Traefik routing requires health checks).serviceblock optional (implied for Service; use forbindoverride).service.bindif present must be a valid IP (127.0.0.1= TCP opt-in; default = Unix socket).
Example:
---
kind: Service
name: web
count: 3
runtime:
one_of: process
command: /usr/bin/httpd
ports:
- name: http
port: 8080
restart:
mode: service
attempts: 5
delay: 2s
update:
strategy: rolling
max_parallel: 1
health:
check_type: http
interval: 5s
timeout: 1s
unhealthy_threshold: 2
constraints:
- node.role == "web"
---
DaemonSet
A workload that runs on every matching node.
schedulerequired;modeone ofevery-node,matching,mandatory.portsnot allowed (no Traefik route by default).countnot allowed (implicit = matching nodes).restartrequired.
Example:
---
kind: DaemonSet
name: log-shipper
schedule:
mode: every-node
runtime:
one_of: process
command: /usr/bin/fluent-bit
restart:
mode: service
---
Block reference
runtime
The runtime backend for the workload.
| Field | Key | Type | Default | Notes |
|---|---|---|---|---|
one_of |
one_of |
string | — | Runtime type (see below) |
image |
image |
string | "" |
Container image (for podman) |
command |
command |
string | — | ExecStart command |
Supported runtime types (one_of):
| Type | Description | Requires |
|---|---|---|
process |
Direct process execution via systemd (default) | systemd on target |
wasm / wasmtime |
WASM via wasmtime CLI (apt-installed on peer, SSH exec) | wasmtime on target |
podman |
Container via podman | podman on target |
pve-vm |
Proxmox VM via qm |
Proxmox node |
pve-ct |
Proxmox container via pct |
Proxmox node |
proxmox |
Alias for Proxmox runtime | Proxmox node |
An empty/missing Runtime or OneOf is runtime-agnostic (always fits
the runtime axis in the scheduler).
ports
Array of port mappings. Required for Service.
| Field | Key | Type | Default | Notes |
|---|---|---|---|---|
name |
name |
string | — | Port name (used in socket path) |
port |
port |
int | — | Container port |
host_port |
host_port |
int | 0 |
Host port |
protocol |
protocol |
string | "" |
Protocol (e.g. tcp) |
host_ip |
host_ip |
string | "" |
Host IP |
Example:
ports:
- name: http
port: 8080
host_port: 80
protocol: tcp
- name: https
port: 8443
host_port: 443
env
Environment variables. Scalar values or secret references.
env:
FOO: bar
BAZ: "qux"
SECRET_REF:
from: "secret:db-password"
INLINE: {from: "secret:token"}
Secret resolution (
from: "secret:...") lands in v0.11-P03. The parser stores the reference; the emitter will emitEnvironmentFile=/LoadCredential=in v0.11.
secrets
List of secret names. Inline array or block list.
secrets: ["db-password", "api-token"]
# or
secrets:
- db-password
- api-token
volumes
Array of volume mounts.
| Field | Key | Type | Default | Notes |
|---|---|---|---|---|
name |
name |
string | — | Volume name |
type |
type |
string | — | Volume type (e.g. host) |
source |
source |
string | — | Source path (or replicate:<peer>,<peer> for Syncthing) |
target |
target |
string | — | Mount target |
read_only |
read_only |
bool | false |
Read-only mount (true/yes/on/1) |
Example:
volumes:
- name: data
type: host
source: /data
target: /data
read_only: true
restart
Restart policy.
| Field | Key | Type | Default | Notes |
|---|---|---|---|---|
mode |
mode |
enum | — | never, on-failure, service |
attempts / max_retries |
attempts or max_retries |
int | 0 |
Max retries (both keys accepted) |
delay |
delay |
duration string | "" |
Retry delay (e.g. 2s) |
update
Update strategy. Required for Service.
| Field | Key | Type | Default | Notes |
|---|---|---|---|---|
strategy |
strategy |
enum | — | rolling, canary, blue-green |
max_surge |
max_surge |
int | 0 |
Max surge |
max_parallel |
max_parallel |
int | 1 (clamped to count) |
Max parallel updates |
min_healthy_time |
min_healthy_time |
duration | "" |
Min time healthy before next batch |
healthy_deadline |
healthy_deadline |
duration | "" |
Deadline for health |
canary |
canary |
int or "<n>%" |
— | Canary size (int count or percentage) |
auto_promote |
auto_promote |
bool | false |
Auto-promote canary (true/yes/on/1) |
Strategies:
- rolling: batches of
max_parallel, each batch waits for healthy. - canary: canary batch first, then
promote(manual orauto_promote), then remaining inmax_parallelbatches. - blue-green: all new allocs start in parallel, wait healthy, then
cutover.
Transactional update execution lands in v0.11-P10. The current emitter computes the plan; execution is a v0.11 deliverable.
service
Traefik route definition. Implied for Service; not allowed for
Job/DaemonSet. See docs/ingress.md for details.
| Field | Key | Type | Default | Notes |
|---|---|---|---|---|
name |
name |
string | — | Service name |
port |
port |
int | — | Service port |
bind |
bind |
string (IP) | "" |
Bind mode: empty = Unix socket (default); 127.0.0.1 = TCP opt-in (R-007) |
host |
host |
string | "" |
Host (stored, not yet consumed by emitter) |
route_id |
route_id |
string | "" |
Route ID (stored, not yet consumed by emitter) |
health
Health check. Required for Service.
| Field | Key | Type | Default | Notes |
|---|---|---|---|---|
check_type |
check_type |
string | — | Check type (e.g. http) |
interval |
interval |
duration string | — | Check interval (e.g. 5s) |
timeout |
timeout |
duration string | — | Check timeout |
unhealthy_threshold |
unhealthy_threshold |
int | 0 |
Failures before unhealthy |
Maps to Traefik healthCheck stanza (path: /healthz).
lifecycle
Lifecycle hooks. Maps to systemd ExecStartPost / ExecStop.
| Field | Key | Type | Default | systemd mapping |
|---|---|---|---|---|
post_start |
post_start |
string list | nil | ExecStartPost= (runs after main starts) |
pre_stop |
pre_stop |
string list | nil | ExecStop= (runs before kill) |
Example:
lifecycle:
pre_stop:
- /bin/sh -c 'sleep 5'
- /usr/local/bin/drain.sh
post_start:
- /usr/local/bin/warm-cache.sh
constraints
CEL-subset expressions for node selection. Inline array or block list.
constraints:
- node.role == "web"
- region == "us"
# or inline
constraints: ['node.role == "web"', 'region == "us"']
CEL subset grammar (hand-rolled, no CEL dependency):
- Node attributes:
node.hostname,node.kind,node.cpus,node.memory,node.tags,node.runtimes - Bare identifiers: equivalent to
node.<name> - Literals: string (
"..."), int - Comparisons:
==,!=,>=,<=,>,< - Membership:
in,not in - Boolean:
and,or,not, parentheses - Anything outside the subset returns an error (node skipped, not silently mis-evaluated)
affinity
Co-location / anti-affinity rules.
affinity:
- target: zone == "a"
weight: 80
- target: web
weight: -50 # anti-affinity (negative weight)
target: CEL expression or bare workload name (for name-based co-location).weight: positive = co-locate, negative = anti-affinity.- Affinity is a hint (not a gate); evaluation failures are ignored.
tasks (task group)
Multi-process alloc (P06). When tasks is non-empty, the alloc runs
multiple processes, each as its own systemd unit, grouped under a
systemd target.
tasks:
- name: app
runtime:
one_of: process
command: /usr/bin/httpd -f
env:
LOG_LEVEL: debug
- name: sidecar
runtime:
one_of: wasm
command: /bin/wasm-runner sidecar.wasm
- A task with no
runtime:inherits the top-levelspec.Runtime. - Each task can have its own
env:overlay. commandfalls back:task.Command→task.Runtime.Command→spec.Runtime.Command.- Task names must be unique within the group.
Kinds matrix
| Feature | Job | Service | DaemonSet |
|---|---|---|---|
count |
must be 1 | ≥ 1 | not allowed |
ports |
optional | required | not allowed |
service block |
not allowed | optional (implied) | not allowed |
restart |
optional | required | required |
update |
optional | required | optional |
health |
optional | required | optional |
runtime |
optional | required (or task group) | optional |
schedule |
optional | optional | required |
tasks |
optional | optional | optional |
| Traefik route | no | yes (implied) | no (by default) |
Body semantics
The body after the closing --- is preserved byte-exact (R-015) —
including trailing newlines, CRLF, BOM in body, and --- inside code
fences. The body is carried verbatim to the target node. It is not
interpreted as commands/scripts by the parser today.
Deprecated: HCL jobspec
The legacy HCL jobspec format is supported via an adapter during the migration window. It is deprecated in v0.9 and will be removed in v0.11.
job "hello-orca" {
}
task "greet" {
command = "/bin/echo"
args = ["hello", "from", "orca"]
}
The adapter converts this to a *WorkloadSpec{Kind: "Job", Name: "hello-orca", Count: 1, Runtime: {OneOf: "process", Command: "/bin/echo"}}. Use .md for all new jobspecs.
See also
- docs/cli.md — CLI reference (including
orca job run) - docs/ingress.md — Traefik ingress configuration
- examples/full-stack/ — Full-stack example jobspecs