Schema · runner/host-action
draftRunner host action
One action a person approves for one enrolled runner to carry out on its host: the action's dotted id from the catalog, the runner that runs it, its parameters (typed per action) and what it does in one plain sentence. It travels inside a signed approval for a pinned run: the approval names this schema, carries the action as its canonical JSON (keys sorted, no whitespace), pins the SHA-256 of that JSON, and the action's runner_id is the run's own. Integers and strings only, so the canonical JSON is the same in every language. The object and every params object are closed: an action can do only what its params say. Catalog: k8s.delete-failed-pods (params.namespace; absent means every namespace); k8s.rollout-restart (params.namespace and params.deployment: restart one deployment and wait for its rollout; the cluster's own namespaces are refused); systemd.user-restart (params.unit: reload the user's systemd manager and restart one user service; Linux only); docker.prune-images (params.until_hours: remove the images unused and older than that many hours; never volumes); gh.cancel-run (params.repo and params.run_id: cancel one GitHub Actions run of an inorbithr repository); iohr.upgrade (no params: the runner upgrades its own iohr the way it was installed); preview.take (params.branch: take one preview-int/, feat/ or fix/ branch into the owner's Preview checkout, then check the Preview app); roll.task (params.rev, params.task and params.services: run one roll task at one commit of main on the deploy server); build.sweep-targets (params.mode: cap or guard the Rust build output of the core checkout and its worktrees); gh.release-rerun (params.run_id: re-run the failed jobs of one failed sdk release run whose commit passed its examples). agent.install-checks (params.block, params.commit and params.service: install one block of monitor checks from core at one commit of main into the agent's checks file and restart the agent; Linux only). disk.prune (params.target and, for docker-builder, params.until_hours: free disk on the host from one named target; host runs the host prune, docker-builder removes the Docker build cache older than that many hours, k3d-images removes the images no pod uses from each k3d node; it reports the free space before and after and fails when nothing was freed; Linux only). helm.runner-scale (params.release, params.max_runners and optionally params.min_runners: set one ARC runner scale set's maximum and minimum runners on the deploy server's cluster and read the values back). labs.reattribute-document (params.document, params.from, params.evidence_ts and the proven row counts params.expected_documents, params.expected_versions and params.expected_comments: on the deploy server, move one Decisions document's proven rows from one subject to Io with labs:reattribute, only while a dry run counts exactly those rows; the run can be reverted).
https://schemas.inorbit.hr/runner/host-action/v0.jsonEach version is a file at its own address and has a page of its own. The raw file is plain JSON, served without a sign-in.
- v0draftMay still be corrected; kept for five minutes. The newest; latest/ opens it. Shown on this page.schemas.inorbit.hr/runner/host-action/v0.json ↗
What changed
One version so far. When a second is published, this is where it is compared with v0, field by field.
Fields of v0, the newest version, by path. A name ending in [] is each item of a list. Every field not listed is refused.
actionstringrequiredThe action's id from the catalog: lower-case dotted words, for example k8s.delete-failed-pods.
pattern ^[a-z0-9]+(-[a-z0-9]+)*(\.[a-z0-9]+(-[a-z0-9]+)*)+$at most 64 charactersrunner_idstringrequiredThe enrolled runner that runs it: rnr_ and a ULID, the same as the run's runner_id.
pattern ^rnr_[0-9A-Z]{26}$paramsobjectrequiredThe action's parameters; their shape is the action's (see the catalog below).
effectstringrequiredWhat it does, one plain sentence a person reads before approving, for example: Deletes every failed pod in namespace example.
pattern ^[^\u0000-\u001f\u007f]+$at least 1 charactersat most 200 characters
Documents with made-up values, the same ones our tests check against the schema: the ones it accepts and the ones it must refuse.
19 valid documentsshow
{
"action": "gh.cancel-run",
"runner_id": "rnr_00000000000000000000000001",
"params": {
"repo": "inorbithr/example",
"run_id": 1
},
"effect": "Cancels GitHub Actions run 1 of inorbithr/example."
}59 documents the schema must refuseshow
Each file name says what is wrong with it. Paste one into the check below to see the schema refuse it.
{
"action": "k8s.delete-failed-pods",
"runner_id": "rnr_00000000000000000000000001",
"params": {
"namespace": "example; rm -rf /"
},
"effect": "Deletes every failed pod in namespace example."
}Paste a document to check it against v0 here, or use the address in your editor or CI.
"json.schemas": [
{ "fileMatch": ["my-file.json"], "url": "https://schemas.inorbit.hr/runner/host-action/v0.json" }
]check-jsonschema --schemafile https://schemas.inorbit.hr/runner/host-action/v0.json my-file.jsonajv validate --spec=draft2020 -s <(curl -s https://schemas.inorbit.hr/runner/host-action/v0.json) -d my-file.jsoncurl -s https://schemas.inorbit.hr/runner/host-action/v0.jsonThe raw file of v0, as published.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://schemas.inorbit.hr/runner/host-action/v0.json",
"title": "Runner host action",
"description": "One action a person approves for one enrolled runner to carry out on its host: the action's dotted id from the catalog, the runner that runs it, its parameters (typed per action) and what it does in one plain sentence. It travels inside a signed approval for a pinned run: the approval names this schema, carries the action as its canonical JSON (keys sorted, no whitespace), pins the SHA-256 of that JSON, and the action's runner_id is the run's own. Integers and strings only, so the canonical JSON is the same in every language. The object and every params object are closed: an action can do only what its params say. Catalog: k8s.delete-failed-pods (params.namespace; absent means every namespace); k8s.rollout-restart (params.namespace and params.deployment: restart one deployment and wait for its rollout; the cluster's own namespaces are refused); systemd.user-restart (params.unit: reload the user's systemd manager and restart one user service; Linux only); docker.prune-images (params.until_hours: remove the images unused and older than that many hours; never volumes); gh.cancel-run (params.repo and params.run_id: cancel one GitHub Actions run of an inorbithr repository); iohr.upgrade (no params: the runner upgrades its own iohr the way it was installed); preview.take (params.branch: take one preview-int/, feat/ or fix/ branch into the owner's Preview checkout, then check the Preview app); roll.task (params.rev, params.task and params.services: run one roll task at one commit of main on the deploy server); build.sweep-targets (params.mode: cap or guard the Rust build output of the core checkout and its worktrees); gh.release-rerun (params.run_id: re-run the failed jobs of one failed sdk release run whose commit passed its examples). agent.install-checks (params.block, params.commit and params.service: install one block of monitor checks from core at one commit of main into the agent's checks file and restart the agent; Linux only). disk.prune (params.target and, for docker-builder, params.until_hours: free disk on the host from one named target; host runs the host prune, docker-builder removes the Docker build cache older than that many hours, k3d-images removes the images no pod uses from each k3d node; it reports the free space before and after and fails when nothing was freed; Linux only). helm.runner-scale (params.release, params.max_runners and optionally params.min_runners: set one ARC runner scale set's maximum and minimum runners on the deploy server's cluster and read the values back). labs.reattribute-document (params.document, params.from, params.evidence_ts and the proven row counts params.expected_documents, params.expected_versions and params.expected_comments: on the deploy server, move one Decisions document's proven rows from one subject to Io with labs:reattribute, only while a dry run counts exactly those rows; the run can be reverted). Draft line: it may still be corrected.",
"type": "object",
"additionalProperties": false,
"required": [
"action",
"runner_id",
"params",
"effect"
],
"properties": {
"action": {
"description": "The action's id from the catalog: lower-case dotted words, for example k8s.delete-failed-pods.",
"type": "string",
"maxLength": 64,
"pattern": "^[a-z0-9]+(-[a-z0-9]+)*(\\.[a-z0-9]+(-[a-z0-9]+)*)+$"
},
"runner_id": {
"description": "The enrolled runner that runs it: rnr_ and a ULID, the same as the run's runner_id.",
"type": "string",
"pattern": "^rnr_[0-9A-Z]{26}$"
},
"params": {
"description": "The action's parameters; their shape is the action's (see the catalog below).",
"type": "object"
},
"effect": {
"description": "What it does, one plain sentence a person reads before approving, for example: Deletes every failed pod in namespace example.",
"type": "string",
"minLength": 1,
"maxLength": 200,
"pattern": "^[^\\u0000-\\u001f\\u007f]+$"
}
},
"oneOf": [
{
"properties": {
"action": {
"const": "k8s.delete-failed-pods"
},
"params": {
"$ref": "#/$defs/k8s-delete-failed-pods"
}
},
"required": [
"action"
]
},
{
"properties": {
"action": {
"const": "k8s.rollout-restart"
},
"params": {
"$ref": "#/$defs/k8s-rollout-restart"
}
},
"required": [
"action"
]
},
{
"properties": {
"action": {
"const": "systemd.user-restart"
},
"params": {
"$ref": "#/$defs/systemd-user-restart"
}
},
"required": [
"action"
]
},
{
"properties": {
"action": {
"const": "docker.prune-images"
},
"params": {
"$ref": "#/$defs/docker-prune-images"
}
},
"required": [
"action"
]
},
{
"properties": {
"action": {
"const": "gh.cancel-run"
},
"params": {
"$ref": "#/$defs/gh-cancel-run"
}
},
"required": [
"action"
]
},
{
"properties": {
"action": {
"const": "iohr.upgrade"
},
"params": {
"$ref": "#/$defs/iohr-upgrade"
}
},
"required": [
"action"
]
},
{
"properties": {
"action": {
"const": "preview.take"
},
"params": {
"$ref": "#/$defs/preview-take"
}
},
"required": [
"action"
]
},
{
"properties": {
"action": {
"const": "roll.task"
},
"params": {
"$ref": "#/$defs/roll-task"
}
},
"required": [
"action"
]
},
{
"properties": {
"action": {
"const": "build.sweep-targets"
},
"params": {
"$ref": "#/$defs/build-sweep-targets"
}
},
"required": [
"action"
]
},
{
"properties": {
"action": {
"const": "gh.release-rerun"
},
"params": {
"$ref": "#/$defs/gh-release-rerun"
}
},
"required": [
"action"
]
},
{
"properties": {
"action": {
"const": "agent.install-checks"
},
"params": {
"$ref": "#/$defs/agent-install-checks"
}
},
"required": [
"action"
]
},
{
"properties": {
"action": {
"const": "disk.prune"
},
"params": {
"$ref": "#/$defs/disk-prune"
}
},
"required": [
"action"
]
},
{
"properties": {
"action": {
"const": "helm.runner-scale"
},
"params": {
"$ref": "#/$defs/helm-runner-scale"
}
},
"required": [
"action"
]
},
{
"properties": {
"action": {
"const": "labs.reattribute-document"
},
"params": {
"$ref": "#/$defs/labs-reattribute-document"
}
},
"required": [
"action"
]
}
],
"$defs": {
"k8s-delete-failed-pods": {
"description": "k8s.delete-failed-pods: delete the pods in phase Failed. namespace names one namespace; absent, every namespace.",
"type": "object",
"additionalProperties": false,
"properties": {
"namespace": {
"description": "A Kubernetes namespace name: lower-case letters, digits and hyphens, at most 63.",
"type": "string",
"minLength": 1,
"maxLength": 63,
"pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$"
}
}
},
"k8s-rollout-restart": {
"description": "k8s.rollout-restart: restart deployment `deployment` in namespace `namespace` and wait for it to roll out. The cluster's own namespaces (kube-system, kube-public, kube-node-lease) are refused by the runner. Effect, as a person reads it: Restarts deployment example in namespace example and waits for the new pods.",
"type": "object",
"additionalProperties": false,
"required": [
"namespace",
"deployment"
],
"properties": {
"namespace": {
"description": "A Kubernetes namespace name: lower-case letters, digits and hyphens, at most 63.",
"type": "string",
"minLength": 1,
"maxLength": 63,
"pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$"
},
"deployment": {
"description": "A deployment's name in that namespace: lower-case letters, digits and hyphens, at most 63.",
"type": "string",
"minLength": 1,
"maxLength": 63,
"pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$"
}
}
},
"systemd-user-restart": {
"description": "systemd.user-restart: reload the user's systemd manager, then restart one user service. Linux only; a runner on any other system refuses it. Effect, as a person reads it: Restarts the user service example.service on the runner's host.",
"type": "object",
"additionalProperties": false,
"required": [
"unit"
],
"properties": {
"unit": {
"description": "A user service unit: lower-case letters, digits and @ . _ -, ending in .service, at most 71 characters.",
"type": "string",
"minLength": 9,
"maxLength": 71,
"pattern": "^[a-z0-9][a-z0-9@._-]{0,62}\\.service$"
}
}
},
"docker-prune-images": {
"description": "docker.prune-images: remove every image no container uses that is older than until_hours. Never volumes, networks or a system prune. Effect, as a person reads it: Removes the unused Docker images older than 24 hours on the runner's host.",
"type": "object",
"additionalProperties": false,
"required": [
"until_hours"
],
"properties": {
"until_hours": {
"description": "Only images created more than this many hours ago: 1 to 720 (30 days).",
"type": "integer",
"minimum": 1,
"maximum": 720
}
}
},
"gh-cancel-run": {
"description": "gh.cancel-run: cancel one GitHub Actions workflow run, with the runner host's own GitHub sign-in. Effect, as a person reads it: Cancels GitHub Actions run 1 of inorbithr/example.",
"type": "object",
"additionalProperties": false,
"required": [
"repo",
"run_id"
],
"properties": {
"repo": {
"description": "The repository, owner/name, of the inorbithr organisation only; the name is never . or ..",
"type": "string",
"maxLength": 110,
"pattern": "^inorbithr/(?!\\.\\.?$)[a-z0-9._-]{1,100}$"
},
"run_id": {
"description": "The workflow run's id: 1 to 2^53 - 1, so it is exact in every language.",
"type": "integer",
"minimum": 1,
"maximum": 9007199254740991
}
}
},
"iohr-upgrade": {
"description": "iohr.upgrade: the runner upgrades its own iohr to the newest release the way it was installed (Homebrew, or the install script's checked archive); never a downloaded script. No params. Effect, as a person reads it: Upgrades iohr on the runner's host to the newest release.",
"type": "object",
"additionalProperties": false,
"properties": {}
},
"preview-take": {
"description": "preview.take: take one branch into the owner's Preview checkout on the runner's host (mise run preview:take: fast-forward, else a merge; it stops on a conflict and changes nothing), then check the Preview app without restarting it (mise run preview:up --check). Effect, as a person reads it: Takes branch feat/example into the Preview checkout and checks the Preview app.",
"type": "object",
"additionalProperties": false,
"required": [
"branch"
],
"properties": {
"branch": {
"description": "The branch on origin: preview-int/, feat/ or fix/ and then lower-case letters, digits and . _ / -, at most 100 after the prefix; never with .. anywhere.",
"type": "string",
"minLength": 5,
"maxLength": 112,
"pattern": "^(preview-int|feat|fix)/(?!.*\\.\\.)[a-z0-9][a-z0-9._/-]{0,99}$"
}
}
},
"roll-task": {
"description": "roll.task: run one roll task at one commit on the deploy server, through the runner host's roll script (it checks the commit is on origin/main, then runs mise run <task> <services> in a checkout of that commit on the server). services is the task's arguments, space-separated; empty means none. Effect, as a person reads it: Runs roll task roll:services for example other-example at commit 0000000000000000000000000000000000000000 on the deploy server.",
"type": "object",
"additionalProperties": false,
"required": [
"rev",
"task",
"services"
],
"properties": {
"rev": {
"description": "The commit: 40 lower-case hex digits.",
"type": "string",
"minLength": 40,
"maxLength": 40,
"pattern": "^[0-9a-f]{40}$"
},
"task": {
"description": "The roll task, one of the catalog's.",
"type": "string",
"enum": [
"roll:check",
"roll:services",
"roll:ui",
"roll:running",
"roll:restart",
"roll:envoy",
"db:migrate",
"edge:gate-check"
]
},
"services": {
"description": "The task's arguments: empty, or up to 32 service names (lower-case letters, digits and hyphens, at most 40 each, never starting with a hyphen, so none is read as an option) separated by one space.",
"type": "string",
"maxLength": 1311,
"pattern": "^$|^[a-z0-9][a-z0-9-]{0,39}( [a-z0-9][a-z0-9-]{0,39}){0,31}$"
}
}
},
"build-sweep-targets": {
"description": "build.sweep-targets: bound the Rust build output of the core checkout and its worktrees on the runner's host (build output only; a target in use is never touched). mode cap caps every target now (mise run target:sweep); mode guard sweeps only the targets on a volume that is full enough (mise run target:guard). Effect, as a person reads it: Caps every Rust build target on the runner's host.",
"type": "object",
"additionalProperties": false,
"required": [
"mode"
],
"properties": {
"mode": {
"description": "cap or guard.",
"type": "string",
"enum": [
"cap",
"guard"
]
}
}
},
"gh-release-rerun": {
"description": "gh.release-rerun: re-run the failed jobs of one release workflow run of the sdk repository, with the runner host's own GitHub sign-in. The repository is fixed by the runner, never a param. The runner refuses unless the run is the release workflow on main, it concluded failure, and its commit has a green examples-run status. Effect, as a person reads it: Re-runs the failed jobs of sdk release run 100000.",
"type": "object",
"additionalProperties": false,
"required": [
"run_id"
],
"properties": {
"run_id": {
"description": "The workflow run's id: 100000 to 2^53 - 1 (six digits or more), so it is exact in every language.",
"type": "integer",
"minimum": 100000,
"maximum": 9007199254740991
}
}
},
"agent-install-checks": {
"description": "agent.install-checks: install one block of monitor checks into the runner host's agent checks file (~/.config/iohr-agent/checks.toml), then restart the agent. The block fixes the source in the core checkout, never a param: contract is devops/checks/generated/agent.toml, gates is devops/gates/gate-checks.toml and status-components is devops/checks/status-components.toml. The runner reads the source at the commit (which must be on origin/main), backs the file up with a timestamp, replaces or appends the block between its # BEGIN and # END markers (the BEGIN line names the commit), lints the file with iohr agent checks lint (a failure restores the backup), restarts the user service and checks the agent is connected again with at most 100 checks. Linux only; a runner on any other system refuses it. Effect, as a person reads it: Installs the contract checks from core at commit 0000000000000000000000000000000000000000 into the agent's checks file and restarts iohr-agent.",
"type": "object",
"additionalProperties": false,
"required": [
"block",
"commit"
],
"properties": {
"block": {
"description": "The block of checks, one of the catalog's; it fixes the source file.",
"type": "string",
"enum": [
"contract",
"gates",
"status-components"
]
},
"commit": {
"description": "The core commit to read the source at: 40 lower-case hex digits, on origin/main.",
"type": "string",
"minLength": 40,
"maxLength": 40,
"pattern": "^[0-9a-f]{40}$"
},
"service": {
"description": "The agent's user service, without .service: lower-case letters, digits and @ . _ -, at most 63 characters. Absent means iohr-agent. The runner refuses a service its config does not list.",
"type": "string",
"minLength": 1,
"maxLength": 63,
"pattern": "^[a-z0-9][a-z0-9@._-]{0,62}$"
}
}
},
"disk-prune": {
"description": "disk.prune: free disk on the runner's host from one named target, and nothing else: host runs mise run host:prune in the core checkout (Docker's build cache older than two days then capped at 5 GB, untagged images, per-commit image tags beyond the newest three, the k3d registry's old tags, unused images in each k3d node, shared Rust target files untouched for three days); docker-builder runs docker builder prune with an until filter of until_hours; k3d-images runs crictl rmi --prune in each k3d node. Never a volume, a :dev image, an image a container uses, /mnt/fast or any path a param names. The runner reads the free bytes of the Docker data root (or the root disk when Docker has none) before and after, reports both and fails when the run freed nothing. Linux only; a runner on any other system refuses it. Effect, as a person reads it: Removes the Docker build cache last used more than 48 hours ago on the runner's host.",
"type": "object",
"additionalProperties": false,
"required": [
"target"
],
"properties": {
"target": {
"description": "What to prune: host, docker-builder or k3d-images.",
"type": "string",
"enum": [
"host",
"docker-builder",
"k3d-images"
]
},
"until_hours": {
"description": "Only for docker-builder, and then required: remove the build cache last used more than this many hours ago, 1 to 720 (30 days). Any other target refuses it.",
"type": "integer",
"minimum": 1,
"maximum": 720
}
},
"if": {
"properties": {
"target": {
"const": "docker-builder"
}
},
"required": [
"target"
]
},
"then": {
"required": [
"until_hours"
]
},
"else": {
"properties": {
"until_hours": false
}
}
},
"helm-runner-scale": {
"description": "helm.runner-scale: set the maximum and minimum runners of one ARC runner scale set. The runner runs helm upgrade <release> <chart> --namespace arc-runners --version <the installed chart version, from helm get metadata> --reuse-values --set maxRunners=<max_runners> --set minRunners=<min_runners> --force-conflicts, where the chart is oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set, then reads the values back with helm get values -o json and fails when they are not the ones set. Nothing else of the release changes. min_runners above max_runners is refused. Effect, as a person reads it: Sets the runner scale set core-ci to at most 4 and at least 0 runners.",
"type": "object",
"additionalProperties": false,
"required": [
"release",
"max_runners"
],
"properties": {
"release": {
"description": "The runner scale set's Helm release, one of the catalog's.",
"type": "string",
"enum": [
"core-ci",
"core-images"
]
},
"max_runners": {
"description": "The most runners the scale set may start: 1 to 32.",
"type": "integer",
"minimum": 1,
"maximum": 32
},
"min_runners": {
"description": "The runners it keeps warm: 0 to 32, at most max_runners. Absent means 0.",
"type": "integer",
"minimum": 0,
"maximum": 32
}
}
},
"labs-reattribute-document": {
"description": "labs.reattribute-document: move the proven rows of ONE Decisions document from one subject to the Io agent, and nothing else. On the deploy server the runner checks the evidence snapshot named by evidence_ts against SHA256SUMS in ~/.inorbit/state/reattribute-evidence, takes a fresh detached worktree of core at origin/main (refused when mise run labs:reattribute has no --document flag), dry-runs mise run labs:reattribute --account <the host action's account> --from <from> --unproven keep --document <document> --dry-run and refuses, printing the counts, unless the proven rows it counts per kind are exactly the expected ones; then runs the same with --apply, requires its run lrat_ line, and re-runs the dry run to see no proven row left for that document. Only the run line and the counts are printed, never a title or a URL. The run can be reverted with mise run labs:reattribute --revert <run>. Effect, as a person reads it: Moves the proven rows of document ldoc_01M4P30371W9A3V5S3YCAHRTAN (documents 1, versions 2, comments 3) to Io; it can be reverted with labs:reattribute --revert <run>.",
"type": "object",
"additionalProperties": false,
"required": [
"document",
"from",
"evidence_ts",
"expected_documents",
"expected_versions",
"expected_comments"
],
"properties": {
"document": {
"description": "The Decisions document to move: ldoc_ and a ULID.",
"type": "string",
"pattern": "^ldoc_[0-9A-Z]{26}$"
},
"from": {
"description": "The subject the rows are filed under now: a lower-case UUID.",
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"evidence_ts": {
"description": "The evidence snapshot's stamp, as in labs-writes-<stamp>.jsonl: yyyymmddThhmmZ.",
"type": "string",
"pattern": "^[0-9]{8}T[0-9]{4}Z$"
},
"expected_documents": {
"description": "The proven documents a dry run counted for this document: 0 to 1000000.",
"type": "integer",
"minimum": 0,
"maximum": 1000000
},
"expected_versions": {
"description": "The proven versions a dry run counted for this document: 0 to 1000000.",
"type": "integer",
"minimum": 0,
"maximum": 1000000
},
"expected_comments": {
"description": "The proven comments a dry run counted for this document: 0 to 1000000.",
"type": "integer",
"minimum": 0,
"maximum": 1000000
}
}
}
}
}