Concept
Admission, lease-and-claim placement, terminal state.
The scheduler owns task admission and terminal state. Accepted workflows wait in a durable action log; workers on approved hosts claim them by lease and execute the reviewed trajectory.
How a task moves#
- Your service submits origin, action, justification, and idempotency key through the client.
- Admission validates the organization, allowlists, input shape, and justification, then appends the task to the durable action log.
- A worker on an approved host leases and claims the task.
- The worker executes the reviewed trajectory in the deployment-selected verified browser build, resolving scoped Skarbiec credential references — never plaintext from the request.
- The run records its terminal state and evidence; deployments with receipt issuance sign the outcome and evidence digest and return them to the caller.
Approved host#
Browser automation runs only on an approved host: the supervised environment whose local receipt matches the exact Stado release coordinate and checksum selected for the worker. Chromium and Firefox launch only when that receipt matches. The weles onboarding journey explains this boundary on its host-execution screen.
Managed service startup#
The managed launcher acquires its startup credentials and imports the HTTP API into the same Node process. It does not keep a second API process or a private Skarbiec broker. Individual browser runs remain finite tasks owned by the service.
Startup resolves the attested Skarbiec executable through Stado and runs skarbiec capability-status against SKARBIEC_CAP_SOCKET, read from the Skarbiec unit's environment as `stado service env show skarbiec --host <this host> --json` reports it (the launcher never guesses a socket path). A host that manages no Skarbiec unit refuses startup with that operation and host named. That command reports the broker's PID and checks its vault, capability-state and route-table paths against the caller's configuration. A listening broker is not proof that a credential or a browser workflow works.
Weles declares its sign-in origin routes through skarbiec route declare instead of replacing the shared route table. A route-declaration refusal retains its actual cause and leaves that origin's existing row unchanged; other origins and API startup continue. An unavailable shared broker or different broker state paths refuse startup during the earlier broker inspection. Another API already holding the port refuses the duplicate launcher; the existing process and shared broker are left alone.
Startup writes JSON startup_step records with operation, pid and began_at before invoking a dependency. Completion adds ended_at and duration_ms. This includes native executable probes, Skarbiec resolution and acquisition, individual route declarations, declaration loading, listener inspection and HTTP API import. A begin record without its matching completion names the operation still waiting; completion records alone do not assert success. Read the accompanying exit status or cause. No child arguments or credential values are included in these records.
A workload acquisition refusal retains the acquisition stage, selected Skarbiec origin, exact consumer/item/field, HTTP status and asserted workload identity. If its response body cannot be read, the diagnosis includes the actual body-read cause instead of a body-unreadable placeholder. Existing SKARBIEC_ACQUIRE_REASON classification and value redaction are unchanged.
Qualify this boundary with node tests/security/acquisition/refusal.mjs using the real source launcher and an isolated, actually refusing Skarbiec authority. Supply WELES_TEST_ACQUISITION_SCOPE_FILE, WELES_TEST_ACQUISITION_CONSUMER, WELES_TEST_ACQUISITION_ITEM, WELES_TEST_ACQUISITION_FIELD, WELES_TEST_ACQUISITION_EXIT, WELES_TEST_ACQUISITION_REASON, WELES_TEST_ACQUISITION_OPERATION and WELES_TEST_ACQUISITION_CAUSE alongside the launcher's actual workload proof and Stado resolution environment. The test retains checkout revision, command, exit status and stderr while omitting credential stdout. It creates no substitute authority. If the expected refusal disappears, the normal launcher may acquire and consume a one-time credential; use a disposable isolated field. No browser or provider sign-in runs.
Read the selected host's retained startup output with stado host unit-log <host> com.wisent.weles --lines <count> --json. These records remain available when startup has not reached the HTTP listener; worker status cannot diagnose a process it cannot reach. A dependency failure is not repaired by a manual restart.
The managed unit is com.wisent.weles, the host's one Weles process. Started by launchd as that unit, the launcher retires superseded Weles launch agents before inspecting the API port. A system LaunchDaemon remains outside the user's domain; startup reports it and leaves it running. A launcher started directly, including a qualification run, retires no service.
Run node tests/worker/launcher-steps.mjs only on a Stado-provisioned isolated worker with real startup dependencies and an unused API endpoint. Supply WELES_TEST_STARTUP_HOST, WELES_WORKER_API_BASE, WELES_WORKER_TOKEN and WELES_TEST_STARTUP_EXPECTED_EXIT through its qualification environment. A nonzero expected exit also requires WELES_TEST_STARTUP_FAILURE_OPERATION and WELES_TEST_STARTUP_FAILURE_CAUSE for the actual refused dependency. The runner refuses an already answering endpoint, captures actual startup records and binds a successful API response to its owned PID, selected host and source revision. It stops only the process it started and retains reports and complete output under build/real-tests/worker-launcher-steps.
Resident worker controls#
The CLI provides weles worker status --json, weles worker version --json, weles worker start --json, weles worker stop --json and weles worker restart --json. It uses the existing WELES_WORKER_API_BASE and WELES_WORKER_TOKEN operator connection. Without them the endpoint comes from stado service directory connect weles-admission and the bearer is the token of the item playing the worker's API role, read with stado credentials get --role echo-weles-api --field token; no item name is built in, and a role no item plays is refused by Stado with the role it read. The endpoint must use HTTPS unless it is loopback. Before any mutation, the CLI reads status and requires the resident worker schema; an older endpoint cannot use these commands to recreate the retired daemon. Output retains the requested endpoint, HTTP status and response; a refusal exits nonzero.
Weles Desktop exposes the same actions under System → Worker: Read status, Read identity, Start worker, Stop worker and Restart idle worker. The screen names the configured API endpoint before an action and shows the full response. Status and control responses also show the observed PID, worker state, readiness and task counts. An identity response is metadata, not a readiness claim. This connection is independent of the selected host on the Hosts screen. Configuration and HTTP failures remain visible, and native controls perform the same resident-schema check before a mutation.
A worker protocol refusal keeps the response that caused it. The CLI emits ok=false, error=worker_contract_invalid and the validation message, with observed_response holding the received object alongside operation, endpoint and http_status. It exits nonzero and does not send a control request after a refused status read. Desktop keeps the complete response and HTTP status under System → Worker, shows the decoding or resident-contract error, and does not label a refused report as ready for tasks. Status must supply worker; a control result must supply after and a valid before-state for the same API process.
GET /worker/status uses the general API bearer and reports the task dispatcher inside the API process. Its pid is the API process PID. The response includes active and queued task counts, draining and recovery state, the last failed operation, and readiness prerequisites. A running dispatcher does not imply that credential, deployment identity or browser prerequisites are ready.
Authenticated worker status also carries serviceIdentityFailure, the last failed deployment identity operation and its redacted message. read-deployed-service-identity names a failure reading the Stado-published placement, directory or transport; validate-deployed-service-identity names a refusal of the returned identity against this release. A successful identity read clears the field to null. Status reports the retained observation; it does not initiate another identity check. The CLI preserves this field in JSON and text output, and Desktop's System → Worker shows the operation and cause as Deployment identity was refused. The anonymous /api/v1/version response remains a generic deployed-service-identity-mismatch refusal and never includes this private diagnostic.
Use WELES_BIN=/absolute/path/to/weles WELES_TEST_IDENTITY_STATE=refused node tests/worker/identity-diagnostics.mjs against a dedicated deployment with a refused identity, then repeat with WELES_TEST_IDENTITY_STATE=ready after Stado restores its declared identity. The read-only runner exercises the real CLI and public version endpoint, checks the server's exact source revision, refreshes the observed identity before reading status again, and retains commands, exit statuses, CLI hash and HTTP documents under build/real-tests/worker-identity. It checks the retained cause on refusal, the private/public diagnostic boundary, and absence of a stale cause on success. It does not alter deployment state, restart a service, launch a browser or prove graphical rendering. An unmatched source revision or absent diagnostic is blocked, not passed.
Authenticated GET /worker/version captures the answering process's release and deployment identity when the request is handled. Weles does not periodically rewrite a deployment-version setting as a heartbeat. A stored version value or its timestamp is not proof of a live worker; read the worker endpoint and preserve any connection, authorization or identity failure.
weles worker version --json and Desktop's Read identity perform only GET /worker/version, without requiring a resident-status response first. This lets an operator inspect a process whose control protocol was refused without changing it. The collector resolves the package that supplied its own module, not the service's working directory. It hashes src/trajectories and src/worker rather than retired scripts paths. identity.capture_failures retains the field, failed command or file operation, native cause, error code, process exit status and stderr when available. Missing metadata remains null alongside that cause; a clean Git status is false, not unknown, and a failed per-file status is not reported as clean. A package without Git metadata can still report its available package and file hashes plus the exact Git refusal. The success envelope acknowledges the identity read, not complete deployment identity or browser readiness.
POST /worker/start is idempotent when the dispatcher is already running, and resumes a stopped dispatcher on the queue it kept. POST /worker/stop is start's inverse: the dispatcher starts no queued task, running tasks finish, new submissions still queue, and the worker state reads stopped with dispatcher.paused true; stopping a stopped dispatcher changes nothing. POST /worker/restart reloads an idle dispatcher's durable task state without starting or restarting a launchd job and without replacing the API process, and also ends a stop. Completed tasks are not executed again. Queued tasks retain their durable identities and ordering.
A restart during task execution, dispatch or shutdown returns HTTP Conflict worker_busy and leaves the active task alone. Concurrent control requests return HTTP Conflict worker_control_in_progress. During recovery, new submissions return HTTP Service Unavailable worker-recovering; status reads and cancellation remain available. A failed recovery answers worker_recovery_failed with its redacted cause in detail, records the operation and cause in the dispatcher's failure, keeps new dispatch paused, and requires an explicit start or restart request after the cause is repaired. Missing or invalid API authorization returns HTTP Unauthorized.
Public browser evidence tasks run one at a time inside the API process: a capture owns the host's browser while it runs, and the next queued task starts when none is active. There is no concurrency setting; GET /worker/status reports active and queued counts, not a configured concurrency.
POST /run returns a run's result unredacted (creds raw) only when the service declaration sets WELES_API_ALLOW_RAW_CREDS to the digit one. Without that grant a raw request is refused with raw_creds_forbidden and the caller uses redact (the default) or store. GET /healthz reports the grant as rawCredsAllowed. The API reads its bearer only from WELES_API_TOKEN and its recordings directory only from WELES_RECORDINGS_ROOT, which the release launcher exports; an unset recordings directory stops startup with the variable's name.
A credential run is one whose route registers an account or signs one in: the register, login and ads_login routes the worker's dispatch table declares. At most one runs at a time per action, account and parameters, and a second request joins it. In store mode the answer is accounts, the Skarbiec account items the run saved ({ item, platform, username }, from the account.json WSession.saveAccount writes beside its recordings); a run that saved none answers HTTP Unprocessable Content no_account_saved. In redact mode the answer is credential_produced and, for a failure, the trajectory's AUTH_FAILURE marker (auth_failure_unreadable when the marker is not a JSON code, stage and message), a scoped Skarbiec acquisition record, or auth_failure_unreported when the run declared neither, each with the last STEP stage.
The client window in GET /api/v1/version (currentVersion, minimumVersion, supportedGenerations) is the deployment's statement, read from WELES_CLIENT_CURRENT_VERSION, WELES_CLIENT_MINIMUM_VERSION and WELES_CLIENT_SUPPORTED_GENERATIONS in the service environment. An undeclared value is reported as null; a supportedGenerations value that is not an integer stops startup naming the variable and the text it held.
Native service installation and whole-process replacement belong to Stado. These endpoints never create a separate Weles worker service.
Qualifying artifact filesystem admission#
Authenticated task-status responses expose evidenceError as a redacted cause while retention is pending, or null when no retention failure is recorded. This adds no terminal status and does not substitute for a retained receipt. Poll the original task instead of resubmitting its browser action. A status read can retry that task's evidence retention using its existing completion; it does not rerun the browser.
Run node tests/artifacts/retention-status.mjs against an isolated managed task with a real, still-present storage refusal. Supply WELES_PUBLIC_TASK_API_BASE, WELES_PUBLIC_TASK_BEARER, WELES_RETENTION_TASK_ID, WELES_RETENTION_ORGANIZATION, WELES_RETENTION_TASK_STATUS, WELES_EXPECTED_RETENTION_CAUSE, WELES_EXPECTED_RETENTION_OPERATION and WELES_EXPECTED_RETENTION_RESOURCE. The resource is the independently known failed path or canonical private-object URI. The journey binds the actual worker source revision, checks an unauthenticated refusal and the authenticated pending task's cause, operation, resource, identity and lack of a terminal receipt, and retains request results. It creates or cancels no task, but its authenticated status read can retry artifact retention.
Artifact collection refuses a failed directory read rather than treating an unreadable subtree as empty. Proof creation refuses a failed file stat instead of silently omitting the proof. Both errors retain operation, path and native cause. Private-object upload response reads, upload receipt JSON decoding, refusal-body reads and successful-response stream reads retain the object URI, HTTP status and original cause; stream failures also report bytes already read. These failures do not acknowledge a complete retained tree; public-task retention persists them in evidenceError and remains pending instead of publishing a completed evidence receipt.
The signed-artifact SDK client distinguishes a failed signing request, an unreadable response body and invalid receipt JSON. Its error names the POST endpoint, the response status when available and the original cause; it never substitutes an empty signing receipt. This is the delivery boundary, separate from upload retention and its evidenceError state.
Run node tests/artifacts/delivery-client.mjs from the compiled checkout against an isolated real signing-service refusal. release/source-commit must match the checkout; WELES_ARTIFACT_MODULE_SHA256 is the independently recorded dist/worker/artifacts/artifact-delivery-client.js digest. Supply WELES_ARTIFACT_DELIVERY_URL and WELES_ARTIFACT_DELIVERY_TOKEN through the protected fixture environment, WELES_SIGNING_ARTIFACTS_FILE with actual isolated artifact locators, and independently observed WELES_EXPECTED_SIGNING_OPERATION and WELES_EXPECTED_SIGNING_CAUSE. The journey calls the real SDK and signing service, not a simulated server, and retains operation/cause evidence without the bearer. It may issue signed URLs if the expected refusal has disappeared; use only isolated fixtures. It starts no browser.
Run node tests/artifacts/filesystem-admission.mjs from the compiled Weles checkout. Its release/source-commit must match the checkout revision, and WELES_ARTIFACT_MODULE_SHA256 must match the independently retained dist/worker/upload-artifacts.js digest. Supply the deployment's real STADO_API_URL and WELES_STADO_OBJECT_API_TOKEN through its protected qualification environment; the report does not include the token.
The journey calls the actual artifact upload API against fresh, isolated filesystem fixtures under build/real-tests. It requires a non-directory run root and a missing root to fail with the directory-read operation, path and native cause, without changing the fixture. An observed empty directory must return no artifacts and create no upload proof. A silently omitted unreadable tree fails qualification. The runner removes only its own fixture and retains source identity, module digest, operations and failure details through the existing evidence recorder.
This journey qualifies filesystem admission only: it sends no object upload, starts no browser and does not establish private-object persistence, complete-run proof correctness or graphical rendering. Those remain separate real storage and run-inspection journeys.
Cancellation#
The client exposes one explicit cancellation operation with a required human-readable reason and idempotency key. There is no hidden retry anywhere in the contract: on ambiguous transport failure, reconcile through an approved status-read channel using the original idempotency key before submitting anything new. A cancellation request alone does not prove the executor stopped before an external side effect — inspect the signed outcome and service evidence.