Skip to main content

Delivery-line proofs

Orbit’s delivery line uses five repository commands. Each command prints one JSON object on stdout, has --help, and exits nonzero on failure. The error text says what to do next. Commands that can run a side effect support --dry-run and default to read-only work. They add no proof field on a task or subtask. They reuse the existing task fields and fails_on_base. The style matches bin/review-check, the Project task check, and doctor: one structured result, no repair, and a next step on failure. Run every command from the repository root.

Shared result

Success prints one JSON object and exits 0. Failure prints one JSON object on stdout, writes the next step on stderr, and exits 1. Wrong flags exit 2. --help prints the command contract and exits 0. Machine output contains no heading, progress line, or ANSI sequence.

bin/bug-repro

Run one command against current main the same way the task engine runs a fails_on_base command: extract main, overlay the given paths, and run the command. Pass when that command exits nonzero. Do not file a task.
When --main is omitted, the command names current main only after the local origin/main or main SHA matches git ls-remote origin main. It does not fetch. A cached ref that differs is main_stale. Fetch origin main, then run again. --main pins a ref and skips that check. The command copies installed vendor and node_modules directories into the extracted tree, then copies each path from the working tree, including an uncommitted file. It registers no Git worktree and does not change the checkout. A --paths value that is absolute, that leaves the tree, that is a symlink, or that is not a file fails before the run. When the command exits nonzero, stdout includes main_sha, command, exit_code, and paths. When it exits 0, the verdict is not_reproduced. The next step is to stop: the failure is not on current main, so do not file a task.

bin/task-group-check

Validate a task-group payload’s shape against CreateTaskGroupRequest without creating a group. This local check does not read the Project repository. The Gateway checks deliverable paths against the selected base when the group or subtask is created or updated. Then apply Orbit’s repository policy from the Orbit Tasks skill.
The command refuses an unsupported top-level key, a duplicate top-level key, or a body that is not one JSON object. It checks project_id, title, brief, optional status, notify_coder, notify_on_settle, and tasks with the same required, prohibited, length, list, and fails_on_base rules as create. project_id must be an integer of at least 1. The check does not look up the Project row. The check does not read a subtask’s topology. The Gateway validates it when it creates the group, by the rules in Declare a topology, and the same section owns admission and readiness. It then requires one ordered group: the payload is that group, and tasks is a nonempty list in order. A wrapper with groups is accepted only when that list has exactly one group. Every subtask brief must contain a Goal line and an Acceptance line, as the skill template writes them. A subtask that only restates the final gate fails. That includes a goal or acceptance that only names review-check, the candidate gate, the task check, or preparing or proposing the pull request. Every handoff already runs the task check, and the last subtask’s approval proposes the pull request. A bug group’s first code-changing subtask must carry fails_on_base true on a command deliverable, with paths. A docs-only first subtask does not count as the first code-changing subtask. A bug group with no failing command fails. The command does not add a proof field.

bin/pr-head-check

Read the pull request, its reviews, its Required checks run, and its file diff. Pass only when those records match the current head and the diff has no leftover the merge skill already names. Do not merge.
The result includes merged, copied from the pull request record, alongside the existing passed and mismatch fields. Before any review or merge attempt, read this field even when the command exits nonzero. When the JSON has merged:true, stop: the pull request is already merged and the merge flow is a terminal no-op. Do not review it again or run gh pr merge. This applies even when passed is false or the result names a missing review or another mismatch. When merged is false, all review and merge requirements still apply. A missing or unreadable merged field is not evidence of a merge; resolve the read before proceeding. The command keeps a review only when its commit_id equals the current full head SHA and its state is APPROVED. COMMENTED, CHANGES_REQUESTED, DISMISSED, and PENDING do not count. GitHub refuses APPROVE from the pull request author. An empty successful review list is missing. A failed, partial, or unparsable read is unreadable and is not treated as empty. On that same full head SHA, the GitHub Actions check run named Required checks must have status completed and conclusion success. That run’s head_sha must equal the pull request head. Newer gh wraps gh api --paginate --slurp check-runs in a one-element array of the check-runs object. Older gh has no --slurp and returns the object. Both shapes flatten to the check_runs list. A check that is not completed is pending. A completed check whose conclusion is not success is failed. No run with that name is missing. The admin bypass is not a successful check. Those checks must also have run with the current tip of the pull request’s base branch, which is main for Orbit pull requests. The command compares the base branch with the head (gh api repos/{owner}/{repo}/compare/main...<head-sha>). The checks ran with the tip when the head contains it (behind_by is 0), or when the pull request’s recorded base.sha equals it, because the pull_request run merged the head into that base. Otherwise the result is base_behind: green on an older base does not prove the head still works on current main. The JSON base object names the base ref, the recorded base SHA, the current tip, and behind_by. A merged pull request skips this comparison. The diff fails when it adds a leftover the merge skill already names: GitHub auto-merge, --auto, --admin, an orbit tasks:merge command, a Gateway merge endpoint, a merge SDK, MCP, or API contract, a ruleset change, an App permission change, an object-storage-host, or a linear-reference. Markdown sentences that explicitly prohibit those leftovers are exempt. A negation in a URL, another sentence, or product code does not exempt the match. The scan also skips the detector (bin/pr-head-check), the delivery-line test that names the leftover refusal, and recorded delivery-line fixtures. Those files hold the forbidden names so the checker can refuse them. A leftover added in product code still fails. Do not add hostnames under the generic upcloudobjects.com provider suffix (object-storage-host), linear.app references, Linear issue IDs, or Linear issue, ticket, project, product, task, integration, or workspace wording (linear-reference). A trailing sentence period or DNS root dot still matches the provider host; a domain that extends the suffix does not. Patterns and fixtures contain no real storage bucket, endpoint, region, or account names. This repository does not wake ReviewBot when a GitHub pull_request_review event arrives without a new push; wiring that wake is a follow-up. A review event never bypasses bin/pr-head-check: an APPROVED review and successful Required checks must still match the exact current head. On any mismatch the JSON names the mismatch. For an unmerged pull request, the next step is to review the new head. For merged:true, stop instead, regardless of the mismatch. The command never runs gh pr merge.

bin/deploy-verify

Read the live tip version, /up, and gateway status. Pass when APP_VERSION equals the given merged SHA, /up is up, and gateway status is ok. Those are the checks recorded in ops verified-merges. Do not deploy and do not roll back. Each Gateway release runs the same checks as its verify step. The command stays a read-only check from outside the Gateway.
--dry-run exits 0 and does not claim a live pass. --fixture evaluates the recorded /up body, gateway status, and APP_VERSION 2f214816deae marker shape from 2026-10-01. A fixture pass is not a live pass. The JSON says source is fixture or live. Python HTTPS calls need SSL_CERT_FILE set to Orbit’s root CA, or they fail certificate verification. That file is the Gateway profile ca_path after orbit gateway:trust. An unreachable result from this machine is not a reason to change the live checks. instance:rollback is not part of this command. A rollback selects one retained production release and does not undo deploy steps, environment writes, or database files. See Roll back.

bin/gateway-smoke

Smoke-test a Gateway release after its switch, on the Gateway host. Each Gateway release runs it after the verify step and the web switch, and stores its JSON on the release record. Operators run it by hand the same way. It does not deploy, switch, restart, or roll back.
Run it as the Gateway account from the release under test. It then uses that release’s apps/cli/orbit with the account’s Gateway profile. Python HTTPS calls need SSL_CERT_FILE, as for bin/deploy-verify. A restarted scheduler starts its first tick on the next full minute, so a release does not pass --wait-for-tick. It confirms the first tick afterwards. tasks_tick runs artisan schedule:list with only HOME, PATH, and LANG, as the release’s own artisan commands do. With --wait-for-tick, it reads orbit tasks:status --json every 2 seconds until the total limit, and passes once last_tick_at is at or after --since, or no more than --tick-within seconds before the run. All checks run at the same time. Each has its own time limit, and --timeout bounds the whole run, so a release waits at most that long. A check that does not finish in time is timeout. The tasks:status tick record comes from the Gateway clock, so compare it on the Gateway host. Each check command runs in a session of its own. When the run itself receives SIGTERM, SIGINT, or SIGHUP, it first kills every running check command. Then it prints a result with error terminated and exits 128 plus the signal number. So a caller that stops smoke leaves no check behind. The write check is off by default, because it writes to the live Gateway on every release. Use a dedicated Project for it. The file is named gateway-smoke-<sha12>-<random>.txt. When a step fails after create, the check removes the file and records cleanup as removed. When that fails too, cleanup is failed and the message names the entry to remove.

Result

The command prints one JSON object and exits 0 when no check failed or timed out. Skipped checks do not fail the run. It exits 1 when a check failed or timed out, and 2 on wrong flags. Store the object as it is: schema is 1, and new fields are added without changing existing ones.

Why it works this way

These reasons explain the design. Check them before you propose a change.

Existing fields already name the failing command

The first subtask that changes code in a bug group already carries a command deliverable with fails_on_base true and paths. A new proof field would duplicate that contract and break the Orbit Tasks policy. bin/bug-repro is the operator command that runs the same overlay against current main before anyone files a task.

Repository commands stay outside the engine

The Gateway task engine is generic. Orbit’s policy lives in the repository skill and these checks. Putting the proofs in bin/ beside review-check keeps Project policy out of the engine, the CLI product, and the SDK.

Doctor’s verify-only boundary applies to deploy

bin/deploy-verify reads the same three signals ops already records after a merge: tip version, /up, and gateway status. It does not deploy, and it does not roll back. Wiring instance:rollback into the verify command would hide that a rollback does not restore data or undo steps.

A stale head is named, not merged

bin/pr-head-check stops when the review or Required checks belong to another SHA. The next step is to review the new head. It also stops when those checks never ran with the current main tip. A branch that was green before main moved can still break main without a textual conflict. The next step is to update the branch, wait for green, and review the new head. Auto-merge, --admin, and a tasks:merge command remain the leftovers the merge skill already forbids.

A smoke test reads, and writing is opt-in

A release smoke test runs on every main commit against the live Gateway. Read checks prove that the release serves its version, API, web build, scheduler, and agent view without changing state. A document write proves storage too, but it writes on every release, so an operator turns it on for a dedicated Project.

A release does not wait for the first tick

schedule:work runs schedule:run only at the start of a minute. After the runtime handoff restarts the scheduler, the first tasks:tick can come up to a minute later. A drain that ends just after a minute boundary costs nearly that whole minute. Smoke instead proves what can fail at once: the scheduler runs from the new release, and that release loads its schedule with tasks:tick in it. The first tick itself is confirmed after the release, where a silent scheduler alerts without holding up the next release.

The scheduler records its own tick

tasks:tick writes the time it takes its lock into the Gateway cache, and tasks:status reports it. It also records the version of the code that ran it, so a release can tell its own scheduler’s tick from the previous one’s. The smoke test reads the time through the CLI with --wait-for-tick. A journal read depends on log permissions and log wording, and a process list cannot tell an idle scheduler from a stuck one.