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