On this page

Submitting through the machine interface

How submit of the machine interface stays idempotent, the bounds of a source archive, packing a directory, and qualifying source staging.

Submit is idempotent by construction

submit takes a JSON request file. The request must carry a client_request_id (letters, digits, ., _, -; 1–128 characters, starting alphanumeric); the accepted fields are client_request_id, command, provider, gpu_type, pinned_host, vram_gb, max_cost_per_hour_usd, submitter_restarts, pin_to_provider, priority, repo, repo_ref, repo_workdir, repo_extras, pre_command, apt_packages, output_uri, verify_command, exclusive, terminates_agent, source_archive_path, input_objects, and secret_env.

submitter_restarts is an optional boolean. true says the submitting application starts every new execution itself, so Stado never puts the job back after a lost worker; its first loss ends it. Otherwise a job whose worker is lost goes back to the queue until the same kind of loss happens again; Stado keeps no restart count. A value that is not a boolean is refused with submitter_restarts must be true or false. A request stored before the field existed may still carry max_restarts; Stado reads that key only as submitter_restarts: true, because the one value ever sent in it asked for exactly that, and a non-number there is refused. A manual stado job rerun carries the original job's submitter_restarts into its new plan.

terminates_agent (a boolean, the same as stado submit --terminates-agent) declares a job that stops the agent running it, so the reapers finalize it as completed when that agent disappears. An omitted value does not mark the job as terminating its agent, and no default key is inserted into the normalized request: the request keeps the digest it had before the field existed.

stado machine submit --request-file request.json

The request is validated, then reserved under machine_requests/<client_request_id>.json together with the SHA-256 digest of the request. Retrying the exact same request replays the stored result; sending a different request under the same client_request_id is refused with IDEMPOTENCY_CONFLICT. A caller persists the complete request before submission and reuses it after a missing acknowledgement; rebuilding it from new observations can describe different work. A source_archive_path upload must satisfy the deployment's current source policy before admission or replay.

Source archive bounds

Source-bearing submissions require a machine.source_limits declaration. Requests without a source archive do not require it:

stado config set machine.source_limits \
  "{\"archive_bytes\":$ARCHIVE_BYTES,\"extracted_bytes\":$EXTRACTED_BYTES,\"members\":$MEMBERS,\"trailing_bytes\":$TRAILING_BYTES}"
stado config get machine.source_limits
stado config validate

All four fields are required positive whole numbers. archive_bytes bounds the compressed file, checked both from the opened file and while copying it. extracted_bytes bounds the sum of regular-file sizes reported by the tar headers. members bounds all archive entries, including directories. trailing_bytes bounds zero padding after the tar terminator; nonzero padding, another tar payload or another gzip member is still refused.

WC_MACHINE_SOURCE_LIMITS overrides the document with the same JSON shape. Missing, malformed, unknown, zero, negative or fractional bounds return INVALID_SOURCE_ARCHIVE, naming the declaration. An exceeded bound names its field and the observed size or member position. The declaration is read once before reserving the request and is shared by staging and authoritative readback. Existing path, symlink, duplicate-entry, member-type and digest checks remain mandatory. Stado Desktop's existing configuration operation accepts the same dotted key and JSON value; it does not choose a separate archive policy.

A tighter current declaration can refuse a retained source on retry, without changing the accepted request record or the existing job. A source rejection is not a cancellation. Requests that still satisfy the current declaration retain their digest and replay the same job rather than submitting another.

Packing a directory

stado storage archive SOURCE OUTPUT --json requires storage.archive_limits, independently of source-admission policy. Declare positive whole-number entries, path_bytes, member_bytes and total_bytes; there are no compiled packing caps. Entries include directories. Path bytes measure the encoded relative name, not character count. Member bytes bound each regular file; total bytes bound their sum. Exact bounds are accepted.

stado config set storage.archive_limits "$PACKING_LIMITS_JSON"
stado config get storage.archive_limits
stado config validate
stado storage archive "$SOURCE" "$NEW_ARCHIVE" --json

WC_STORAGE_ARCHIVE_LIMITS overrides the complete document. Missing policy returns archive packing limits are not declared: set storage.archive_limits with stado config set, or WC_STORAGE_ARCHIVE_LIMITS with its JSON document. Malformed JSON names the override. Missing, unknown, zero, negative or fractional fields name storage.archive_limits and the parser's cause. An exceeded bound names its field and observed size or entry state. Symlinks, unsupported file types, output inside the source and overwriting remain refused. The JSON receipt includes packing_limits. Every caller, including Spis, uses the selected Stado configuration or this override. Desktop Releases → Pack a directory as a release archive uses paths on the selected API host and preserves the same refusal; Compute providers can set the dotted configuration key. Run STADO_BIN="$CANDIDATE" node tests/storage/archive-limits.mjs for real local packing qualification. The test measures its fixture bounds, extracts and compares files, checks deterministic bytes and refusals, and retains source/binary identity, commands, statuses and observations in build/storage-archive/run-*/report.json.

Real source staging qualification

From the Stado checkout:

STADO_BIN="$CANDIDATE" node tests/machine/source-limits.mjs

The journey creates an archive through stado storage archive and submits it through the real machine CLI, using an isolated home and local store under build/machine-source-limits/. It checks declared bounds and meaningful refusals, uploaded bytes, the accepted request record, idempotent replay, configuration precedence and cancellation. Its accepted request omits the optional terminates_agent field. It retains source revision and patch, binary and test digests, commands, exit statuses, receipts and final-state observations in run-*/report.json, then removes its isolated home. It starts no worker and qualifies neither source extraction on a worker nor the rendered Desktop. A missing dependency or a machine command that emits no JSON is a failed or blocked run, never a passed source submission.

Source: this website