# ol.smolvm.cloud.workflow Explicit asynchronous cloud workflows built from single-request operations. Every function returns a java.util.concurrent.CompletableFuture of its documented value. Validation, request and workflow failures complete it exceptionally. Deref wraps failures in ExecutionException. No blocking workflow variant or opaque Machine/Job/Episode handle is introduced. Normal success or exceptional completion occurs after required cleanup attempts and local stream closure. Cancelling the returned future marks it cancelled immediately, stops further ordinary scheduling, and requests cancellation of active work where possible. Required cleanup can continue after cancellation. A cancelled future is not proof of remote rollback, completed cleanup, or termination of commands/jobs. A timed deref does not cancel the workflow. Cleanup failures after cancellation cannot replace the cancelled result. Callers needing observable cleanup outcomes should await normal completion and use explicit remote cleanup plus verification, rather than treating future cancellation as a cleanup acknowledgement. Unknown mutation outcomes require reconciliation. Late child IDs received after cancellation still trigger the documented branch cleanup. HTTP failures use sanitized invoke error data. Resource IDs may accompany errors, but credentials, signed URLs, task data and response bodies do not. No retries occur except checkpoint part PUT retries. Options are open maps. Client configuration is explicit. All implementation stays under impl. ## wait-until-ready ```clojure (wait-until-ready client id) (wait-until-ready client id opts) ``` Polls machine `id` until it is ready. Returns a future of its latest info map. Ready means server `:ready true`. When `:ready` is absent, state `running` or `started` is accepted for older servers. States error, stopped and deleted fail with INVALID_STATE when not ready. Request errors propagate immediately. | | | | --- | --- | | Option | Meaning | | `:timeout-ms` | Positive total polling budget. Defaults to 120000. | | `:interval-ms` | Positive interval after an incomplete probe. Defaults to 1000. | | `:probe-agent?` | Boolean, defaults to false. After two seconds, a running machine without published ports may pass by successfully executing `true`. | Poll immediately. Use a monotonic deadline and bound each request wait by the remaining budget. Timeout completes the future exceptionally with TIMEOUT. A successful agent probe returns the latest info without inventing `:ready true`. This is not application health. No start, resume, deletion or automatic transport retry is performed. [source,window=_blank](https://github.com/outskirtslabs/smolvm-clj/blob/main/src/ol/smolvm/cloud/workflow.clj#L31-L49) --- ## connect ```clojure (connect client id-or-name) ``` Resolves `id-or-name` to a future of machine info, without starting it or waiting. Fetch the exact ID first. Only HTTP 404 or NOT_FOUND triggers a list lookup for an exact name. No match throws NOT_FOUND. Multiple matches throw AMBIGUOUS_NAME. Other failures propagate. No machine handle is created. [source,window=_blank](https://github.com/outskirtslabs/smolvm-clj/blob/main/src/ol/smolvm/cloud/workflow.clj#L51-L58) --- ## branch-exec ```clojure (branch-exec client id name command) (branch-exec client id name command opts) ``` Creates child `name` from source `id`, waits, executes `command`, then deletes it. Returns a future of the ordinary cloud exec result. Nonzero exits are results. The source is never deleted. Only a child ID returned by successful creation is eligible for cleanup, never a guessed ID or a pre-existing name. | | | | --- | --- | | Option | Meaning | | `:exec` | Open map of cloud exec options. Defaults to `{}`. | | `:wait` | Open map of [`wait-until-ready`](#wait-until-ready) options. Defaults to `{}`. | | `:cleanup-timeout-ms` | Positive delete-request budget. Defaults to 60000. | Cleanup is attempted after success, failure, timeout or cancellation with its separate budget. Normal completion waits for that attempt. Cleanup failure after execution success completes exceptionally with CLEANUP_FAILED and :machine-id. If execution or waiting also failed, that failure is primary and cleanup failure is suppressed. Cancellation is immediate and cleanup may continue afterward, including after a late creation response supplies an ID. No mutation is retried. An ambiguous creation response requires reconciliation. Source and child must satisfy the server’s branch prerequisites. [source,window=_blank](https://github.com/outskirtslabs/smolvm-clj/blob/main/src/ol/smolvm/cloud/workflow.clj#L60-L82) --- ## publish ```clojure (publish client id port) ``` Adds guest TCP `port` to machine `id`. Returns a future of updated machine info. Reads the current ports, preserves other guest ports, and replaces the set only when needed. Host ports are allocated by the server. A missing requested port in the reply throws INVALID_REPLY after the update may have succeeded. Serialize all port updates for a machine. This read/replace is not atomic. [source,window=_blank](https://github.com/outskirtslabs/smolvm-clj/blob/main/src/ol/smolvm/cloud/workflow.clj#L84-L92) --- ## unpublish ```clojure (unpublish client id port) ``` Removes guest TCP `port` from machine `id`. Returns a future of updated machine info. Reads the current ports and replaces the set only when the port is present. Other guest ports are preserved. Serialize all port updates for a machine. This read/replace is not atomic and is not retried. [source,window=_blank](https://github.com/outskirtslabs/smolvm-clj/blob/main/src/ol/smolvm/cloud/workflow.clj#L94-L101) --- ## job-wait ```clojure (job-wait client id job-id) (job-wait client id job-id opts) ``` Waits for `job-id` on machine `id` to exit. Returns a future of its final info map. Uses successive job-info long polls of at most 30 seconds. State `exited` succeeds even for nonzero exit status. State `lost` throws JOB_LOST. Invalid states or missing terminal exit status throw INVALID_REPLY. | | | | --- | --- | | Option | Meaning | | `:timeout-ms` | Positive total wait budget. Defaults to 120000. | Each request wait is bounded by the remaining monotonic budget. Timeout throws TIMEOUT. Future cancellation and timeout do not send job-cancel. Request errors are not retried. A nonterminal immediate reply is followed by a 100 ms pause. [source,window=_blank](https://github.com/outskirtslabs/smolvm-clj/blob/main/src/ol/smolvm/cloud/workflow.clj#L103-L118) --- ## job-read-all ```clojure (job-read-all client id job-id) (job-read-all client id job-id opts) ``` Collects one output stream of `job-id` on machine `id` through EOF. Returns a future of a byte array. Reads pages in order, using returned byte offsets. Empty nonterminal pages are long-polled again. Nonadvancing data or regressing offsets throw INVALID_REPLY. An empty nonterminal reply is followed by a 100 ms pause before another long poll. This does not combine stdout and stderr. | | | | --- | --- | | Option | Meaning | | `:stream` | `:stdout` or `:stderr`. Defaults to `:stdout`. | | `:timeout-ms` | Positive total read budget. Defaults to 120000. | | `:max-bytes` | Positive aggregate size limit, at most Integer/MAX_VALUE. Defaults to 16777216. | Uses pages at most 1048576 bytes and long polls at most 30 seconds. Exceeding the aggregate limit throws LIMIT_EXCEEDED without returning partial output. Timeout throws TIMEOUT. Workflow timeout or future cancellation does not cancel the remote job. Streaming larger output is possible with the existing job-output builder. [source,window=_blank](https://github.com/outskirtslabs/smolvm-clj/blob/main/src/ol/smolvm/cloud/workflow.clj#L120-L139) --- ## file-write ```clojure (file-write client id path data) (file-write client id path data opts) ``` Uploads `data` to absolute guest `path` on machine `id`, then optionally chmods it. Accepts the same text, bytes, File or Path data as cloud/file-write. Returns a future of nil. This is not atomic. A chmod failure leaves the uploaded file in place. | | | | --- | --- | | Option | Meaning | | `:mode` | Integer permission bits 0 through 07777. Omission skips chmod. Zero explicitly removes permissions. | Chmod uses argv, not a shell string. A nonzero chmod exit throws EXEC_FAILED. No upload or chmod retry is performed. Use cloud/file-write for one request. [source,window=_blank](https://github.com/outskirtslabs/smolvm-clj/blob/main/src/ol/smolvm/cloud/workflow.clj#L141-L154) --- ## checkpoint-upload ```clojure (checkpoint-upload client path) (checkpoint-upload client path opts) ``` Uploads local checkpoint `path` using service-allocated raw PUT parts. `path` is a nonblank string, File or Path naming a nonempty regular file. Returns a future of completed checkpoint metadata. Checks metadata and file readability before remote allocation. Opens file sections during part invocation and closes every section on completion. Keep the file unchanged until normal completion or until cancelled work has released its resources. | | | | --- | --- | | Option | Meaning | | `:parallel` | Concurrent part uploads, 1 through 16. Defaults to 4. | | `:part-attempts` | Attempts per part, 1 through 4. Defaults to 4. | | `:timeout-ms` | Positive total workflow budget. Defaults to 1800000. | | `:progress` | Optional function of aggregate bytes read and total file bytes. Called serially after positive part reads and failed-attempt resets. | Allocation and completion each make one API request. Only part PUTs retry. Match pinned Go: transport failures and every non-2xx part response retry, bounded by part-attempts, the total deadline and cancellation. Default four attempts use delays of 2, 4 and 8 seconds. Each attempt opens a fresh exact file section. Validation/file errors and progress callback failures do not become HTTP retries. No additional attempt starts after cancellation. Track bytes read separately for each part and serialize aggregate callbacks. Each positive sender read updates that part. A failed attempt resets its count to zero and reports the new aggregate before a retry or final failure. Aggregate progress can move backward and can reach total before storage accepts the data. It measures reads, not acknowledged bytes. No synthetic initial or completion callback is promised. Callback failure stops further scheduling and completes exceptionally with PROGRESS_FAILED after active work is cancelled/settled and local resources close. No callback exception message or cause is exposed. Presigned URLs must be HTTPS, with no user info or fragment. Storage requests contain no API bearer, cookies or API headers and never follow redirects. Each PUT has its exact content length. Plan sizes and part counts are checked before transfer. URLs and provider error bodies are not exposed in failures. Completion sends no ETags. No automatic reallocation or completion retry occurs. Timeout stops scheduling, cancels active work where possible and completes exceptionally after active operations settle and local resources close. Explicit future cancellation is immediate. Local cleanup may finish later. Progress callbacks can finish during that cancellation cleanup, but none run after all active work settles. Partial uploads are not automatically deleted. Normal failures identify the checkpoint when its ID is known. Checkpoint deletion exists, but abort/deletion of incomplete uploads is not established. Cancelled futures cannot deliver a newly learned checkpoint ID. Use raw steps when recording the allocation before transfer is required. The remote result of an interrupted mutation can be uncertain. This is raw multipart object upload, not multipart/form-data. [source,window=_blank](https://github.com/outskirtslabs/smolvm-clj/blob/main/src/ol/smolvm/cloud/workflow.clj#L156-L207)