Reference
Every way a Weles operation fails, and what fixes it.
Generated from the failure catalogues weles 0.8.0 declares (weles errors list --json). Each cause names what failed, why, the state the refusal shows, the fix, its code and statuses, and the test that provokes it.
Operations#
Each operation below declares every way it fails, including the ways nobody has met yet. A refusal prints `weles: <what failed>: <why>; observed: <state>`, then `fix:` and the cause, and exits 2 for a refusal of the request's own form, 69 when retrying unchanged can help, 1 otherwise; with --json the same cause is printed as one JSON document. `weles errors show <operation>` prints a catalogue; `weles errors check` refuses a cause without its fix or its test and an error message that names no failed step.
| Operation | Started through | Causes | What it does |
|---|---|---|---|
accounts.login-account | the operations that use it | 17 | Resolve the subscription a caller names to the one account and login that sign it in, read that login's material for the sign-in, and keep the grant it earned on the subscription. |
accounts.sign-in | weles accounts list [--provider <provider>], weles accounts refresh --provider <provider> --subscription <id>, weles accounts logout --provider <provider> --subscription <id> | 4 | List the accounts the vault's subscriptions resolve to, refresh one account's sign-in on the managed executor, and sign one out by erasing the grant the vault holds for it. |
accounts.session | a trajectory signing an account in or registering it (resolveAccountSession) | 4 | Restore an account's proxy exit and persona before its browser starts, so its sign-ins look like the same device. |
accounts.pinned | a trajectory signing an account in or registering it (resolveAccountSession) | 8 | Read the proxy pinned to an account, refusing a burned, retired or failing exit, and write back what the session assigned. |
accounts.social | a trajectory that signs in with a social account (getSocialAccount), a session whose cookies were refused (markCookiesStale) | 2 | Choose the account a run signs in with, and mark its cookies stale so its login runs again. |
mail.alternative | a session reading a sign-up mail, the Google app password run handing its password to Skrzynka, a PARP sign-in reading its mailbox | 2 | Find the Skrzynka program through the mail alternative Stado names for Weles. |
mail.program | every Skrzynka command Weles runs: reading an inbox, listing and syncing mailboxes | 4 | Run one Skrzynka command for an organization and read its JSON answer, relaying its refusal with its code. |
mail.inbox | a session waiting for a sign-up code or link (check_email) | 2 | Read the messages Skrzynka holds for an address, newest first, from the sender a session waits on. |
mail.domain | a trajectory registering an account on a new address | 3 | Pick the inbound domain a new account's address is made on, least used first, skipping broken and blocked ones. |
proxy.pin | a trajectory signing an account in or registering it (resolveAccountSession) | 13 | Rebuild an account's pinned proxy from what its pin states and prove it still opens a tunnel to the platform. |
proxy.exit-geography | WSession.start of a session without a proxy (openSessionBrowser) | 4 | Ask the geography lookup where an exit is: the country, timezone and network of a proxy exit address, or of this host itself for a session that runs without a proxy. |
trajectories.parp-login | the feng/auth/parp_login trajectory | 7 | Sign in to lsi.parp.gov.pl by asking a password reset and setting the new password, kept only in the vault item its managed declaration names, from the reset mail. |
run.apple-capabilities | POST /run of an Apple login, agreement or certificate action, the guarded Apple run (apple/guarded/run.mjs) | 5 | Read the email, password and two-factor capabilities an Apple run is bound to, before anything is filled. |
identity.sms | a session that needs a phone number (WSession getPhoneNumber), discord phone verification | 4 | Rent a phone number in the country asked from the first SMS provider that holds one, and read the code texted to it. |
identity.sms-provider | renting a number, reading its code and releasing it, at JuicySMS or SMS-Activate | 8 | Ask one SMS provider's HTTP API, with the key the vault holds, never quoting the key. |
accounts.acquired | claude/account/acquire | 2 | Record a bought account as its login row and Brama subscription item before it is signed in. |
captcha.provider | every captcha a session solves through a service | 15 | Ask each solving service whose key Weles holds, receive its answer at the run callback, and record the price of the one that solved. |
captcha.proxy | a captcha task solved through the run's proxy | 4 | Hand a solving service the proxy the token must be solved from, as address, port and login. |
captcha.recaptcha | a session solving reCAPTCHA v2 in the page or as a token | 10 | Solve reCAPTCHA's image grid in the page, choosing the tiles by solving services and the vision model, or get a token from a service. |
secrets.managed-passwords | every credential write and read of a managed password (Microsoft, Microsoft Entra, PARP) | 8 | Read the deployment-owned list of accounts whose passwords Weles writes and reads, from one owner-only file. |
secrets.capability | a browser step filling a credential (fill_credential), a captcha, SMS or Apple two-factor step redeeming its capability | 24 | Check the one-shot capability a run was handed and redeem or cancel it at the Skarbiec broker as this workload. |
proxy.public-address | weles doctor, the machine's public address, a proxy exit preflight | 3 | Ask the declared plain-text service which public address this host, or an exit, browses from. |
runtime.cost | every run that records a captcha, SMS or proxy cost | 5 | Keep the run's captcha, SMS and proxy costs in its rollup file under recordings/_costs. |
human.timing | a browser session moving the pointer or typing | 2 | Draw the pauses and choices of human-like input from the run's recorded seed, so a run can be replayed. |
accounts.observed | weles accounts billing [--provider <provider>] [--account <account>], weles accounts quota [--provider <provider>] [--account <account>] | 0 | Read the billing (plan, status, cost, invoices) and the quota Weles observed on the provider's own pages for each subscription it collected, as the subscription records hold them. |
reauth.admission | the operations that use it | 5 | Admit a request to the Weles API's account routes: Brama's reauth bearer, the provider and the subscription it names, and the account revision the caller resolved. |
reauth.sign-in | POST /reauth, POST /reauth/resolve, POST /reauth/logout | 2 | Sign one subscription's account in again on this host, resolve it without signing in, or sign it out by erasing the grant the vault holds for it. |
reauth.sign-in-outcome | the operations that use it | 5 | Say why a sign-in run that started ended without a grant, in the failure of its last result line. |
reauth.accounts | POST /reauth/accounts, POST /subscriptions/billing, POST /subscriptions/quota | 0 | List the accounts the vault's subscriptions resolve to, of one provider or of every provider, and the billing and quota Weles observed for each subscription it collected. |
reauth.subscription-runs | POST /subscriptions/acquire, POST /reauth/authorize | 8 | Buy a new account of a provider and sign it in for Brama, or complete one OAuth authorization a coding-agent harness started for an account the pool holds, answering the provider's redirect to the harness. |
admission.caller | the operations that use it | 2 | Authenticate the caller of a public task route: the bearer is the Spis admission token this worker was started with. |
admission.request | the operations that use it | 6 | Read the body of a public task request: JSON under a declared media type and an Idempotency-Key, for the organization the bearer admits. |
admission.lookup | the operations that use it | 2 | Find one public task by the id its submission answered: the id is a UUID and this worker holds its record. |
admission.submit | POST /api/v1/tasks | 37 | Admit one public browser-evidence task: the request is authenticated, its form and its Spis binding are checked, the deployed service identity and the page host are verified, and the task is recorded and queued. |
tasks.document | the operations that use it | 2 | Read one JSON object a task or receipt command is given as a file, or - for standard input. |
tasks.public | weles tasks submit --request <file|-> (--consumer <consumer> | --endpoint <url>) --role <role> [--idempotency-key <key>], weles tasks show <task-id> (--consumer <consumer> | --endpoint <url>) --role <role>, weles tasks await <task-id> (--consumer <consumer> | --endpoint <url>) --role <role>, weles tasks cancel <task-id> --reason <text> (--consumer <consumer> | --endpoint <url>) --role <role> [--idempotency-key <key>] | 3 | Submit a browser-evidence task to the public task API, read it, wait until it ends, or cancel it, with the bearer of the item playing the caller's role, answering what the API answered. |
receipts.verify | weles receipts verify --receipt <file|-> --keys <file> | 10 | Verify one signed receipt of any outcome against a map of trusted public keys, offline, and print the claims it signed. |
admission.status | GET /api/v1/tasks/<task-id> | 4 | Read one public task, or with ?wait=terminal hold the answer until it ends; a terminal task carries its signed receipt. |
admission.cancel | POST /api/v1/tasks/<task-id>/cancel | 4 | Cancel one public task: a queued task ends cancelled at once, a running one is stopped and ends cancelled, a terminal one is answered as it ended. |
browser.release | requireBrowserRelease, selectedBrowserVersion | 14 | Find the executable of the selected, checksum-verified weles-chromium or weles-firefox release: the deployment's pin or the host's selection record, used only when the install's receipt matches it. |
browser.engine | the operations that use it | 23 | Launch a Weles browser and act on its pages, every failure the engine reports classified by its own words and quoted whole. |
session.launch | WSession.start, openSessionBrowser, accountProfileDirectory, loadOperatorCdpConfig | 11 | Bring up the browser of a WSession: resolve its proxy, file its account's profile, adopt the operator's browser or launch the selected release, and attach the protocol observers. |
session.cookies | SessionStore, personaSignature, proxySignature | 5 | Keep each session label's cookies in the vault setting browser_sessions, put them into a new browser, and sign the persona and egress a cookie jar was minted under. |
session.input | WSession.click, WSession.fill, WSession.fillSelector, WSession.waitFor, WSession.patchAccount, WSession.fillIdentity, WSession.textedCode, resolvedPlaceholders | 12 | Act on a WSession's page as a person would: click and fill the control a step names, type only into a field whose markup allows it, and put in the values the session generated or rented. |
session.credential-fill | WSession.fillCredential, WSession.fillIdentity, assertLiteralControl | 11 | Put a credential a capability names, or a generated identity value, into the one page field whose own markup declares its class, and refuse literal text into such a field. |
session.capture | scrubHar, writeJsonFile, attachInstrumentationCheckpoints, captureStepArtifacts | 12 | Write what a WSession recorded: its HAR with the request bodies withheld, the instrumentation artifacts, each frame's instrumentation log, and why a failed step has no artifacts. |
agent.inference | POST /run, a run whose step asks a model, weles mcp, a page question, a trajectory generating text or an image | 6 | Hand one model request to Stado's inference alternatives and read the answer and the alternative that gave it. |
agent.model | POST /run, a run whose step asks a model, weles mcp, a page question, a trajectory asking a model | 5 | Ask a model one question, with the page's images and the functions it may call, and read its text or its one function call. |
agent.draft | POST /run, a draft-first run, POST /run with WELES_TRAJECTORY_RECORDING set | 7 | Ask the model for a draft-first run's trajectory and read its steps and notes. |
agent.flows | FetchAccountValue, the generic vision login (agent login.run), the typed vision extractors | 12 | Sign in with the generic vision login, read one account value, keep its learned route, and read typed values off a page. |
agent.tools | dispatch | 8 | Run one browser tool call an agent's model made on its WSession, refusing an argument the tool does not take before anything acts. |
agent.decision | askLlm | 5 | Read the browser tool and its arguments the model chose as an agent's next step. |
agent.loop | POST /run, a generic_browser_task run, execute (the browser agent of the library) | 10 | Run one browser task step by step (replayed or chosen by the model) until done, keeping every step taken. |
vision.screenshot | a page question (askPage, checkPage, identifyPage, findClickTarget), weles mcp, a page question | 5 | Capture the page a question is asked about as a PNG, through its screenshot() or the DevTools connection. |
vision.question | a page question (askPage, checkPage, identifyPage, findClickTarget), weles mcp, a page question, POST /run, a step that asks about the page | 12 | Ask a vision model one question about the page, recorded in the run's vision folder, escalating a refusal to the screenshot's transcription. |
proxy.addresses | loadSpecialAddresses, specialBlockOf, assertGloballyReachable | 11 | Judge whether a network address is public by IANA's Special-Purpose Address Registries, read once per process, naming the block an address falls in. |
agent.evidence-target | installBrowserEvidencePolicy, configuredTarget, publicAddresses, resolveBrowserEvidenceTarget, enforceBrowserEvidenceToolPolicy | 15 | Hold a browser-evidence run to the one public https origin and address set it was admitted with: every other request is withheld into its ledger, and so is every interactive tool the policy does not admit. |
browser.session | weles mcp | 8 | Drive a Weles browser one operation at a time over JSON-RPC: start a browser, open pages in it and navigate, read, act and assert on them by the ids the server hands out. |
browser.open | weles open <url> | 3 | Open one page in a browser launched from the command line, and answer its address, title and HTTP status, with a screenshot and its text when asked. |
executor.request | the operations that use it | 16 | Reach the managed Weles executor from an operator's shell: find it through Stado's service directory or WELES_WORKER_API_BASE, read its bearer, send the request and read its JSON answer. |
executor.run | the operations that use it | 4 | Start one trajectory as a detached run on the managed Weles executor and read its persisted record back: the answer must start the run that was asked, and the record must be that run's, bound to the request that started it. |
run.request | POST /run | 12 | Admit one run of a named trajectory: its action, how its output is answered, its parameters and the Google login they name, its account and profile, and when detached the id it is read back by. |
run.answer | POST /run | 3 | Answer a run the request waited for: the accounts it saved, its redacted outcome, or its raw result. |
run.builder | POST /weles-builder | 3 | Turn a goal stated in prose into a run of the browser agent from a neutral page, whose executed steps become a new reusable trajectory. |
runs.manage | weles runs list, weles runs show <run-id>, weles runs answer <run-id>, weles runs cancel <run-id>, GET /runs, GET /runs/<run-id>, POST /runs/<run-id>/answer, POST /runs/<run-id>/cancel | 15 | Attend the managed worker's runs: list each run it has a live child for and what it waits for, read one run's record, tell a run waiting for a person what the person did, and cancel a run with who and why. |
security.account | weles security --provider google --login-role, weles security --run | 6 | Read whether a Google account has two-step verification on, without changing it: start google_mfa_status on the managed executor for the Skarbiec login a role names, and read the run's persisted observation back. |
identity.password | the operations that use it | 5 | Generate the password of an account Weles registers or resets: one character of every class, the rest from all of them, at the length the vault policy or WELES_GENERATED_PASSWORD_LENGTH states. |
password.app | weles password --provider google --login-role, weles password --provider google --login-item, weles password --run | 7 | Issue a Google app password for Skrzynka: start google_app_password on the managed executor for the Skarbiec login a role or item names, bound to the Skrzynka organization and the mailbox account named, and read the run's record back. |
oauth-callback.app | weles oauth-callback --provider google --login-role, weles oauth-callback --provider google --login-item, weles oauth-callback --run | 8 | Make one OAuth client accept a new callback address: start google_oauth_callback on the managed executor for the owner's Skarbiec login and the item holding the client, each by role or item, with the https callback, and read the run's record back. |
sign-in.authorization | the operations that use it | 16 | Read the command line of one authorized Apple run, take the operator confirmation in words, have Stado issue three one-use capabilities on the execution host, start the run on the executor, and read it back with --run. |
sign-in.apple | weles login --provider apple, weles login --run <run-id> | 0 | Sign one Skarbiec Apple account in on a Stado host, in one authorized run, and read the run back. |
sign-in.agreement | weles agreement --provider apple, weles agreement --run <run-id> | 0 | Have the Apple Account Holder accept the updated Developer Program License Agreement, in one authorized run, and read the run back; Apple refuses the team's provisioning until it is accepted. |
certificate.apple | weles certificate --provider apple, weles certificate --run <run-id> --certificate-out <file> | 11 | Issue one Apple Developer ID Application certificate: the key and request are made on this machine, one authorized run has the Account Holder issue it in the portal, and --run writes the certificate and, with --private-key and --store-host, keeps the identity in the vault the release recipes read. |
worker.control | weles worker status, weles worker version, weles worker start, weles worker stop, weles worker restart, GET /worker/status, GET /worker/version, POST /worker/start, POST /worker/stop, POST /worker/restart | 9 | Read and control the managed worker: what deployment answers and its process, the resident task dispatcher's state, and stopping, starting or restarting that dispatcher in the same process. |
worker.launch | node src/worker/weles-api-launcher.mjs | 9 | Start the Weles API as its unit runs it: read the declared address, refuse a port another process holds, retire the units it replaces, check the release's declarations, acquire the startup secrets and declare Weles's capability routes. |
worker.api | the operations that use it | 8 | Admit a request to the Weles API's token routes and read it: the bearer it carries, the address it names and the body's JSON and fields. |
worker.request-body | readBody, readText | 1 | Read a request's whole body off its connection, for the route to parse. |
worker.diagnostics | GET /diagnostics/<run>, GET /diagnostics/<run>/file?path=<file>[&wait=terminal] | 4 | Answer a run's evidence: its manifest, one file of it, or the run outcome once the run has one. |
worker.run-evidence | the operations that use it | 10 | Read what a run left on this worker: the recording roots its evidence may live under, one file of it, the named documents it recorded, and its persisted outcome, waiting for the outcome when asked. |
worker.captcha-answer | POST /captcha/answers/<run>/<token> | 2 | Keep the answer a captcha provider pushes for a task a run of this worker created, for that run to read. |
worker.serve | node src/worker/weles-api-server.mjs | 12 | Start the Weles API process: prove the release it answers under and read the settings its service declaration states, before the port is bound. |
settings.value | the operations that use it | 4 | Read a setting a Weles process is started with by name: required text, a secret long enough to sign with, a TCP port, or a whole count. |
settings.origin | the operations that use it | 7 | Read the origin of a service a Weles process reaches: HTTPS, or plain HTTP to this machine's loopback, without credentials, path, query or fragment. |
worker.pages | POST /pages/snapshot, POST /pages/form-export | 10 | Read or export a rendered public page in the worker's own browser: admit the target, load it, and read it or fill its form and take the file it downloads. |
worker.records | POST /records/accounts/list, POST /records/accounts/get, POST /records/accounts/upsert, POST /records/accounts/update, POST /records/settings/get, POST /records/settings/set, POST /records/services/list | 1 | Serve the trajectory accounts, runtime settings and service records Weles keeps in its vault to the products that manage them. |
errors.catalogue | weles errors list, weles errors show, weles errors check | 6 | Print every way a Weles functionality can fail, as it declares it, and hold a checkout to the error standard: complete causes, a real or listed test for each, and no generic failure sentence in the source. |
cli.command | weles <command>, weles onboarding <action>, weles import <trajectory-export.json> --host <host>, weles sources [discover <adapter> <file>], weles release <action>, weles design <action> --provider figma, weles version, weles doctor | 9 | Read the weles command line into one command, its action, arguments and required options, and identify the installed release from the package manifest beside its dist. |
recordings.root | the operations that use it | 2 | Find the directory run recordings live in: WELES_RECORDINGS_ROOT names it, and it must be a directory this user can read. |
recordings.write | the operations that use it | 8 | Write one file of a run's recordings: create the run's directory, write the file under a temporary name beside it and rename it into place, so a refused write leaves the old file. |
recordings.scrub | weles recordings scrub | 3 | Rewrite every stored run's session_meta.json so no environment value stays on disk: env_all becomes env_names, the sorted names alone, and every file changed, or left unread or unwritten, is reported. |
pointer.trace | the operations that use it | 2 | Read the pointer trace Weles moves by: the packaged parts src/human/pointer/pointer-trace/, or the directory of parts or JSON file WELES_POINTER_TRACE names. |
pointer.harvest | weles pointer harvest, weles pointer show | 8 | Harvest the pointer travel between presses and the wheel steps from instrumentation dumps of runs a person drove into the trace Weles ships, and show what the trace holds. |
parts.read | the operations that use it | 6 | Read a JSON document kept as a directory of numbered parts, merging them in name order: arrays continue across parts, objects gain each part's keys. |
keeper.socket | the operations that use it | 9 | Reach a keeper by its session: the session names one directory, its socket path fits the system limit, a keeper listens there and answers one JSON line. |
keeper.control | weles keeper start --session <id> [--url <url>] [--headless], weles keeper status --session <id>, weles keeper stop --session <id>, GET /keepers, GET /keepers/<session>, POST /keepers/<session>/stop | 11 | Start one keeper for a session and serve its socket until it is stopped, read whether it runs and what it does, and stop it, closing its browser and finishing its evidence before the stop is confirmed. |
keeper.command | one JSON command line on ~/.weles/keeper/<id>/socket, trajectory keeper/action (SESSION, the command on stdin) | 9 | Run one command in a running keeper through the humanized atoms: navigate, settle, click, fill, type, press, scroll, ask the vision model, evaluate, read cookies or save a screenshot. |
onboarding.journey | weles onboarding status|next|reset, weles onboarding import <trajectory-export.json> --host <host>, weles onboarding verify --receipt <file> --keys <file> | 6 | Walk an operator through the authorization boundary, the import of existing trajectory exports and approved host execution, completed by verifying a real workflow receipt against trusted keys. |
onboarding.steps | weles onboarding status|next|import|verify|reset, runWelesOnboarding({ action }) | 12 | Take one step of Weles's first-use journey: read where it stands, advance it, record an import, verify a receipt or start again. |
onboarding.storage | weles onboarding status|next|import|verify|reset --state-dir <dir> | 7 | Keep the first-use journey's bundle, progress and queued events in its state directory: read every file as JSON, check the journal, and publish each change as the next version. |
onboarding.plane | weles onboarding status|next|import|verify|reset (with WELES_STADO_INTEGRATION_TOKEN set) | 10 | Reach the onboarding control plane Stado names for onboarding-events: read the journey bundle and state, assign an experiment and send each queued event. |
onboarding.runtime | weles onboarding status|next|import|verify|reset | 11 | Check the journey bundle an attempt runs, choose the next screen of its definition, and keep a started attempt's progress and events. |
imports.trajectories | weles import <trajectory-export.json> --host <host>, weles onboarding import <trajectory-export.json> --host <host>, POST /imports | 10 | Import existing trajectory exports as drafts bound to one managed worker: read the export, send it to the Weles web API's /api/v1/imports and read the report of what it imported, left unchanged or refused. |
secrets.alternative | the operations that use it | 16 | Reach the vault through the secrets alternative Stado names for Weles: resolve it, prove it again when it stops answering, and read and write items through Skarbiec's consumer routes as the consumer weles with the bearer of the grant Stado declares for it, or, when Stado hands out the owner-only credential file, through `stado credentials ls|get|put|rm`. |
secrets.scoped | the operations that use it | 12 | Read and write a trajectory's own service credentials as the service catalog declares them: each field from the vault the secrets alternative names, and each write through `stado credentials put --role`. |
skarbiec.runtime | node src/_shared/vaulting/skarbiec-runtime.mjs active-binary|endpoint|capability-socket | 6 | Find the Skarbiec of this host as Stado declares it: the attested program, the route of the secrets alternative Stado names for Weles, and the capability socket of its unit. |
broker.status | node src/worker/weles-api-launcher.mjs | 6 | Ask the Skarbiec capability broker of this machine, on the socket Stado declares for its Skarbiec unit, whether it listens, before the Weles API issues capabilities into its state. |
integrations.action | node src/trajectories/slack/rename_app.mjs, node src/trajectories/slack/post_message.mjs, node src/trajectories/resend/verify_domain_status.mjs, node src/trajectories/meta/ads/ads_performance.mjs | 9 | Act through Wisent Integrations as the alternative Stado names for the action's family: the Slack app's actions, the receiving provider's mail list, the Resend domain checks and the Meta ads reads and uploads. |
skarbiec.vault | the operations that use it | 8 | Read and write the vault through the secrets alternative Stado names: the item inventory, documents, tags, policy, the item playing a role, and the records and settings Weles keeps. |
skarbiec.records | weles records adopt [--json] | 0 | Tag every Weles record in this vault that carries no weles:record:<kind> tag with the kind its own context names, keeping the tags it carries. |
records.subscription | node src/lib/subscribing/service_subscriptions.mjs list|get <id>|upsert '<row-json>'|update <id> '<patch-json>'|delete <id> | 9 | Keep the subscriptions Weles observed, one vault record each, found by service, provider and account: what the collector observed and when, never filled in. |
records.credential | node src/lib/service_credentials.mjs list [term,term]|patch <id> <json>|ensure-kimi-google-sso <id> [source] [email] | 7 | Keep the service credentials Weles signs in with, one vault login item each, listing them without their passwords. |
doctor.check | weles doctor [--json] | 12 | Check this install as a worker will use it: the vault route the secrets alternative names, the recording runtime every browser task needs, the running Weles API against the installed release, and the login inventory. |
capture.params | POST /run generic_capture, POST /run generic_accessibility_audit, trajectory generic/capture and generic/accessibility_audit (their plan in the environment) | 13 | Read the plan of one capture or accessibility audit: the page, the axis, the viewport, the steps, the seconds of video and where its artifacts go. |
release.document | the operations that use it | 3 | Read the JSON documents a release judgement works on, from paths the caller names or the checkout holds, and write back the ones it keeps. |
release.surface | weles release surface | 11 | Read everything a build publishes -- library exports, CLI commands, MCP tools, worker HTTP routes, API schemas, task statuses -- from a checkout's source through the TypeScript program, with the version it declares as released. |
release.enforce | weles release enforce | 9 | Pass a release only when AutoVersion's decision, the released baseline, the version declaration and the package manifest say the same thing. |
release.validate | weles release validate | 5 | Judge a candidate deployment manifest against the weles.deployment.v2 contract, the revision the release is for and the tag cut for it. |
release.adopt | weles release adopt, node scripts/release/version/adopt-published.mjs | 10 | Record a published release as the baseline the version gate judges against: the surface its build carried and the version it was published as, read from the release Stado serves or named by hand. |
trajectories.requirements-publish | node src/trajectories/publish-requirements.mjs | 8 | Publish the trajectory capability declaration to the fleet byte for byte: check it, write it through `stado storage put` and read it back through `stado storage get`. |
trajectories.inputs | the operations that use it | 6 | Read what a run's caller states in the environment: counts, budgets, numbers, text, one of a trajectory's choices and JSON documents, refusing an unset or misshapen one by name before anything is opened. |
trajectories.page | the operations that use it | 11 | The page steps every trajectory shares: write a field and read it back, take a control once, fit a value into its field, find a row or control a model judges, read across a navigation, wait out an operator review, print an address. |
trajectories.overleaf | the operations that use it | 1 | Read Overleaf's dashboard: every project it lists, its load-more control pressed until a settled page shows no more rows. |
trajectories.engagement | the operations that use it | 10 | Act on a platform as one of its accounts, probe an account's health, or read a declared observation, and read the platform's ban detector afterwards. |
trajectories.document | weles documents list, weles documents prune | 9 | Check a document a reviewed trajectory downloaded against its declared media type and keep it, readable by its owner alone, in its own directory; list the copies kept and prune the older ones. |
trajectories.google-sign-in | the operations that use it | 20 | Sign in to Google as a stored login: read the login and its authenticator seed, type the identifier and password, answer the second factor and decline Google's offers after sign-in. |
trajectories.apple-ads | node src/trajectories/apple/ads/run.mjs | 9 | Run one Apple Ads action as the asc ads command the catalogue of actions declares, built from the run's stated inputs and gated when it changes something. |
trajectories.avatar | the operations that use it | 3 | Load the avatar a profile edit uploads: the character's private stado://weles/avatars object, made upright and cut to a square, kept as a file for the browser. |
trajectories.media | a trajectory posting an image (instagram content post) | 2 | Generate the image a post carries through Stado's inference alternatives and keep it in the run's recordings. |
objects.store | the operations that use it | 17 | Write one private Stado object at its canonical stado:// address, with the receipt acknowledging exactly those bytes, and stream it back to compare its size and digest. |
objects.upload | the operations that use it | 4 | Mirror every file under a run's recordings into private Stado objects with its relative path intact, and write the run's upload proof once every object is acknowledged. |
artifacts.locators | normalizeArtifactLocators | 8 | Check a run's artifact locators: one list per declared kind, each locator a private stado://weles/recordings/<run uuid>/<path> URI, before the client asks for them to be signed and before the delivery service signs them. |
artifacts.signed-urls | requestSignedArtifactUrls | 8 | Have the artifact delivery service sign a run's private artifact locators: the locators are checked first, and every signed URL it answers must sign exactly the locator it stands for, at the service's origin, before its expiry. |
artifacts.delivery | loadArtifactDeliveryConfig, createArtifactDeliveryServer, handleArtifactDeliveryRequest | 16 | Serve a run's private artifacts: list the subscriptions Oko's console reads, sign a run's locators for a limited time, and admit an object URL only while its signature and expiry hold. |
artifacts.object-stream | deliverObject | 9 | Stream one private object out of Stado's object API to the holder of its signed URL, passing its range, type and size through and offering HTML only as a download. |
skarbiec.acquisition | node src/worker/deploy/acquire/skarbiec-acquire.mjs <scope-file> <consumer> <item> <field> | 23 | Acquire one field for one consumer as the workload this host runs: a scope the catalog declares, a signed proof, a field-bound bearer and the one-time read it redeems. |
skarbiec.write | node src/worker/deploy/acquire/skarbiec-write.mjs <consumer> <item|role:ROLE> <field> <absolute-token-file> <operation> <operation-id> | 13 | Write one captured credential into Skarbiec as the exact Weles writer consumer: a whole new item for an acquisition, the one field for any other operation. |
admission.service-identity | GET /api/v1/version | 11 | Read which published public service this host is: the placement policy, the service-directory snapshot and the local transport Stado publishes, each checked against this release. |
release.deployment-identity | GET /worker/version, weles worker version | 5 | Read the immutable release identity a worker was deployed with (its version, revision, digests, ring and claims switch) and its instance id, as GET /worker/version answers them. |
executor.submit | the operations that use it | 5 | Start a run one run asks for as a detached POST /run on the Weles API this process belongs to, and read its persisted result back. |
admission.state | the operations that use it | 13 | Keep the public tasks' durable state: task files and Idempotency-Key reservations written atomically and recovered at start, and each task's wire answer and canonical JSON built from them. |
admission.retention | createEvidenceRetention | 21 | Retain a finished public task's browser evidence: hash the run directory, write the manifest, have durable storage acknowledge and read back every object, and sign the receipt over exactly those bytes. |
credentials.operation | POST /api/v1/credential-operations | 53 | Run one credential operation another product asks for: admit its bearer through Skarbiec's introspection, match the request to the acquisition Weles declares (or, for an authenticator enrolment, start the run the owner's order allows, pass it the owner's answers and say where it stands), run it once, and answer its status from the worker's record. |
secrets.binding | checkedTokenFile, checkedTenantDirectory, checkedTenantFile | 4 | Read what Weles reaches Skarbiec with: the owner-only token files Stado issues and a tenant's binding directory and files. |
worker.routes | statusOf, captchaAnswersOf | 2 | Build the worker's routes: the HTTP statuses from Node's table and the captcha answers directory of each run. |
credentials.capture | store_credential, wsStoreCredential, wsAutoStoreCredential | 17 | Capture the credential a provider page shows, on the authorized origin and of the contract's shape, and store it in the one Skarbiec item its acquisition names. |
browser.controls | resolveObservedControl, clickObservedControl, clickSelectorControl | 7 | Bind the control a step names to exactly one element: an observed description to the element that still has it, a selector to the one visible element it matches. |
declarations.load | weles API launcher: load engagement declaration, weles API launcher: load observation declaration, weles API launcher: load document declaration | 10 | Read and check the whole engagement, observation or document declaration when the API launcher starts. |
declarations.admit | POST /run generic_saved_task, POST /run generic_keeper_task, POST /run generic_document_task | 6 | Admit a generic run by the declared engagement, observation or document it names, before anything is spawned. |
trajectories.run | node src/trajectories/<provider>/<action>.mjs, POST /run | 0 | Run one trajectory as its own process; a declared refusal it raises ends the process with the cause's sentence, fix and exit status (src/trajectories/_shared/running/failure-report.mjs). |
trajectories.ncbr-audit | node src/trajectories/ncbr/pangram_audit_new_wniosek.mjs | 3 | Extract the written answers of the selected sections of an NCBR application draft and read Pangram's analysis of each, never writing to LSI2. |
secrets.acquired-write | every credential a run acquires, resets or adopts (writeWelesAcquiredSecret) | 14 | Write one credential a run acquired, reset or adopted into the one vault item its acquisition contract names, after checking the contract, the value, the request and the origin. |
secrets.acquired-read | every read of a scoped role field or a workload-acquired field (readScopedField, readAcquiredField) | 13 | Read one scoped credential field: a role's field with the consumer's own grant through Stado, or a field the run's workload identity acquires from Skarbiec through the acquisition helper. |
secrets.acquisition-job | POST /api/v1/credential-operations (acquireSecret, buildSecretAcquisitionPlan) | 4 | Build the job a worker runs to acquire, rotate or reset one credential: its objective, its identity and the constraints naming the vault item. |
secrets.operation-answer | a credential operation run stating what it did (writeCredentialOperation), the worker answering the operation from it | 2 | Read what a credential operation's run states it did to the credential, from the one file and member it writes. |
secrets.semantic-scholar-followup | the semanticscholar_key_followup action (trajectories/semanticscholar/key_followup.mjs) | 3 | Read the mailbox of a Semantic Scholar key request again until the key mail arrives, and store the first key Semantic Scholar accepts. |
design.export-request | weles design export --provider figma --request <export-request.json> | 11 | Read a design export request and the Figma inventory it names, before Git, the vault or Figma is asked. |
design.export-checkout | the operations that use it | 19 | Check the checkout a design export publishes into: the root of a repository on the named branch, one push URL, a committer, nothing uncommitted, and the remote branch at the same commit. |
design.export-publish | weles design export --provider figma --request <export-request.json> | 13 | Hold the checkout for one design export, swap its generated tree into the managed directory, commit only that directory, push it and read the remote branch back. |
design.parse | weles design parse --provider figma <document.json[.gz]> <summary.json> <nodes.json> [<vocabulary.json>] | 5 | Read a Figma document from its bytes and write its summary, node index and design vocabulary. |
design.figma-export | weles design export --provider figma --request <export-request.json> | 13 | Ask Figma for each inventory file's document, source images and previews with the acquired token, and write them into the export's working tree. |
trajectories.linkedin-proxy | the operations that use it | 15 | Hold a LinkedIn registration to one dedicated ISP exit: asked for by provider pool, resolved to a live ISP record, and seen from the same address at every stage. |
trajectories.linkedin-submit | the operations that use it | 7 | Submit one LinkedIn signup form step: click its enabled button, await the POST it sends and read its answer, keeping the request, answer and page beside a failure. |
trajectories.linkedin-register | the linkedin_register action (trajectories/linkedin/register.mjs) | 22 | Register a new LinkedIn account on a dedicated ISP exit: check the browser fingerprint, reach the signup form, submit it, confirm the mailed code and save the signed-in account, ending at any challenge. |
accounts#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
subscription-not-found | selecting the {provider} subscription Weles acts on | the vault holds no brama:subscription item of this provider carrying that brama:id: tag: it was never recorded, was retired, or the id is mistyped | no brama:subscription item of {provider} carries id {subscription_id}; the vault lists {provider_count} of {provider}: {provider_ids} | run `weles accounts list` for the subscriptions and logins the vault resolves and name one of them with --subscription | not_found, exit 1, HTTP 404 | tests/accounts/logins/login-account-failures.mjs::a_subscription_the_vault_does_not_hold_is_named |
subscription-ambiguous | selecting the {provider} subscription Weles acts on | more than one brama:subscription item of this provider carries that id, or no id was named and the provider has several, so Weles cannot tell which account to act on | {count} brama:subscription items of {provider} match id {subscription_id}: {subscription_items} | name exactly one subscription with --subscription <id>; when two items carry one brama:id: tag, retag the stale one with `stado credentials item retag` | config, exit 1, HTTP 503 | tests/accounts/logins/login-account-failures.mjs::two_subscriptions_with_one_id_are_refused_naming_both |
subscription-identity-missing | resolving the login of subscription {subscription_item} | the subscription records no account (no context.account_ref, brama:account: or brama:login: tag) and no single login of its provider identifies it | subscription {subscription_item} records no context.account_ref, brama:account: or brama:login: tag; logins of {provider}: {provider_login_items}; every login: {login_items}; {way_out} | record the account on the subscription item as the observed sentence says (`brama subscription attribute`, or a brama:login:<login item> tag), then run the command again | config, exit 1, HTTP 503 | tests/accounts/logins/login-account-failures.mjs::a_subscription_naming_no_account_is_refused |
login-missing | resolving the login of subscription {subscription_item} | no login item in the vault carries the account the subscription identifies, so there is nothing to sign it in with | subscription {subscription_item} identifies {account_ref}; none of the {logins_read} logins read carries that account | store the account's login in the vault as a login item whose username is {account_ref}, then run the command again | config, exit 1, HTTP 503 | tests/accounts/logins/login-account-failures.mjs::a_subscription_whose_account_has_no_login_is_refused |
login-ambiguous | resolving the login of subscription {subscription_item} | several login items carry the account and they hold different passwords or authenticators, so Weles cannot tell which one signs it in | subscription {subscription_item} identifies {account_ref}; logins: {candidates}, holding {credential_count} different passwords and {authenticator_count} authenticators | keep one login for {account_ref} and retire the others, or tag the subscription brama:login:<login item> to name the one that signs in | config, exit 1, HTTP 503 | tests/accounts/logins/login-account-failures.mjs::two_logins_of_one_account_that_disagree_are_refused |
login-method-unsupported | resolving the login of subscription {subscription_item} | the login declares no login method, or one Weles does not sign in with (google_sso, email_password, email_code) | login {login_item} declares login_method {login_method}; declare it with `{declaration}` | declare how the login signs in, in its context's login_method or a weles:login-method:<method> tag, as the observed sentence names | config, exit 1, HTTP 503 | tests/accounts/logins/login-account-failures.mjs::a_login_without_a_login_method_is_refused |
subscription-provider-unsupported | resolving the login of subscription {subscription_item} | the subscription's brama:provider: tag names a provider Weles signs no subscription in for (claude, codex, kimi) although a sign-in route of the worker took it | subscription {subscription_item} carries brama:provider:{provider}; Weles signs in {supported} | correct the subscription item's brama:provider: tag to the provider it belongs to, then ask for the sign-in again | config, exit 1, HTTP 503 | tests/accounts/logins/login-account-failures.mjs::a_subscription_of_a_provider_without_a_sign_in_is_refused |
login-record-conflict | reading what vault item {item} declares about its sign-in | the item declares {field} differently in two of the places it may be written (its context, a tag, its metadata, its username), and Weles does not let the first one that is not empty decide | item {item} declares {field} as {said} | keep one value of {field} on item {item}: correct or remove the stale place the observed sentence names | config, exit 1, HTTP 503 | tests/accounts/logins/login-account-failures.mjs::a_login_whose_method_disagrees_with_its_tag_is_refused |
login-record-unreadable | reading what vault item {item} declares about its sign-in | the item holds {field} that is not JSON where Weles reads the login's metadata from it | {field} of item {item} holds {length} characters that do not parse as JSON ({error}); the value is not shown, it may hold a grant | rewrite {field} of item {item} as one JSON document, or move the secret it holds into the field of its own name (password, totp_secret) | config, exit 1, HTTP 503 | tests/accounts/logins/login-account-failures.mjs::a_login_whose_value_is_not_json_is_refused |
subscription-source-cycle | following the source items of subscription {subscription_item} | the items its context.source_item names lead back to an item already read, so no login is ever reached | a cyclic source reference: {path} leads back to {source_item} | point context.source_item of {source_item} at the item that holds the login, or record the account on the subscription itself | config, exit 1, HTTP 503 | tests/accounts/logins/login-account-failures.mjs::a_subscription_whose_sources_loop_is_refused |
subscription-source-missing | following the source items of subscription {subscription_item} | its context.source_item names an item the vault does not hold: it was retired or renamed | the sources {path} end at {source_item}, which the vault lists under no id or item_uid | point context.source_item at an item the vault holds, or record the account on the subscription itself | config, exit 1, HTTP 503 | tests/accounts/logins/login-account-failures.mjs::a_subscription_naming_a_missing_source_is_refused |
login-item-not-found | reading how login {login_item} signs in | the vault holds no login item with that id or item_uid | the vault lists {login_count} login items; none has id or item_uid {login_item} | run `weles accounts list` for the subscriptions and logins the vault resolves, and name a login item it shows | not_found, exit 1, HTTP 404 | tests/accounts/logins/login-account-failures.mjs::an_authenticator_enrolment_for_a_login_the_vault_lacks_is_refused |
login-fields-missing | reading the sign-in material of login {login_item} | the login item holds no fields at all, so it has no username or password to sign in with | login {login_item} of subscription {subscription_id} is a document of {members} with no fields | store the username and password on login {login_item}, then ask for the sign-in again | config, exit 1, HTTP 503 | tests/accounts/logins/login-account-failures.mjs::a_sign_in_whose_login_lost_its_fields_is_refused |
login-identity-changed | reading the sign-in material of login {login_item} | the login's username no longer names the account resolved when the sign-in was admitted: it was rewritten while the run started | login {login_item} now names {now}; the sign-in was admitted for {account_ref} | ask for the sign-in again, so the account is resolved from the login as it stands now | config, exit 1, HTTP 503 | tests/accounts/logins/login-account-failures.mjs::a_sign_in_whose_login_changed_account_is_refused |
login-password-missing | reading the sign-in material of login {login_item} | the login declares a method that signs in with a password, and holds none | login {login_item} signs in by {login_method}; its password field is {state} | store the password on login {login_item}, or declare the method it really signs in with | config, exit 1, HTTP 503 | tests/accounts/logins/login-account-failures.mjs::a_password_sign_in_whose_login_holds_no_password_is_refused |
subscription-changed | keeping the grant a sign-in earned on subscription {subscription_item} | the subscription or one of its login items changed in the vault while the sign-in ran, so the grant may belong to another account and is not written | the account revision was {admitted} at admission and is {current} now; the existing grant was not overwritten | run `weles accounts refresh --provider <provider> --subscription {subscription_id}` again; it resolves the account as it stands now | config, exit 1, HTTP 503 | tests/accounts/logins/login-account-failures.mjs::a_grant_for_a_subscription_that_changed_meanwhile_is_not_written |
grant-unconfirmed | keeping the grant a sign-in earned on subscription {subscription_item} | the vault accepted the write and then returned another value: another writer replaced it, or the vault lost the write | {written} characters were written to fields.value of {subscription_item}, and the vault returned {found} | run `weles accounts refresh --provider <provider> --subscription {subscription_id}` again once the vault answers | infra_down, exit 69, HTTP 503, retryable | tests/accounts/logins/login-account-failures.mjs::a_grant_the_vault_does_not_return_is_named |
refresh-unfinished | refreshing the sign-in of {provider} subscription {subscription} | the executor admitted the sign-in and its answer ended before the run's result line: the Weles API restarted or the connection broke while the run went on | the answer of {endpoint} ended after {events} events; the last: {last} | run `weles runs list` to see whether the sign-in still runs; run the same `weles accounts refresh` again, which joins a run still under way | infra_down, exit 69, HTTP 503, retryable | tests/accounts/sign-in-failures.mjs::a_refresh_whose_answer_ends_before_its_result_is_named |
refresh-failed | refreshing the sign-in of {provider} subscription {subscription} | the sign-in ran on the executor and ended without a new grant: the provider refused the login, asked for a step nobody answered, or the run failed | run {run}: {error}: {message} | run `weles runs show {run}` for what the run saw, answer what it asks for, then run `weles accounts refresh --provider {provider} --subscription {subscription}` again | refused, exit 1, HTTP 403 | tests/accounts/sign-in-failures.mjs::a_refresh_the_provider_refuses_names_its_run |
refresh-unavailable | refreshing the sign-in of {provider} subscription {subscription} | the sign-in did not get to sign the account in, for a cause the run states asking again can change: its browser host, its browser or the provider's OAuth endpoint was not reached | run {run}: {error}: {message} | run `weles runs show {run}` for the step it stopped at; run the same `weles accounts refresh` again once that host or endpoint answers | infra_down, exit 69, HTTP 503, retryable | tests/accounts/sign-in-failures.mjs::a_refresh_that_did_not_reach_the_provider_is_retryable |
sign-out-unconfirmed | signing {provider} subscription {subscription} out | the vault accepted the item without its grant and then returned the item with a grant again: another writer (a sign-in that just finished) wrote one meanwhile | item {item} still holds a grant after the write | run `weles runs list` for a sign-in of this account that is under way, cancel it with `weles runs cancel`, then run `weles accounts logout` again | infra_down, exit 69, HTTP 503, retryable | tests/accounts/sign-in-failures.mjs::a_sign_out_a_sign_in_overwrites_is_named |
session-action-unset | restoring the session of the {platform} account {account} for {script} | ACTION is not set, and the account session reads the run's network requirements by the action it performs | ACTION {value}; script {script} | run it through the Weles worker, which sets ACTION for every run it starts, or set ACTION to the action it performs | config, exit 1, HTTP 503 | tests/accounts/logins/session-failures.mjs::a_session_whose_run_names_no_action_is_refused |
session-proxy-type-unstated | resolving the proxy {action} of the {platform} account {account} browses through | the run needs a proxy ({reason}) and its network names no proxy type to resolve, and the account has no pinned exit | proxy type: none; country {country}; PROXY_URL unset; WELES_ALLOW_DIRECT_ACCOUNT_SESSION unset | name the proxy type of the run (its network requirements), set PROXY_URL to an exit, or pin one to the account | config, exit 1, HTTP 503 | tests/accounts/logins/session-failures.mjs::a_session_whose_network_names_no_proxy_type_is_refused |
session-proxy-unavailable | resolving the proxy {action} of the {platform} account {account} browses through | no provider able to serve {action} resolved a {type} exit in {country}, so the browser is not launched without one | providers tried: {tried}; {why} | run again once a provider resolves a {type} exit in {country} (the observed names the providers whose standing is failing or unpriced), or pin an exit to the account | infra_down, exit 69, HTTP 503, retryable | tests/accounts/logins/session-failures.mjs::a_session_no_provider_resolves_is_refused |
session-exit-unparsable | resolving the proxy {action} of the {platform} account {account} browses through | the {provider} exit resolved for the account has a server address that is no URL, so no host, port or protocol can be pinned from it | server of {length} characters; the parser said: {problem}; nothing was pinned | correct the {provider} source's server form in src/proxy/sources/, then run the session again | unknown, exit 1, HTTP 500 | tests/accounts/logins/session-failures.mjs::a_session_whose_resolved_exit_is_no_url_is_refused |
pinned-address-unparsable | reading the proxy pinned to the {platform} account {account} for {action} | the pin's {field} is no URL, so no host, port or protocol can be read from it | {field} of {length} characters; the parser said: {problem}; the value is not shown, it may carry the proxy's password | write the pin's {field} as <protocol>://<host>:<port> in metadata_json.proxy of the account's Skarbiec record, or retire the account | config, exit 1, HTTP 503 | tests/accounts/pinned-failures.mjs::a_pin_whose_address_is_no_url_is_refused |
pinned-protocol-unstated | reading the proxy pinned to the {platform} account {account} for {action} | the pin {host}:{port} names no protocol, so the proxy cannot be asked in the scheme it serves | pin members: {members} | state protocol (http or socks5) in metadata_json.proxy of the account's Skarbiec record, or retire the account | config, exit 1, HTTP 503 | tests/accounts/pinned-failures.mjs::a_pin_without_a_protocol_is_refused |
pinned-burned | reading the proxy pinned to the {platform} account {account} for {action} | the pinned host {host} is in the burned_proxies setting for {platform}: the platform banned it, so the account cannot browse from it and is not moved to another exit | burned_proxies holds {host} for {platform}; port {port} | retire the account, or remove {host} from the burned_proxies Weles setting in Skarbiec once the platform lifted the ban | refused, exit 1, HTTP 403 | tests/accounts/pinned-failures.mjs::a_pin_on_a_burned_host_is_refused |
pinned-pool-retired | reading the proxy pinned to the {platform} account {account} for {action} | the pinned exit {host}:{port} belongs to a retired pool ({retired}); the account was marked inactive with retired_reason {reason} | retired_proxy_pools names it: {retired} | register a new account for {platform}: an account keeps its one exit, and this one is retired | refused, exit 1, HTTP 403 | tests/accounts/pinned-failures.mjs::a_pin_in_a_retired_pool_retires_the_account |
pinned-provider-unknown | reading the proxy pinned to the {platform} account {account} for {action} | the pin {host}:{port} names no provider and its host is no provider's, so it cannot be rebuilt; the account was marked inactive with retired_reason {reason} | provider: none; host {host}, which src/proxy/policy.ts providerFromHost gives no provider | register a new account for {platform}, or state the provider in the retired account's pin and mark it active again | refused, exit 1, HTTP 403 | tests/accounts/pinned-failures.mjs::a_pin_of_no_provider_retires_the_account |
pinned-capability-failing | reading the proxy pinned to the {platform} account {account} for {action} | the capability matrix marks {provider} failing for {action}, so the account's pinned exit is not used for it | proxy_capability_matrix: {provider} x {action} = fail; pin {host}:{port} | run the capability probe for {provider} again once the provider serves {action}; the matrix decides from its result | refused, exit 1, HTTP 403 | tests/accounts/pinned-failures.mjs::a_pin_whose_provider_fails_the_action_is_refused |
pinned-record-write-refused | writing {change} to the Skarbiec account record {id} of {account} | the write failed with a {kind} that no layer of the vault declares: every refusal of Skarbiec and of the secrets alternative is declared, so this one is a defect of this release, and the account record still holds what it held before | {kind} at the write of {id}: {problem}; nothing was written | run the session again; when it repeats, report the error above with `weles version` (a release whose vault layer declares it will name the cause) | unknown, exit 1, HTTP 500 | tests/accounts/pinned-failures.mjs::a_record_write_skarbiec_refuses_is_named |
pinned-record-absent | writing {change} to the Skarbiec account record {id} of {account} | Skarbiec holds no live account record {id}: it was deleted or retired after the session read it | no item tagged weles:record:account has id {id} | read the account again (`weles records adopt` tags records Skarbiec holds untagged) and start the session from the record Skarbiec holds | not_found, exit 1, HTTP 404 | tests/accounts/pinned-failures.mjs::a_record_skarbiec_no_longer_holds_is_named |
social-bindings-conflicting | choosing the {platform} account this run signs in with | WELES_LOGIN_ITEM, ACCOUNT_ITEM and ACCOUNT_ID name {count} different accounts, and a run signs in with one | WELES_LOGIN_ITEM {login}; ACCOUNT_ITEM {item}; ACCOUNT_ID {id}; no account record was read | start the run bound to one account: set only one of WELES_LOGIN_ITEM, ACCOUNT_ITEM and ACCOUNT_ID, or the same item in each (the Weles worker sets them from the run request) | config, exit 1, HTTP 503 | tests/accounts/logins/social-failures.mjs::a_run_bound_to_two_accounts_is_refused |
stale-account-absent | marking the cookies of account {account} stale and starting its re-login | the vault holds no active account record {account}: it was retired or deleted after the run read it, or the run was bound to a mistyped id | the account record {account} {state}; nothing was marked and no login run was submitted | bind the next run to an active account record (`stado capabilities resolve secrets --consumer weles` names the vault the account records are read from), or record the account again | not_found, exit 1, HTTP 404 | tests/accounts/logins/social-failures.mjs::a_stale_mark_on_an_absent_account_is_refused |
acquired-item-exists | recording the {provider} account {email} bought by acquisition {request} | the vault already holds item {item}, so recording this account would overwrite another record of the same address | items already held: {held}; subscription id {subscription}; nothing was written | `weles accounts list --provider {provider}` shows the subscription already recorded for {email}; sign that one in instead of buying the address again, or retire the old items first | refused, exit 1, HTTP 403 | tests/accounts/acquired-failures.mjs::an_account_whose_items_exist_is_not_overwritten |
acquired-item-unconfirmed | recording the {provider} account {email} bought by acquisition {request} | the vault took the write of {item} and returned it without its acquired_at: another writer replaced it, or the vault lost the write | item {item} read back: {found}; the login row {login} and the subscription item {subscription_item} were both written | `stado credentials get {item}` (on the vault Stado names) shows the item as the vault holds it; write the acquisition record again once the vault keeps its writes | infra_down, exit 69, HTTP 503, retryable | tests/accounts/acquired-failures.mjs::an_account_the_vault_does_not_return_is_named |
mail#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
mail-alternative-unanswered | resolving the mail family for weles, to {purpose} | Stado names no mail alternative that answers: {why} | `{command}` exited {status}; Stado cause {stado_cause}: {answer}; Stado fix: {stado_fix} | install the skrzynka program where Stado looks for it (its PATH, or ~/.local/bin of the worker's HOME), or repair what Stado names; `stado capabilities resolve mail --consumer weles --json` shows the alternative Stado names | infra_down, exit 69, HTTP 503, retryable | tests/mail/alternative-failures.mjs::a_mail_family_no_program_answers_is_named |
mail-alternative-other | resolving the mail family for weles, to {purpose} | the alternative Stado named is not the Skrzynka program Weles runs: it expected provider skrzynka of kind program at an absolute path | provider {provider}, kind {kind}, route {route} | put skrzynka first in the mail order (`stado capabilities select mail --order skrzynka`); `stado capabilities resolve mail --consumer weles --json` shows the alternative Stado names | config, exit 1, HTTP 503 | tests/mail/alternative-failures.mjs::a_mail_alternative_that_is_no_program_is_named |
skrzynka-unrun | running Skrzynka to {purpose} | the Skrzynka program Stado named could not be started ({errno}): it was removed or lost its execute permission after Stado proved it | `{command}` in {directory}: {problem} | reinstall skrzynka at {program}, or let Stado prove another; `stado capabilities resolve mail --consumer weles --json` shows the alternative Stado names | infra_down, exit 69, HTTP 503, retryable | tests/mail/inbox-failures.mjs::an_inbox_program_that_cannot_start_is_named |
skrzynka-unavailable | running Skrzynka to {purpose} | Skrzynka could not reach what it reads the mail from (it calls its refusal {skrzynka_code} and says it may pass) | `{command}` in {directory} exited {status}; Skrzynka said: {said} | `skrzynka --organization {organization} status` names the database and what Skrzynka cannot reach; run again once it answers | infra_down, exit 69, HTTP 503, retryable | tests/mail/inbox-failures.mjs::an_inbox_skrzynka_cannot_read_is_named |
skrzynka-refused | running Skrzynka to {purpose} | Skrzynka refused the command ({skrzynka_code}) and says retrying will not change it | `{command}` in {directory} exited {status}; Skrzynka said: {said} | repair what Skrzynka names (`skrzynka {verb} --help` shows what the command takes), then run again | unknown, exit 1, HTTP 500 | tests/mail/inbox-failures.mjs::an_inbox_listing_skrzynka_refuses_is_named |
skrzynka-not-json | running Skrzynka to {purpose} | Skrzynka ended successfully and printed no JSON: the program Stado named is another release, or one started with --text | `{command}`: {bytes} bytes; the parser said: {problem} | `{program} --version` names the release; install a skrzynka whose answers are JSON; `stado capabilities resolve mail --consumer weles --json` shows the alternative Stado names | unknown, exit 1, HTTP 500 | tests/mail/inbox-failures.mjs::an_inbox_answer_that_is_not_json_is_named |
inbox-organization-unset | reading the mail Skrzynka holds for {recipient} from {sender} | WELES_SKRZYNKA_ORGANIZATION names no Skrzynka organization, so the mail to {recipient} cannot be read: Skrzynka files every mailbox under one | WELES_SKRZYNKA_ORGANIZATION {value}; Stado was not asked and Skrzynka not run | set WELES_SKRZYNKA_ORGANIZATION in the Weles service's environment (`stado service env set weles`) to the organization the mailbox of {recipient} is declared in | config, exit 1, HTTP 503 | tests/mail/inbox-failures.mjs::an_inbox_without_its_organization_is_refused |
inbox-list-shape | reading the mail Skrzynka holds for {recipient} from {sender} | Skrzynka printed JSON that is no list of messages with id, sender, recipients, subject, received_at and body_text: another release of Skrzynka answered | `{command}`: {found} | `{program} --version` names the release; install the skrzynka whose message list Weles reads | unknown, exit 1, HTTP 500 | tests/mail/inbox-failures.mjs::an_inbox_answer_of_another_shape_is_named |
domain-record-shape | reading the inbound mail domains recorded in the Weles setting inbound_email_domains | the setting holds {type}, not the list of domain rows Weles keeps there, so no domain is picked from it and it is not overwritten | {type} of {length} characters, beginning {head} | move the setting inbound_email_domains aside in the vault (it holds something else) so Weles records the domains again, or store a list of {domain, status, signup_count} rows | config, exit 1, HTTP 503 | tests/mail/domain-failures.mjs::a_domain_record_that_is_no_list_is_refused |
domain-unstated | choosing the inbound mail domain of a new {platform} account | no inbound domain is recorded and none of FORCE_EMAIL_DOMAIN, AGENT_EMAIL_DOMAINS or AGENT_DOMAIN is set; no domain is assumed | inbound_email_domains holds {rows} rows; FORCE_EMAIL_DOMAIN {forced}; AGENT_EMAIL_DOMAINS {listed}; AGENT_DOMAIN {single} | set AGENT_DOMAIN (or AGENT_EMAIL_DOMAINS, comma-separated) to a domain whose mail Skrzynka receives, in the Weles service environment (`stado service env set weles`) | config, exit 1, HTTP 503 | tests/mail/domain-failures.mjs::a_pick_with_no_domain_stated_is_refused |
domain-unavailable | choosing the inbound mail domain of a new {platform} account | every inbound domain recorded is broken or blocked for {platform}: no active domain has a mail exchanger and no block by {platform} | {domains}; the rows were written back with the status found | record a new inbound domain whose MX record points at the receiving provider, or repair the MX of one listed (`dig MX <domain>` shows it); a domain {platform} blocked stays blocked for it | refused, exit 1, HTTP 403 | tests/mail/domain-failures.mjs::a_pick_when_every_domain_is_blocked_is_refused |
proxy#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
pin-provider-unstated | rebuilding the proxy {host}:{port} pinned to the {platform} account {account} for {action} | the pin names no provider and its host is no provider Weles knows, so no provider secret can rebuild its credentials | provider: none; host {host}, which src/proxy/policy.ts providerFromHost gives no provider | state provider in the account's pin (metadata_json.proxy of its Skarbiec account record), or retire the account | config, exit 1, HTTP 503 | tests/proxy/pin-failures.mjs::a_pin_naming_no_provider_is_refused |
pin-type-unstated | rebuilding the proxy {host}:{port} pinned to the {platform} account {account} for {action} | the pin states {kind} as its pool type, which is none of isp, mobile or residential, so its credentials cannot be chosen | proxy_type {kind}; provider {provider} | state proxy_type (isp, mobile or residential) in the account's pin (metadata_json.proxy of its Skarbiec account record), or retire the account | config, exit 1, HTTP 503 | tests/proxy/pin-failures.mjs::a_pin_naming_no_pool_type_is_refused |
pin-secret-undeclared | rebuilding the proxy {host}:{port} pinned to the {platform} account {account} for {action} | Weles declares no scoped secret holding the credentials of {provider} {type} proxies, and the pin carries none of its own | provider {provider}, pool {type}; credential_ref {reference} | pin the account to a provider Weles holds credentials for (src/proxy/sources/provider_credentials.ts secretServiceFor), or give the pin a credential_ref of its own | config, exit 1, HTTP 503 | tests/proxy/pin-failures.mjs::a_pin_whose_provider_has_no_secret_is_refused |
pin-secret-unread | rebuilding the proxy {host}:{port} pinned to the {platform} account {account} for {action} | the scoped secret {secret} holding {provider}'s credentials gave no {missing}, so the proxy cannot be authenticated | secret {secret}: username {username}, password {password}; the values are not shown | store username and password on the item the scoped secret {secret} names in Skarbiec, then start the session again | config, exit 1, HTTP 503 | tests/proxy/pin-failures.mjs::a_pin_whose_secret_lacks_a_field_is_refused |
pin-sticky-session-unstated | rebuilding the proxy {host}:{port} pinned to the {platform} account {account} for {action} | the pin is a {type} exit and states no sticky_session_id, so the session it was pinned in cannot be asked for again | proxy_type {type}; sticky_session_id absent; country {country} | state sticky_session_id in the account's pin (metadata_json.proxy of its Skarbiec account record), or retire the account | config, exit 1, HTTP 503 | tests/proxy/pin-failures.mjs::a_sticky_pin_without_its_session_is_refused |
pin-country-unstated | rebuilding the proxy {host}:{port} pinned to the {platform} account {account} for {action} | the pin is a {type} exit and states no country, so the sticky session would be asked for in no country it was pinned in | proxy_type {type}; sticky_session_id present; country absent | state country in the account's pin (metadata_json.proxy of its Skarbiec account record), or retire the account | config, exit 1, HTTP 503 | tests/proxy/pin-failures.mjs::a_sticky_pin_without_its_country_is_refused |
pin-address-absent | opening a CONNECT tunnel to {target}:{targetPort} through the proxy {host}:{port} pinned to the {platform} account {account} | the rebuilt pin names no proxy host and port to ask; the account keeps its one exit, so the session does not start on another | host {host}, port {port} | state host and port in the account's pin (metadata_json.proxy of its Skarbiec account record), or retire the account | config, exit 1, HTTP 503 | tests/proxy/pin-failures.mjs::a_pin_without_an_address_is_refused |
pin-runtime-port-unknown | opening a CONNECT tunnel to {target}:{targetPort} through the proxy {host}:{port} pinned to the {platform} account {account} | this Node's HTTPS agent names no default port, so the tunnel has no port to ask the proxy for | node {node}; https.globalAgent.defaultPort is {kind} | run Weles on the Node release its package.json engines name (`node --version` shows this one) | config, exit 1, HTTP 503 | tests/proxy/pin-failures.mjs::a_node_without_an_https_port_is_named |
pin-tunnel-unauthenticated | opening a CONNECT tunnel to {target}:{targetPort} through the proxy {host}:{port} pinned to the {platform} account {account} | the proxy refused the credentials of the pin (HTTP {status}): the provider's password changed, or the account's sticky session ended; the account keeps its one exit, so the session does not start on another | CONNECT answered {status} {message}; credentials {sent} | store the provider's current username and password on the item of its scoped secret in Skarbiec, or retire the account when its session ended | auth, exit 1, HTTP 401 | tests/proxy/pin-failures.mjs::a_pin_whose_credentials_the_proxy_refuses_is_named |
pin-tunnel-refused | opening a CONNECT tunnel to {target}:{targetPort} through the proxy {host}:{port} pinned to the {platform} account {account} | the proxy answered the CONNECT with HTTP {status} instead of 200: it will not reach the platform from this exit; the account keeps its one exit, so the session does not start on another | CONNECT answered {status} {message} | read the provider's dashboard for why the exit refuses the platform; retire the account when the exit is gone | refused, exit 1, HTTP 403 | tests/proxy/pin-failures.mjs::a_pin_whose_proxy_refuses_the_tunnel_is_named |
pin-tunnel-unresolved | opening a CONNECT tunnel to {target}:{targetPort} through the proxy {host}:{port} pinned to the {platform} account {account} | the proxy's host name did not resolve ({errno}): the provider retired the name, or this host's resolver is down; the account keeps its one exit, so the session does not start on another | {errno}: {problem} | `dig {host}` shows whether the name resolves; run again once it does, or retire the account when the provider retired it | infra_down, exit 69, HTTP 503, retryable | tests/proxy/pin-failures.mjs::a_pin_whose_proxy_name_does_not_resolve_is_named |
pin-tunnel-connection-refused | opening a CONNECT tunnel to {target}:{targetPort} through the proxy {host}:{port} pinned to the {platform} account {account} | nothing accepts connections at the proxy's address (ECONNREFUSED): the provider moved the exit or it is down; the account keeps its one exit, so the session does not start on another | {errno}: {problem} | `nc -vz {host} {port}` shows whether the exit listens; run again once it does, or retire the account when it is gone | infra_down, exit 69, HTTP 503, retryable | tests/proxy/pin-failures.mjs::a_pin_whose_proxy_refuses_connections_is_named |
pin-tunnel-broken | opening a CONNECT tunnel to {target}:{targetPort} through the proxy {host}:{port} pinned to the {platform} account {account} | the connection to the proxy broke before it answered the CONNECT ({errno}): the network between, or the exit, dropped it; the account keeps its one exit, so the session does not start on another | {errno}: {problem} | run again; when it repeats, `nc -vz {host} {port}` shows whether the exit answers from this host | infra_down, exit 69, HTTP 503, retryable | tests/proxy/pin-failures.mjs::a_pin_whose_connection_breaks_is_named |
exit-geography-unreached | reading where {subject} is from {service} | the geography lookup did not answer, so the exit has no country a persona could follow | {problem} | run again once {service} answers from this host (`curl -s {service}` from this host shows what it answers) | infra_down, exit 69, HTTP 503, retryable | tests/proxy/exits/exit-geography-failures.mjs::a_lookup_that_does_not_answer_is_named |
exit-geography-unreadable | reading where {subject} is from {service} | the lookup answered HTTP {status} with a body that is not JSON | {bytes} characters, beginning {head} | run again once {service} answers its JSON document (`curl -s {service}` from this host shows what it answers) | unknown, exit 1, HTTP 500 | tests/proxy/exits/exit-geography-failures.mjs::a_lookup_answer_that_is_not_json_is_named |
exit-geography-refused | reading where {subject} is from {service} | the lookup answered HTTP {status} with status {answer}, so it placed the address nowhere | its message: {message} | for a proxy exit, resolve another exit; for a session without a proxy, run it on a host whose public address the lookup places, or start it with a proxy whose exit it places | unknown, exit 1, HTTP 500 | tests/proxy/exits/exit-geography-failures.mjs::an_address_the_lookup_cannot_place_is_named |
exit-geography-unplaced | reading where {subject} is from {service} | the lookup answered success but named no country code | the fields it answered: {fields} | run again once {service} names the country of the address (`curl -s {service}` from this host shows what it answers) | unknown, exit 1, HTTP 500 | tests/proxy/exits/exit-geography-failures.mjs::a_lookup_answer_without_a_country_is_named |
public-address-service-unset | reading which service tells this host its public address | WELES_PUBLIC_ADDRESS_URL is {state}, and no address service is assumed | WELES_PUBLIC_ADDRESS_URL {state} | set WELES_PUBLIC_ADDRESS_URL to the https:// address of a service that answers the caller's address as plain text, with no user, password or fragment | config, exit 1, HTTP 503 | tests/proxy/exits/public-address-failures.mjs::an_unset_address_service_is_refused |
public-address-service-invalid | reading which service tells this host its public address | WELES_PUBLIC_ADDRESS_URL {problem} | WELES_PUBLIC_ADDRESS_URL of {length} characters; scheme {scheme}; the value is not shown, it may carry a password | set WELES_PUBLIC_ADDRESS_URL to the https:// address of a service that answers the caller's address as plain text, with no user, password or fragment | config, exit 1, HTTP 503 | tests/proxy/exits/public-address-failures.mjs::an_address_service_that_is_no_https_url_is_refused |
public-address-answer-not-ip | reading the public address {service} answered | the answer is not one IP address as plain text: the service answers a page, or another format | {bytes} characters, beginning {head} | point WELES_PUBLIC_ADDRESS_URL at a service that answers only the caller's address (`curl -s {service}` shows what it answers) | unknown, exit 1, HTTP 500 | tests/proxy/exits/public-address-failures.mjs::an_address_answer_that_is_no_ip_is_named |
registry-unreached | reading the IANA {family} special-purpose registry {address} | the registry could not be fetched: the name did not resolve, the connection was refused or broke, or a proxy refused it | {answer} | check that this host reaches https://www.iana.org (`curl -sI {address}`), then run again | infra_down, exit 69, HTTP 503, retryable | tests/proxy/exits/addresses-failures.mjs::a_registry_that_cannot_be_fetched_is_named |
registry-failing | reading the IANA {family} special-purpose registry {address} | IANA answered HTTP {status}, a failure or a limit on its side, so the registry is not read | HTTP {status} {statusText} | run again later; IANA answers its registries again once it recovers | infra_down, exit 69, HTTP 503, retryable | tests/proxy/exits/addresses-failures.mjs::a_registry_iana_fails_to_serve_is_named |
registry-refused | reading the IANA {family} special-purpose registry {address} | IANA answered HTTP {status} for the registry address, so it no longer serves the registry there | HTTP {status} {statusText} | find the registry's current CSV address at https://www.iana.org/assignments and update REGISTRY_CSV; the next judgement reads the registries again; if IANA moved or reformatted them, the reader in src/proxy/quality/special-addresses.ts needs the new form | config, exit 1, HTTP 503 | tests/proxy/exits/addresses-failures.mjs::a_registry_address_iana_does_not_serve_is_named |
registry-answer-unread | reading the IANA {family} special-purpose registry {address} | the registry answer broke off while its body was read | HTTP {status}; {answer} | run again; the answer is read whole or not at all | infra_down, exit 69, HTTP 503, retryable | tests/proxy/exits/addresses-failures.mjs::a_registry_answer_cut_off_is_named |
registry-empty | reading the IANA {family} special-purpose registry {address} | the registry answered no header line, so there is no column to read | {bytes} bytes of blank lines | the next judgement reads the registries again; if IANA moved or reformatted them, the reader in src/proxy/quality/special-addresses.ts needs the new form | unknown, exit 1, HTTP 500 | tests/proxy/exits/addresses-failures.mjs::an_empty_registry_is_named |
registry-column-absent | reading the IANA {family} special-purpose registry {address} | the registry has no "{column}" column, which the reader needs to judge an address | header: {header} | the next judgement reads the registries again; if IANA moved or reformatted them, the reader in src/proxy/quality/special-addresses.ts needs the new form | unknown, exit 1, HTTP 500 | tests/proxy/exits/addresses-failures.mjs::a_registry_without_its_columns_is_named |
registry-prefix-invalid | reading the IANA {family} special-purpose registry {address} | the registry lists "{prefix}" ({name}), which is not an address prefix of the form network/length | block "{prefix}" named {name} | the next judgement reads the registries again; if IANA moved or reformatted them, the reader in src/proxy/quality/special-addresses.ts needs the new form | unknown, exit 1, HTTP 500 | tests/proxy/exits/addresses-failures.mjs::a_registry_block_that_is_no_prefix_is_named |
registry-blocks-none | reading the IANA {family} special-purpose registry {address} | the registry lists no special-purpose blocks, so no address could be judged | {records} records under header {header} | the next judgement reads the registries again; if IANA moved or reformatted them, the reader in src/proxy/quality/special-addresses.ts needs the new form | unknown, exit 1, HTTP 500 | tests/proxy/exits/addresses-failures.mjs::a_registry_without_blocks_is_named |
registries-unread | judging whether {address} is public | the IANA special-purpose registries have not been read in this process (loadSpecialAddresses), so the address is refused rather than guessed; a defect of the caller | address {address}; registries unread | report the caller: it must await loadSpecialAddresses() before judging an address | unknown, exit 1, HTTP 500 | tests/proxy/exits/addresses-failures.mjs::an_address_judged_before_the_registries_are_read_is_refused |
address-not-ip | judging whether {address} is public | {address} is not an IP address, so no registry block can hold it | address "{address}" | resolve the name to its addresses first and judge each of them | refused, exit 1, HTTP 403 | tests/proxy/exits/addresses-failures.mjs::text_that_is_no_address_is_refused |
address-not-public | judging whether {address} is public | non-public network address denied: {address} is in {prefix} ({name}), which IANA does not declare globally reachable | {address} in {prefix} ({name}) | reach the target on a public address; a private, loopback, link-local or documentation address is never admitted | refused, exit 1, HTTP 403 | tests/proxy/exits/addresses-failures.mjs::a_private_address_is_refused_naming_its_block |
trajectories#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
parp-organization-unset | reading the PARP reset mail of {email} from Skrzynka | WELES_SKRZYNKA_ORGANIZATION names no Skrzynka organization, and Skrzynka files the PARP mailbox under one | WELES_SKRZYNKA_ORGANIZATION {value}; no browser was opened | set WELES_SKRZYNKA_ORGANIZATION to the organization the mailbox of {email} is declared in (`stado service env set weles`), then run again | config, exit 1, HTTP 503 | tests/trajectories/parp_ncbr/parp-login-failures.mjs::a_parp_login_without_an_organization_is_refused |
parp-account-other | binding the new PARP password of {email} to its vault item | the run's credential constraints name another account than PARP_EMAIL, so the password would be kept as another account's login | PARP_EMAIL {email}; the constraints name {constrained} for item {item}; no browser was opened | start the run with PARP_EMAIL and the constraints' account_email naming the same PARP account | config, exit 1, HTTP 503 | tests/trajectories/parp_ncbr/parp-custody.mjs::a_reset_for_another_account_is_refused |
parp-contract-other | binding the new PARP password of {email} to its vault item | the item the constraints name is no managed PARP password: its contract is set on another origin or is no password | item {item}: field {field}, origin {origin}; a PARP password is field password on {expected}; no browser was opened | declare {item} with provider parp in WELES_MANAGED_PASSWORD_CONTRACTS_FILE, or start the run for the item declared so | config, exit 1, HTTP 503 | tests/trajectories/parp_ncbr/parp-custody.mjs::a_reset_into_an_item_of_another_provider_is_refused |
parp-mailbox-absent | reading the PARP reset mail of {email} from Skrzynka | Skrzynka declares no mailbox {email} in organization {organization}, so the reset mail cannot be read | `{command}` listed {count} mailboxes: {emails}; the reset was already asked of PARP | declare the mailbox from its Skarbiec item (`skrzynka --organization {organization} mailbox declare --skarbiec-item <item>`), then run again | not_found, exit 1, HTTP 404 | tests/trajectories/parp_ncbr/parp-login-failures.mjs::a_parp_mailbox_skrzynka_does_not_declare_is_named |
parp-reset-unarrived | reading the PARP reset mail of {email} from Skrzynka | no mail from {sender} received since {since} links below {host}: the reset mail has not arrived yet, or PARP held the reset (a captcha or a block) | {count} messages from {sender} in mailbox {mailbox} since {since}, none with a link below {host} | run again once the reset mail arrives; the run's screenshots under its recordings (parp_<step>_*.png) show the page (reset_submitted) | timeout, exit 69, HTTP 504, retryable | tests/trajectories/parp_ncbr/parp-login-failures.mjs::a_reset_mail_that_has_not_arrived_is_named |
parp-field-absent | filling {what} on {page} | the page shows no visible field {what}: PARP changed its form, or another page opened in its place | page {page}; selector {selector} matches no visible field | the run's screenshots under its recordings (parp_<step>_*.png) show the page; correct the selector in src/trajectories/feng/auth/parp_login.mjs to the field the page shows | unknown, exit 1, HTTP 500 | tests/trajectories/parp_ncbr/parp-login-failures.mjs::a_page_without_its_field_is_named |
parp-record-unwritten | recording where the new PARP password of {email} is kept | the reference record could not be written ({errno}): the recordings directory is absent or not writable; the password is already in vault item {item} | {path}: {problem} | make the recordings directory writable by the worker user (WELES_RECORDINGS_ROOT names it); the password is read from item {item} | config, exit 1, HTTP 503 | tests/trajectories/parp_ncbr/parp-custody.mjs::a_record_that_cannot_be_written_is_named |
publish-arguments-untaken | reading the node src/trajectories/publish-requirements.mjs command line | it takes no argument: the file and the object are named by WELES_TRAJECTORY_REQUIREMENTS_FILE and WELES_TRAJECTORY_REQUIREMENTS_URI | arguments: {given} | run node src/trajectories/publish-requirements.mjs with no argument | refused, exit 2, HTTP 403 | tests/trajectories/publish-requirements.mjs::an_argument_is_refused_before_anything_is_read |
requirements-unreadable | reading the trajectory requirements {path} | the file cannot be read: it does not exist at that path, or this user may not read it | {answer} | name the declaration with WELES_TRAJECTORY_REQUIREMENTS_FILE, or run from a Weles tree that carries src/trajectories/requirements.json | config, exit 1, HTTP 503 | tests/trajectories/publish-requirements.mjs::a_declaration_that_does_not_exist_is_named |
requirements-not-json | reading the trajectory requirements {path} | the file is not one JSON document | the parser said: {answer} | correct {path} (src/trajectories/requirements.json, or the file WELES_TRAJECTORY_REQUIREMENTS_FILE names), then run it again | config, exit 1, HTTP 503 | tests/trajectories/publish-requirements.mjs::a_declaration_that_is_not_json_is_named |
requirements-schema | reading the trajectory requirements {path} | it declares another schema than {expected}, so its readers would read something else | schema: {schema} | correct {path} (src/trajectories/requirements.json, or the file WELES_TRAJECTORY_REQUIREMENTS_FILE names), then run it again | config, exit 1, HTTP 503 | tests/trajectories/publish-requirements.mjs::a_declaration_of_another_schema_is_refused |
requirements-empty | reading the trajectory requirements {path} | it names no trajectory: trajectories is absent, not an object, or empty | trajectories: {value} | correct {path} (src/trajectories/requirements.json, or the file WELES_TRAJECTORY_REQUIREMENTS_FILE names), then run it again | config, exit 1, HTTP 503 | tests/trajectories/publish-requirements.mjs::a_declaration_without_trajectories_is_refused |
requirements-capabilities-not-list | reading the trajectory requirements {path} | a trajectory declares its capabilities as something other than a list | {trajectory}: {value} | correct {path} (src/trajectories/requirements.json, or the file WELES_TRAJECTORY_REQUIREMENTS_FILE names), then run it again | config, exit 1, HTTP 503 | tests/trajectories/publish-requirements.mjs::a_trajectory_without_a_list_is_refused |
requirements-capability-unknown | reading the trajectory requirements {path} | a trajectory names a capability no reader on the fleet knows, so publishing it would promise what nothing keeps | {trajectory} declares {unknown}; the known capabilities are {known} | correct {path} (src/trajectories/requirements.json, or the file WELES_TRAJECTORY_REQUIREMENTS_FILE names), then run it again | config, exit 1, HTTP 503 | tests/trajectories/publish-requirements.mjs::an_unknown_capability_is_refused |
requirements-not-read-back | reading {uri} back after publishing it | Stado accepted the write and the object then reads back other bytes: another publisher wrote it in between, or the store did not keep the write | local {local_bytes} bytes sha256 {local_digest}, published {remote_bytes} bytes sha256 {remote_digest} | run it again; `stado storage stat {uri}` reports the object Stado holds now | infra_down, exit 69, HTTP 503, retryable | tests/trajectories/publish-requirements.mjs::a_publication_read_back_otherwise_is_named |
input-unset | reading {names}: {what} | none of these is set, and the run cannot know the value by itself | none of {names} is set | state one of {names} when the run is started; nothing is assumed in its place | refused, exit 2, HTTP 403 | tests/trajectories/pages/inputs-failures.mjs::a_run_without_its_stated_text_is_refused |
count-invalid | reading {name}: {what} | it is unset, or not a whole number above zero | {name}: {value} | state {name} when the run is started (the request parameter or environment variable of that name); nothing is assumed in its place, as a whole number above zero | refused, exit 2, HTTP 403 | tests/trajectories/pages/inputs-failures.mjs::a_count_that_is_no_whole_number_is_refused |
budget-invalid | reading {name}: {what} | it is unset, or not a whole number of zero or more | {name}: {value} | state {name} when the run is started (the request parameter or environment variable of that name); nothing is assumed in its place, as a whole number of zero or more | refused, exit 2, HTTP 403 | tests/trajectories/pages/inputs-failures.mjs::a_negative_budget_is_refused |
number-invalid | reading {name}: {what} | it is unset, or not a finite number | {name}: {value} | state {name} when the run is started (the request parameter or environment variable of that name); nothing is assumed in its place, as a number | refused, exit 2, HTTP 403 | tests/trajectories/pages/inputs-failures.mjs::a_limit_that_is_no_number_is_refused |
choice-invalid | reading {names}: {what} | the value is not one of the choices this trajectory knows | {names}: "{value}"; the choices: {choices} | state one of {choices} in {names} | refused, exit 2, HTTP 403 | tests/trajectories/pages/inputs-failures.mjs::a_choice_the_trajectory_does_not_know_is_refused |
json-invalid | reading {name} as JSON | it is set and does not parse as JSON | {name}: {answer} | state {name} as one JSON value, or leave it unset where the trajectory takes no value | refused, exit 2, HTTP 403 | tests/trajectories/pages/inputs-failures.mjs::a_json_input_that_does_not_parse_is_refused |
page-field-value-differs | writing {what} on {url} | after the text was typed the field holds other text: the page dropped or reformatted keystrokes, or a clear did not take and the old value stayed | held {observed} ({held} characters), typed {expected} characters; nothing was saved | look at the run recording at that step; when the page reformats the value (a mask, a counter), state the value in the form the page keeps | refused, exit 1, HTTP 403 | tests/trajectories/pages/page-failures.mjs::a_field_that_drops_keystrokes_is_named |
page-field-absent | writing the stated {what} on {url} | the page shows no such field, so the stated value would not be written | no element matches {selector} | leave the value unstated when this page has no such field, or open the page that has it; the run recording shows what the page held | not_found, exit 1, HTTP 404 | tests/trajectories/pages/page-failures.mjs::a_stated_value_whose_field_is_absent_is_named |
page-field-unfocused | typing into the field {selector} on {url} | the field did not take focus after a pointer click and a focus call, so every keystroke would go elsewhere: a control drawn over it took the click | the focused element is {focused} | look at the run recording at that step for what covers the field, and dismiss it before typing | refused, exit 1, HTTP 403 | tests/trajectories/pages/page-failures.mjs::a_field_that_never_takes_focus_is_named |
page-control-missing | taking the {what} action once on {url} | the page shows neither the control that takes it nor the one that undoes it: the page changed, or it is not the page the action is on | neither {take} nor {taken} is visible | open the page the action is on (the run recording shows the page it reached); when the site changed its controls, update the trajectory | not_found, exit 1, HTTP 404 | tests/trajectories/pages/page-failures.mjs::a_page_without_the_control_is_named |
page-value-too-long | fitting {what} into its field on {url} | the value is longer than the field's own maxlength, and a value is written whole or not at all | {length} characters; the field takes {max} | shorten the stated value to {max} characters (the character's profile, or the run's input) | refused, exit 2, HTTP 403 | tests/trajectories/pages/page-failures.mjs::a_value_longer_than_its_field_is_refused |
page-row-unnamed | finding the row about {subject} among {rows} row(s) of the table | the model answered neither a row number in range nor NONE | it answered {answer} | run it again; when the model keeps answering so, `stado capabilities resolve inference --consumer weles` shows which model Weles asks | infra_down, exit 69, HTTP 503, retryable | tests/trajectories/pages/page-failures.mjs::a_model_that_names_no_row_is_named |
page-control-unseen | clicking the control "{description}" on {url} | the vision model found no control matching the description on the settled page | no click target for "{description}" | look at the run recording at that step: open the page that shows the control, or describe it as the page labels it | not_found, exit 1, HTTP 404 | tests/trajectories/pages/page-failures.mjs::a_control_the_model_does_not_see_is_named |
page-closed | reading the page during {operation} | the page or its browser closed before the read finished: the run closed it, the operator did, or the browser ended | last address {url} | run it again; when it recurs, the run log says what closed the page before this step | infra_down, exit 69, HTTP 503, retryable | tests/trajectories/pages/page-failures.mjs::a_closed_page_is_a_terminal_failure |
page-crashed | reading the page during {operation} | the page's renderer crashed (out of memory, a browser fault, the site's own script) | last address {url} | run it again; when the same page keeps crashing, the run's browser log names the renderer's fault | infra_down, exit 69, HTTP 503, retryable | tests/trajectories/pages/page-failures.mjs::a_page_that_crashes_is_named |
page-request-failed | waiting for the answer to {request} after {operation} on {url} | the browser reports the request failed before any answer: the connection was refused, reset or blocked, or the proxy refused it | {answer} | run it again; when it recurs, the browser's reason above and the run's network log name the address that refused it (the proxy, the site) | infra_down, exit 69, HTTP 503, retryable | tests/trajectories/pages/page-failures.mjs::a_request_that_fails_is_named |
page-address-unparsable | printing a page address for the log | the address is no URL, even against the page it is relative to, so it is not printed (it may carry a secret) | {length} characters, relative to {base} | look at the run recording at that step for the link the page held | refused, exit 1, HTTP 403 | tests/trajectories/pages/page-failures.mjs::an_address_that_is_no_url_is_not_printed |
overleaf-listing-incomplete | listing the projects Overleaf's dashboard holds | the dashboard's load-more control was pressed and the page settled with no more project rows than before, so the projects past the {rows} read are not listed, and a listing of {rows} would be taken for every project | {rows} project rows; the control read {label} | look at the run recording at the press: when the dashboard loaded its next projects late, run the listing again; when the control pressed is not the dashboard's load-more, the dashboard changed where it places it | refused, exit 1, HTTP 403 | tests/overleaf/dashboard-projects.mjs::a_press_that_adds_no_rows_is_refused |
engagement-account-absent | reading the active {platform} account for the {verb} run | the accounts store holds no active account of that platform: none was imported, or every one is marked banned or retired | no active {platform} account in {store} | import or reactivate a {platform} account record in the accounts store; `stado credentials ls` lists the account records the store holds | not_found, exit 1, HTTP 404 | tests/trajectories/engagement-failures.mjs::an_action_without_an_account_is_named |
engagement-character-absent | choosing what {platform} account {account} writes for the {verb} run | the account has no character linked to speak as, and the run states no text of its own | account record {item} ({account}) holds neither metadata.character nor metadata.character_id; SVC_TEXT unset; no browser was started | state SVC_TEXT with the text to post, or link a character to account record {item} (metadata.character_id); `stado credentials ls` lists the account records the store holds | refused, exit 2, HTTP 403 | tests/trajectories/engagement-failures.mjs::an_action_without_a_character_or_text_is_refused |
engagement-product-unnamed | choosing the product the {verb} run of {platform} promotes as account {account} | the run names no PRODUCT_ID and the account's character promotes no product | PRODUCT_ID unset; character {character} of account record {item} has no promoted_product_id; no browser was started | state PRODUCT_ID, or give character {character} a promoted_product_id | refused, exit 2, HTTP 403 | tests/trajectories/engagement-failures.mjs::a_promotion_without_a_product_is_refused |
engagement-product-unknown | reading product {product} the {verb} run of {platform} promotes as account {account} | the vault holds no product record of that id | no live record of record_kind product whose item id or product_id is {product} in {store} (PRODUCT_ID {stated}); no browser was started | name a product the vault holds (PRODUCT_ID), or record product {product} first; `stado credentials ls` lists the account records the store holds | not_found, exit 1, HTTP 404 | tests/trajectories/engagement-failures.mjs::a_promotion_of_an_unknown_product_is_named |
engagement-ban-signal | the {verb} run of {platform} account {account} | the platform's ban detector reads a restriction on the page after the run: the account is banned, challenged or limited | signal {signal} after {label}; result {result} | open the account's recording of this run (ban_signal.json beside it names the evidence); resolve the platform's challenge before the account acts again | refused, exit 1, HTTP 403 | tests/trajectories/engagement-failures.mjs::a_run_that_meets_a_ban_signal_is_named |
engagement-recovery-unstarted | starting the recovery of {platform} account {account} after its probe read a checkpoint | the recovery the checkpoint needs could not be started | {answer} (snapshot {snapshot}) | correct what the recovery error names, then run the health probe again | infra_down, exit 69, HTTP 503, retryable | tests/trajectories/engagement-failures.mjs::a_checkpoint_whose_recovery_cannot_start_is_named |
engagement-health-unread | reading the health of {platform} account {account} | the probe read neither a verdict of the ban detector nor a signed-in page: the page did not load, or the platform changed it | snapshot {snapshot} | open the snapshot and the run recording; run the probe again, and update the detector when the platform changed its page | infra_down, exit 69, HTTP 503, retryable | tests/trajectories/engagement-failures.mjs::a_probe_that_reads_nothing_is_named |
engagement-observation-unset | reading {name} of the declared observation | it is unset: an observation runs only as admission resolves it from the parts of src/worker/deploy/weles-observation-declaration/ | {name}: {value} | start the observation through the Weles API (`POST /run` with the observation), which states it | refused, exit 2, HTTP 403 | tests/trajectories/engagement-failures.mjs::an_observation_without_its_declaration_is_refused |
engagement-scrolls-invalid | reading GENERIC_OBSERVATION_SCROLLS of the declared observation {observation} | it is not a whole number of screens | GENERIC_OBSERVATION_SCROLLS: {value} | state it as the whole number of screens the declaration gives | refused, exit 2, HTTP 403 | tests/trajectories/engagement-failures.mjs::an_observation_with_a_scroll_budget_that_is_no_number_is_refused |
engagement-platform-unsupported | choosing the ban detector of the observation {observation} | Weles has no ban detector for that platform, so the run could not tell a ban from a page | platform {platform}; detectors exist for {platforms} | observe one of {platforms} | refused, exit 2, HTTP 403 | tests/trajectories/engagement-failures.mjs::an_observation_of_a_platform_without_a_detector_is_refused |
document-unset | reading the declared document this run fetches | the run was started without the variable admission sets for it, so it was not started through generic_document_task | {name} is {value} | start the run as POST /run generic_document_task naming a declared document; a document trajectory cannot run on its own | config, exit 1, HTTP 503 | tests/trajectories/document-keep.mjs::a_keeper_started_without_admission_is_refused |
document-download-failed | downloading document {document} from {origin} | the browser reports the download of {file} did not complete | {reason} | open this run's recording to see where the site stopped the download, then run the document again; nothing was kept | unknown, exit 1, HTTP 500 | tests/trajectories/document-keep.mjs::a_failed_download_is_named |
document-empty | checking document {document} downloaded as {file} | the site handed over a file with no bytes, so it holds no report however it is named | {file}: 0 bytes | open this run's recording to see what the site showed for the report, then run the document again; nothing was kept | unknown, exit 1, HTTP 500 | tests/trajectories/document-keep.mjs::an_empty_download_is_named |
document-media-mismatch | checking document {document} downloaded as {file} | its bytes are not the {media_type} the declaration states, so the program that reads it would read something else | {file} ({bytes} bytes): {found} | open this run's recording to see which file the site handed over; if the site now gives the report in another format, change the document's media type in the declaration and release it; nothing was kept | unknown, exit 1, HTTP 500 | tests/trajectories/document-keep.mjs::a_download_of_another_media_type_is_named |
document-keep-failed | keeping document {document} at {path} | the documents folder cannot be written by the user the run runs as | {answer} | make {path} writable by that user, or set WELES_DOCUMENTS_DIR to a folder it can write, then run the document again | config, exit 1, HTTP 503 | tests/trajectories/document-keep.mjs::a_document_that_cannot_be_kept_is_named |
document-record-unreadable | comparing document {document} with the copies kept in {path} | a record beside an earlier copy cannot be read as the keeper writes it, so whether this download is new cannot be told | {record}: {answer} | restore {record} from its copy, or remove it together with the document it describes, then run the document again | config, exit 1, HTTP 503 | tests/trajectories/document-keep.mjs::an_unreadable_earlier_record_is_named |
document-nothing-kept | the run of document {document} through {trajectory} | the reviewed trajectory ended without handing a download to the keeper, so no document was fetched | {trajectory} finished; no keepDocument or keepDownload call in this run | open this run's recording to see where the trajectory stopped; the trajectory must end by handing its download to keepDownload | unknown, exit 1, HTTP 500 | tests/trajectories/document-keep.mjs::a_trajectory_that_keeps_nothing_is_named |
document-keep-count-invalid | reading how many copies of document {document} weles documents prune keeps | --keep is not a whole number of at least 1 | --keep {given} | give --keep as the number of newest copies to keep, 1 or more; prune never removes every copy | refused, exit 2, HTTP 403 | tests/cli/documents.mjs::a_prune_keeping_no_whole_number_is_refused |
document-prune-failed | removing an older copy of document {document} | the copy or its record cannot be removed by the user weles runs as | {path}: {answer} | make the directory of {path} writable by that user and prune again; the copies listed before it were removed | config, exit 1, HTTP 503 | tests/cli/documents.mjs::a_copy_that_cannot_be_removed_is_named |
google-login-incomplete | reading the Google login item {item} | the item lacks {field}: it was stored without it, or it is not a login item | item {item}: no non-empty {field} | store the login with fields.username and fields.password (and fields.totp_secret for an authenticator); `stado credentials get {item}` shows the item as the store holds it | config, exit 1, HTTP 503 | tests/trajectories/google/google-sign-in-failures.mjs::a_login_without_a_password_is_named |
google-identity-mismatch | choosing the Google SSO identity for {requested} | the scoped Google SSO login this run holds is another account | requested {requested}; the scoped login is {scoped} | run with the account the scoped login holds, or scope the login of the requested account to this run | config, exit 1, HTTP 503 | tests/trajectories/google/google-sign-in-failures.mjs::a_requested_account_the_scoped_login_is_not_is_named |
google-totp-uri-secretless | reading the authenticator seed of {item} | it is an otpauth URI without a secret parameter, so no code can be generated | an otpauth URI with parameters {parameters} | store the seed again as the base32 secret or the whole otpauth URI Google showed; `stado credentials get {item}` shows the item as the store holds it | config, exit 1, HTTP 503 | tests/trajectories/google/google-sign-in-failures.mjs::an_otpauth_uri_without_a_secret_is_named |
google-totp-uri-parameter | reading the authenticator seed of {item} | it is an otpauth URI stating {name} {value}, which no code is computed with: the algorithm is SHA1, SHA256 or SHA512 and digits and period are whole numbers above zero (the Key URI Format); `skarbiec totp judge` calls an address it would not store invalid before any code is asked for | an otpauth URI with {name}={value} | store the whole otpauth URI Google showed with `skarbiec totp set`, which refuses one its codes are not computed with; `stado credentials get {item}` shows the item as the store holds it | config, exit 1, HTTP 503 | tests/trajectories/google/google-sign-in-failures.mjs::an_otpauth_uri_with_a_parameter_no_code_is_computed_with_is_named |
google-totp-empty | reading the authenticator seed of {item} | it is blank, or decodes to no whole byte, so no code can be computed from it; `skarbiec totp seed` calls the same text declared_empty or invalid | {length} characters | store the base32 secret Google showed when the authenticator was added; `stado credentials get {item}` shows the item as the store holds it | config, exit 1, HTTP 503 | tests/trajectories/google/google-sign-in-failures.mjs::an_empty_authenticator_seed_is_named |
google-totp-character-invalid | reading the authenticator seed of {item} | it is not padded RFC 4648 Base32 as Skarbiec stores a seed (the letters A-Z and digits 2-7, then at most six = filling the last block of eight characters): lowercase, spaces and dashes are not read, exactly as `skarbiec totp seed` calls such a text invalid | character {character} at position {position} of {length} | store the seed again with `skarbiec totp set`, which reads it as Google showed it and keeps it in that form; `stado credentials get {item}` shows the item as the store holds it | config, exit 1, HTTP 503 | tests/trajectories/google/google-sign-in-failures.mjs::an_authenticator_seed_with_a_foreign_character_is_named |
google-browser-not-secure | signing in to Google | Google refused the sign-in on its rejection page: it does not trust this browser or its address | {url} | the run's recording and page diagnosis at that step show Google's page; sign in from the account's own persona and proxy, or have the operator approve the device in the account's security settings | auth, exit 1, HTTP 401 | tests/trajectories/google/google-sign-in-failures.mjs::a_sign_in_google_refuses_as_not_secure_is_named |
google-password-challenge-unavailable | reaching the password step of the Google sign-in | Google offered no password challenge among the ways to sign in (a passkey, a phone prompt or a code only) | {page} | the run's recording and page diagnosis at that step show Google's page; let the account sign in with a password again in its Google security settings | auth, exit 1, HTTP 401 | tests/trajectories/google/google-sign-in-failures.mjs::an_account_without_a_password_challenge_is_named |
google-password-rejected | submitting the password to Google | Google marked the password field invalid: the stored password is not the account's | {page} | store the account's current password as fields.password of its login item (`stado credentials get <item>` shows the item) | auth, exit 1, HTTP 401 | tests/trajectories/google/google-sign-in-failures.mjs::a_wrong_password_is_named |
google-password-refused | submitting the password to Google | Google stated a refusal on the password step (its alert below says why) | {page} | the run's recording and page diagnosis at that step show Google's page; resolve what Google's alert states, then start a new run of the same action, which starts the sign-in again | auth, exit 1, HTTP 401 | tests/trajectories/google/google-sign-in-failures.mjs::a_refusal_google_states_on_the_password_step_is_quoted |
google-authenticator-unavailable | answering the second factor Google asks for | Google asks for a second factor and the authenticator method could not be chosen, and a push or SMS loop is not started | a stored authenticator seed: {seeded}; {url} | the run's recording and page diagnosis at that step show Google's page; add an authenticator to the account and store its seed as fields.totp_secret | auth, exit 1, HTTP 401 | tests/trajectories/google/google-sign-in-failures.mjs::a_second_factor_without_the_authenticator_is_named |
google-ended-during-approval | waiting on Google's device prompt of the enrolment the owner ordered for {item} | Google left its challenge while the run waited, so the approval has nothing to complete; nothing about the account changed | Google moved to {detail} | the order this run spent is used up; if the enrolment is still wanted it is an enrolment of {item}, which only the vault's owner orders in Skarbiec Desktop (https://skarbiec.wisent.com/docs/login/enrol-order), and the owner approves Google's prompt on the phone | auth, exit 1, HTTP 401 | tests/trajectories/google/google-sign-in-failures.mjs::a_challenge_google_ends_during_approval_is_named |
google-phone-prompt-not-shown | choosing Google's phone prompt for the enrolment the owner ordered for {item} | Google showed another page instead of the device prompt | {url} | the run's recording and page diagnosis at that step show Google's page; the order this run spent is used up; if the enrolment is still wanted it is an enrolment of {item}, which only the vault's owner orders in Skarbiec Desktop (https://skarbiec.wisent.com/docs/login/enrol-order) | auth, exit 1, HTTP 401 | tests/trajectories/google/google-sign-in-failures.mjs::a_phone_method_that_shows_no_prompt_is_named |
google-push-not-approved | completing the device prompt of the enrolment the owner ordered for {item} | Google did not complete the prompt: it was declined on the phone, or it expired before it was approved | {detail} | the order this run spent is used up; if the enrolment is still wanted it is an enrolment of {item}, which only the vault's owner orders in Skarbiec Desktop (https://skarbiec.wisent.com/docs/login/enrol-order), and the owner approves Google's prompt on the phone | auth, exit 1, HTTP 401 | tests/trajectories/google/google-sign-in-failures.mjs::a_prompt_the_phone_declines_is_named |
google-seed-missing | answering the second factor Google asks {item} for | the login keeps no working authenticator seed where it keeps its second factor, and no other method is chosen, so nothing reaches the owner's phone; what Google asked for is recorded on that item, so the next sign-in of the account is refused before the browser | {page} | store the account's seed (the seed or otpauth address on stdin to `skarbiec totp set` on the item `skarbiec login factor list {item}` names), or an enrolment of {item}, which only the vault's owner orders in Skarbiec Desktop (https://skarbiec.wisent.com/docs/login/enrol-order) | auth, exit 1, HTTP 401 | tests/trajectories/google/google-sign-in-failures.mjs::a_code_challenge_without_a_seed_is_named |
google-password-without-seed | starting the Google sign-in of {item} | the login is read through scoped fields or a service credential, which carry no record of what its account asks after the password, and Skarbiec does not judge its seed present, so Google could answer its address or password with a prompt on the owner's phone; nothing was typed into Google's page | skarbiec totp judge: its seed is {seed} | store the account's seed (the seed or otpauth address on stdin to `skarbiec totp set {item}`), or an enrolment of {item}, which only the vault's owner orders in Skarbiec Desktop (https://skarbiec.wisent.com/docs/login/enrol-order) | auth, exit 1, HTTP 401 | tests/trajectories/google/google-scoped-seed.mjs::a_scoped_login_without_a_working_seed_sends_no_password |
google-code-rejected | submitting the authenticator code | Google rejected the code the stored seed produced: the seed is not the account's current authenticator, or this host's clock is off | {page} | check the host clock, then replace the seed with `skarbiec totp set` on the item `skarbiec login factor list <login>` names, or with an enrolment of the login, which only the vault's owner orders in Skarbiec Desktop (https://skarbiec.wisent.com/docs/login/enrol-order) | auth, exit 1, HTTP 401 | tests/trajectories/google/google-sign-in-failures.mjs::a_code_google_rejects_is_named |
google-state-unreadable | reading which second factor Google asks for | the page could not be read as a Google challenge: it navigated mid-read, or it is not Google's sign-in | {page} | the run's recording and page diagnosis at that step show Google's page; start a new run of the same action, which starts the sign-in again | auth, exit 1, HTTP 401 | tests/trajectories/google/google-sign-in-failures.mjs::a_challenge_page_that_cannot_be_read_is_named |
google-post-login-repeated | declining what Google offers after the sign-in | the offer was declined and Google showed it again, so declining it does not move on | {url} | the run's recording and page diagnosis at that step show Google's page; accept or settle the offer in the account's settings once, then start a new run of the same action, which starts the sign-in again | refused, exit 1, HTTP 403 | tests/trajectories/google/google-sign-in-failures.mjs::an_offer_that_returns_after_it_is_declined_is_named |
google-post-login-undismissable | declining what Google offers after the sign-in | the page shows no control that declines or skips the offer | {url} | the run's recording and page diagnosis at that step show Google's page; settle the offer in the account's settings once, then start a new run of the same action, which starts the sign-in again | refused, exit 1, HTTP 403 | tests/trajectories/google/google-sign-in-failures.mjs::an_offer_without_a_way_to_decline_is_named |
apple-ads-action-unknown | finding Apple Ads action {action} in the catalogue {catalogue} | the catalogue declares no such action, and it is neither ads_cli nor ads_api_request | {action} is not among the {count} actions declared, e.g. {examples} | state APPLE_ADS_ACTION as one of the actions the catalogue declares (the keys of `actions` in its parts), or ads_cli with APPLE_ADS_CLI_ARGS | refused, exit 2, HTTP 403 | tests/trajectories/apple-ads-failures.mjs::an_action_the_catalogue_does_not_declare_is_named |
apple-ads-input-undeclared | building the asc arguments of Apple Ads action {action} | the catalogue gives the action an input its own `inputs` do not declare, so no flag or environment value is known for it | action {action} names input {input}; the inputs declared: {declared} | declare input {input} under `inputs` in src/trajectories/apple/ads/run/actions/, or correct the action's inputs there | config, exit 1, HTTP 503 | tests/trajectories/apple-ads-failures.mjs::an_action_naming_an_undeclared_input_is_named |
apple-ads-value-missing | building the asc arguments of Apple Ads action {action} | the action needs {label}, and the run states none | {source} is unset or empty | state {label} (the run parameter of that name; {source} in the environment) | refused, exit 2, HTTP 403 | tests/trajectories/apple-ads-failures.mjs::an_action_without_its_required_value_is_refused |
apple-ads-cli-args-missing | building the asc arguments of Apple Ads action ads_cli | ads_cli runs the asc arguments the run states, and it states none | APPLE_ADS_CLI_ARGS is unset or empty | state apple_ads_cli_args (APPLE_ADS_CLI_ARGS) with the asc arguments to run, and confirm the run | refused, exit 2, HTTP 403 | tests/trajectories/apple-ads-failures.mjs::raw_arguments_without_their_text_are_refused |
apple-ads-cli-args-escape | splitting APPLE_ADS_CLI_ARGS into asc arguments | the text ends in a backslash inside quotes, which escapes nothing | APPLE_ADS_CLI_ARGS ({length} characters) ends in \ inside {quote} quotes: …{tail} | end the quoted argument after the escaped character, or remove the trailing backslash | refused, exit 2, HTTP 403 | tests/trajectories/apple-ads-failures.mjs::raw_arguments_ending_in_an_escape_are_refused |
apple-ads-cli-args-quote | splitting APPLE_ADS_CLI_ARGS into asc arguments | a quote opened in the text is never closed, so where the argument ends is unknown | APPLE_ADS_CLI_ARGS ({length} characters): {quote} opened at character {opened} is not closed | close the quote opened at character {opened} | refused, exit 2, HTTP 403 | tests/trajectories/apple-ads-failures.mjs::raw_arguments_with_an_open_quote_are_refused |
apple-ads-unconfirmed | running Apple Ads action {action} | the action would {change}, and the run did not confirm it | none of APPLE_ADS_CONFIRM, WRITE_CONFIRM, SUBMIT is 1; asc was not run | state apple_ads_confirm=true, write_confirm=true or submit=true in the run parameters to {change} | refused, exit 2, HTTP 403 | tests/trajectories/apple-ads-failures.mjs::a_change_the_run_did_not_confirm_is_refused |
apple-ads-asc-unstartable | starting {program} for Apple Ads action {action} | the asc program cannot be run: it is not on PATH, the path ASC_BIN names does not exist, or it is not executable | {program}: {answer} | put the asc program on PATH, or name its binary with ASC_BIN; `command -v asc` shows which one PATH finds and `asc --version` that it runs | config, exit 1, HTTP 503 | tests/trajectories/apple-ads-failures.mjs::an_asc_that_cannot_be_started_is_named |
apple-ads-asc-signalled | running {program} for Apple Ads action {action} | asc was ended by a signal before it finished, so whether the action took effect is unknown | ended by {signal}; its last words: {said} | read the action's state in Apple Ads before running a change again (an ads_*_view or list action), then run it again | infra_down, exit 69, HTTP 503, retryable | tests/trajectories/apple-ads-failures.mjs::an_asc_ended_by_a_signal_is_named |
avatar-locator-invalid | reading the character record's avatar to upload it | avatar_url (or the first image) is not a private object under stado://weles/avatars/: a provider URL or another product's path would let the run fetch from an address nobody admitted | avatar: {locator} | store the image as a private object under stado://weles/avatars/<name> and set the character record's avatar_url to that URI | refused, exit 1, HTTP 403 | tests/trajectories/avatars/avatar-failures.mjs::an_avatar_outside_the_private_avatars_is_refused |
avatar-not-image | making the avatar {locator} upright and square | the object holds no image the image library can decode (its format is not one it reads, or the bytes are cut short) | {bytes} bytes beginning {head} (hex); decoder: {answer} | store a PNG, JPEG or WebP image at {locator} (or point avatar_url at one), then run the edit again | refused, exit 1, HTTP 403 | tests/trajectories/avatars/avatar-failures.mjs::an_avatar_that_is_no_image_is_named |
avatar-file-unwritable | keeping the avatar {locator} as a file to upload | the temporary directory {directory} refused the file ({code}): it is not writable by the worker user, or its disk is full | {path}: {answer} | give the worker's user write access to {directory} (or point TMPDIR at a writable directory) and free space on its disk | config, exit 1, HTTP 503 | tests/trajectories/avatars/avatar-failures.mjs::an_avatar_the_temporary_directory_refuses_is_named |
media-image-absent | reading the image {provider} generated for {model} through `{route}` | the answer carries no base64 image in data[0].b64_json, which the request asked for with response_format b64_json | data holds {count} entries; the first holds: {members} | run `stado capabilities inference` to see which alternative served weles; an alternative that answers with a URL instead needs response_format b64_json honoured, or another alternative chosen with `stado capabilities select inference --order ...` | unknown, exit 1, HTTP 500 | tests/trajectories/media-failures.mjs::an_answer_without_an_image_is_named |
media-image-unwritten | keeping the generated image in {directory} | the recordings directory refused the file ({errno}): it is not writable by the worker user, or its disk is full | {path}: {answer}; the image of {bytes} bytes was generated and is not kept | give the worker's user write access to {directory} (WELES_RECORDINGS_ROOT names its root) and free space on its disk, then run the post again | config, exit 1, HTTP 503 | tests/trajectories/media-failures.mjs::an_image_the_recordings_refuse_is_named |
ncbr-section-pattern-invalid | selecting the sections of the application SECTION_PATTERN names | SECTION_PATTERN is not a regular expression JavaScript can compile | SECTION_PATTERN: {pattern}: {answer} | state SECTION_PATTERN as a regular expression matching "<number> <title>" of the sections, such as ^2\. | refused, exit 2, HTTP 403 | tests/trajectories/parp_ncbr/ncbr-failures.mjs::a_section_pattern_that_does_not_compile_is_refused |
ncbr-section-pattern-empty | selecting the sections of the application SECTION_PATTERN names | SECTION_PATTERN matches none of the sections an LSI2 application form holds | SECTION_PATTERN: {pattern}; the sections: {sections} | state a SECTION_PATTERN that matches "<number> <title>" of at least one of {sections}, or leave it unset for every section | refused, exit 2, HTTP 403 | tests/trajectories/parp_ncbr/ncbr-failures.mjs::a_section_pattern_matching_no_section_is_refused |
ncbr-sections-unanalyzed | reading the Pangram analysis of each extracted section | the Pangram action of these sections ended without a trusted reading: it was refused, signed out, or its verdict could not be read | sections {sections}; their logs are in {directory} | read the logs in {directory}, correct what they name, then run the audit again with SECTION_PATTERN limited to those sections | refused, exit 1, HTTP 403 | tests/trajectories/parp_ncbr/ncbr-failures.mjs::a_section_pangram_did_not_analyze_is_named |
linkedin-proxy-unrequested | reading the proxy {requested} the LinkedIn registration asks for | a LinkedIn registration runs only through a dedicated ISP proxy, and the request asks for none | requested {requested} from {variable}; no browser was opened | request a dedicated ISP pool by its words, LINKEDIN_REGISTER_PROXY="isp <provider> <country>" (src/proxy/sources/pool_request.ts reads them), then run again | refused, exit 2, HTTP 403 | tests/trajectories/linkedin-register-failures.mjs::a_registration_asking_for_no_proxy_is_refused |
linkedin-proxy-url-form | reading the proxy {requested} the LinkedIn registration asks for | the request is a proxy URL, which names no provider or proxy type, so it cannot be judged a dedicated ISP exit | requested {requested} from {variable}; no browser was opened | request a dedicated ISP pool by its words, LINKEDIN_REGISTER_PROXY="isp <provider> <country>" (src/proxy/sources/pool_request.ts reads them), then run again | refused, exit 2, HTTP 403 | tests/trajectories/linkedin-register-failures.mjs::a_registration_asking_for_a_proxy_url_is_refused |
linkedin-proxy-session-unproxied | {step} of the LinkedIn registration | the session started without a proxy server although the registration asked for {requested}: the resolver handed it a direct connection | proxyConfig.server: {server}; provider {provider}; stage {step} | the run's proxy_preflight.json under its recordings names each provider the resolver tried; restore a provider with a dedicated ISP pool, then run again | config, exit 1, HTTP 503 | tests/trajectories/linkedin-register-failures.mjs::a_session_without_a_proxy_is_refused |
linkedin-proxy-server-unparsable | checking the proxy record the LinkedIn registration session resolved to | the record names a proxy server that is no URL, so its host and port cannot be checked against the retired pools | server of {length} characters, {userPart}; provider {provider}; the value is not shown, it may carry the password | write the provider record's server as <scheme>://<host>:<port>, then run again | config, exit 1, HTTP 503 | tests/trajectories/linkedin-register-failures.mjs::a_proxy_record_whose_server_is_no_url_is_refused |
linkedin-proxy-provider-unstated | checking the proxy record the LinkedIn registration session resolved to | the record names no provider, so the exit cannot be told a dedicated ISP one | server {scheme}://{host}:{port}; requested {requested}; provider: none | state the provider in the proxy record the resolver hands out for {requested}, or request another pool | config, exit 1, HTTP 503 | tests/trajectories/linkedin-register-failures.mjs::a_proxy_record_without_a_provider_is_refused |
linkedin-proxy-type-unstated | checking the proxy record the LinkedIn registration session resolved to | the {provider} record names no proxy_type, so the exit cannot be told a dedicated ISP one | server {scheme}://{host}:{port}; proxy_type: none | state proxy_type isp in the {provider} record of a dedicated ISP pool, or request another pool | config, exit 1, HTTP 503 | tests/trajectories/linkedin-register-failures.mjs::a_proxy_record_without_a_type_is_refused |
linkedin-proxy-type-other | checking the proxy record the LinkedIn registration session resolved to | the {provider} record is a {type} exit, and a LinkedIn registration runs only through a dedicated ISP one ({expected}) | proxy_type {type}; server {scheme}://{host}:{port} | request a dedicated ISP pool by its words, LINKEDIN_REGISTER_PROXY="isp <provider> <country>" (src/proxy/sources/pool_request.ts reads them), then run again | config, exit 1, HTTP 503 | tests/trajectories/linkedin-register-failures.mjs::a_proxy_record_of_another_type_is_refused |
linkedin-proxy-retired | checking the proxy record the LinkedIn registration session resolved to | the exit {host}:{port} belongs to a pool retired for LinkedIn ({reason}), so new accounts are not made through it | Skarbiec's retired_proxy_pools names it: {reason}; provider {provider} | request a dedicated ISP pool that is not retired; the retired_proxy_pools setting lists the retired ones | refused, exit 1, HTTP 403 | tests/trajectories/linkedin-register-failures.mjs::a_proxy_of_a_retired_pool_is_refused |
linkedin-proxy-rotating | checking the proxy record the LinkedIn registration session resolved to | the {provider} record carries a rotating-pool session, so its exit address can change during the registration, which a dedicated ISP exit never does | sticky_session_id of {length} characters; proxy_type {type}; server {scheme}://{host}:{port} | remove the sticky session from the {provider} dedicated ISP record, or request its ISP pool instead of the rotating one | config, exit 1, HTTP 503 | tests/trajectories/linkedin-register-failures.mjs::a_proxy_record_of_a_rotating_pool_is_refused |
linkedin-exit-probe-unanswered | checking the exit address of the LinkedIn registration at stage {stage} | the exit address service {service} did not answer through the proxy: the proxy or its tunnel failed, or the service is down | the browser context said: {problem}; proxy {scheme}://{host}:{port} | run again; when it keeps failing, the run's proxy_preflight.json under its recordings shows whether the provider's tunnel answered | infra_down, exit 69, HTTP 503, retryable | tests/trajectories/linkedin-register-failures.mjs::an_exit_probe_that_is_not_answered_is_named |
linkedin-exit-probe-refused | checking the exit address of the LinkedIn registration at stage {stage} | the exit address service {service} answered HTTP {status} instead of the address, so the exit could not be read | HTTP {status} {statusText}; proxy {scheme}://{host}:{port} | run again once {service} answers; HTTP 407 means the proxy refused its login, 429 that the service limits this exit | infra_down, exit 69, HTTP 503, retryable | tests/trajectories/linkedin-register-failures.mjs::an_exit_probe_answered_with_an_error_is_named |
linkedin-exit-unread | checking the exit address of the LinkedIn registration at stage {stage} | the exit address service {service} answered {found} where it answers one IP address | answer of {length} characters: {shown}; proxy {scheme}://{host}:{port} | run again; an answer that is a page rather than an address means a captive portal or the proxy itself answered | infra_down, exit 69, HTTP 503, retryable | tests/trajectories/linkedin-register-failures.mjs::an_exit_probe_answering_no_address_is_named |
linkedin-exit-drifted | checking the exit address of the LinkedIn registration at stage {stage} | the browser is now seen from {actual}, not {expected}, the address the registration was bound to: the exit changed under it | bound {expected} ({bound}); now {actual}; proxy {scheme}://{host}:{port}, provider {provider} | run the registration again on a dedicated ISP exit that keeps its address; an exit that drifts is a rotating one | refused, exit 1, HTTP 403 | tests/trajectories/linkedin-register-failures.mjs::an_exit_that_changed_is_named |
linkedin-preflight-unread | reading the run's proxy preflight record {path} for the failure diagnostics | the record exists and the system refused to read it ({errno}), so the diagnostics cannot say which exits the resolver tried | the system said: {problem}; the registration had already failed with {failure} | give this user read access to {path} (`ls -l {path}`); the registration's own failure is named above it in the run log | config, exit 1, HTTP 503 | tests/trajectories/linkedin-register-failures.mjs::a_preflight_record_that_cannot_be_read_is_named |
linkedin-preflight-not-json | reading the run's proxy preflight record {path} for the failure diagnostics | the record is not JSON: the preflight was cut short while writing it, or something else wrote it | {bytes} characters; the parser said: {problem}; the registration had already failed with {failure} | move {path} aside and run the registration again; the preflight writes a new one | config, exit 1, HTTP 503 | tests/trajectories/linkedin-register-failures.mjs::a_preflight_record_that_is_not_json_is_named |
linkedin-submit-disabled | submitting the LinkedIn signup form ({label}) | the submit button is shown and disabled: LinkedIn did not take what was typed into the form | the page is at {pageUrl}; nothing was sent | the run's recordings under linkedin_register/{label}.json keep the request and answer; run a new registration when the form's fields are what LinkedIn asks for | refused, exit 1, HTTP 403 | tests/trajectories/linkedin-register-failures.mjs::a_disabled_submit_is_named |
linkedin-submit-answer-unread | submitting the LinkedIn signup form ({label}) | the POST was answered and its body could not be read: the connection or the proxy's tunnel broke while it arrived | POST {requestUrl} answered HTTP {status}; the page is at {pageUrl}; reading the body said: {problem} | run again; the run's recordings under linkedin_register/{label}.json keep the request and answer | infra_down, exit 69, HTTP 503, retryable | tests/trajectories/linkedin-register-failures.mjs::a_submit_answer_that_cannot_be_read_is_named |
linkedin-submit-rate-limited | submitting the LinkedIn signup form ({label}) | LinkedIn answered HTTP 429: it limits signups from this exit or persona | POST {requestUrl} answered HTTP {status}; the page is at {pageUrl} | wait before the next registration from this exit, or use another dedicated ISP exit; the run's recordings under linkedin_register/{label}.json keep the request and answer | rate_limit, exit 69, HTTP 429, retryable | tests/trajectories/linkedin-register-failures.mjs::a_submit_answered_with_a_rate_limit_is_named |
linkedin-submit-refused | submitting the LinkedIn signup form ({label}) | LinkedIn refused the POST with HTTP {status}, a client error: it did not accept what the form sent | POST {requestUrl} answered HTTP {status}; the page is at {pageUrl} | the run's recordings under linkedin_register/{label}.json keep the request and answer | refused, exit 1, HTTP 403 | tests/trajectories/linkedin-register-failures.mjs::a_submit_refused_by_linkedin_is_named |
linkedin-submit-server-failed | submitting the LinkedIn signup form ({label}) | LinkedIn answered the POST with HTTP {status}, an error on its side | POST {requestUrl} answered HTTP {status}; the page is at {pageUrl} | run again later; the run's recordings under linkedin_register/{label}.json keep the request and answer | infra_down, exit 69, HTTP 503, retryable | tests/trajectories/linkedin-register-failures.mjs::a_submit_linkedin_fails_is_named |
linkedin-create-account-not-json | reading what LinkedIn answered createAccount | the answer is not JSON, where createAccount answers its submissionId or challengeUrl as JSON: LinkedIn changed the call, or a page stood in for it | POST {requestUrl} answered HTTP {status}; the page is at {pageUrl}; {bytes} characters; the parser said: {problem} | the run's recordings under linkedin_register/{label}.json keep the request and answer | refused, exit 1, HTTP 403 | tests/trajectories/linkedin-register-failures.mjs::a_create_account_answer_that_is_not_json_is_named |
linkedin-submit-diagnostics-unkept | keeping the diagnostics {label} of a failed LinkedIn signup submit | the submit failed ({submitCause}) and its diagnostics could not be written ({keptCause}) | the submit said: {submit}; writing said: {kept} | {keptFix}; then read the submit by its cause {submitCause} | infra_down, exit 69, HTTP 503, retryable | tests/trajectories/linkedin-register-failures.mjs::submit_diagnostics_that_cannot_be_kept_are_named |
linkedin-challenge-shown | reading the LinkedIn page at stage {stage} | LinkedIn shows a {signal} instead of the next step of the signup: it suspects the registration, and this trajectory never solves a challenge | page {url}; {inputs} visible inputs; challenge frames: {frames} | this exit and persona are spent for LinkedIn: run a new registration on another dedicated ISP exit; the run's recordings under linkedin_register hold the page | refused, exit 1, HTTP 403 | tests/trajectories/linkedin-register-failures.mjs::a_challenge_page_is_named |
linkedin-captcha-shown | submitting the email and password of the LinkedIn signup | LinkedIn answered the submit with a visible CAPTCHA, which this trajectory never solves | page {url}; a visible CAPTCHA frame or widget after the submit | this exit and persona are spent for LinkedIn: run a new registration on another dedicated ISP exit; the run's recordings under linkedin_register hold the page | refused, exit 1, HTTP 403 | tests/trajectories/linkedin-register-failures.mjs::a_captcha_after_the_submit_is_named |
linkedin-create-account-challenged | creating the LinkedIn account | LinkedIn answered createAccount with a challenge ({kind}) instead of the account | challenge {kind}, titled {title}, at {challengeUrl} | this exit and persona are spent for LinkedIn: run a new registration on another dedicated ISP exit; the run's recordings under linkedin_register hold the page | refused, exit 1, HTTP 403 | tests/trajectories/linkedin-register-failures.mjs::a_create_account_challenge_is_named |
linkedin-create-account-challenge-unread | creating the LinkedIn account | LinkedIn answered createAccount with a challenge, and its page could not be read to tell which: the page closed or did not load | challenge at {challengeUrl}; the page said: {problem} | this exit and persona are spent for LinkedIn: run a new registration on another dedicated ISP exit; the run's recordings under linkedin_register hold the page | refused, exit 1, HTTP 403 | tests/trajectories/linkedin-register-failures.mjs::a_create_account_challenge_that_cannot_be_read_is_named |
linkedin-signup-not-accepted | confirming the LinkedIn registration at stage {stage} | the page stayed on the signup form after the submit: LinkedIn rejected the registration without saying so, as it does on a low reCAPTCHA score | page {url} | this exit and persona are spent for LinkedIn: run a new registration on another dedicated ISP exit; the run's recordings under linkedin_register hold the page | refused, exit 1, HTTP 403 | tests/trajectories/linkedin-register-failures.mjs::a_registration_left_on_the_signup_form_is_named |
linkedin-verification-incomplete | confirming the LinkedIn registration at stage {stage} | the page is still a verification step ({route}) after the run answered it: LinkedIn did not take the mailed code | page {url} | read the mail the run answered in the account's Skrzynka mailbox, then run a new registration | refused, exit 1, HTTP 403 | tests/trajectories/linkedin-register-failures.mjs::a_verification_left_unfinished_is_named |
linkedin-session-unauthenticated | confirming the LinkedIn registration at stage {stage} | the browser holds no li_at cookie, so LinkedIn did not sign the new account in although the page moved on | page {url}; {cookies} linkedin.com cookies, none of them li_at | this exit and persona are spent for LinkedIn: run a new registration on another dedicated ISP exit; the run's recordings under linkedin_register hold the page | refused, exit 1, HTTP 403 | tests/trajectories/linkedin-register-failures.mjs::a_registration_without_a_session_is_named |
linkedin-form-state-repeated | reaching the LinkedIn signup form | the page came back to a state it already showed after {action}, so another nudge would loop: no email and password fields appear | page {url} ({pageKey}); {inputs} inputs, {buttons} buttons | run again; when it repeats, the run's recordings under linkedin_register (entry_path_diagnostics.json, the screenshots) show the page | refused, exit 1, HTTP 403 | tests/trajectories/linkedin-register-failures.mjs::a_signup_form_that_never_appears_is_named |
linkedin-form-no-transition | reaching the LinkedIn signup form | the page is on /signup without the email and password fields, and it shows no link that leads further | page {url} ({pageKey}) after {action}; {inputs} inputs, {buttons} buttons | run again; when it repeats, the run's recordings under linkedin_register (entry_path_diagnostics.json, the screenshots) show the page | refused, exit 1, HTTP 403 | tests/trajectories/linkedin-register-failures.mjs::a_signup_page_without_its_form_is_named |
linkedin-entry-no-signup-link | walking from the entry page {entry} to the LinkedIn signup form | the entry page shows no visible link to {signupPath}, so the run never reached the signup form | the page stopped at {url} | set LINKEDIN_REGISTER_ENTRY_URL to a page that links to {signupPath}, or leave it unset to open the signup page directly; the run's recordings under linkedin_register (entry_path_diagnostics.json, the screenshots) show the page | refused, exit 1, HTTP 403 | tests/trajectories/linkedin-register-failures.mjs::an_entry_page_without_a_signup_link_is_named |
linkedin-entry-no-arrival | walking from the entry page {entry} to the LinkedIn signup form | the signup link was clicked and the browser never arrived at the signup form | the page stopped at {url}; waiting said: {problem} | run again; when it repeats, the run's recordings under linkedin_register (entry_path_diagnostics.json, the screenshots) show the page | refused, exit 1, HTTP 403 | tests/trajectories/linkedin-register-failures.mjs::an_entry_link_that_leads_nowhere_is_named |
linkedin-account-unsaved | saving the new LinkedIn account {handle} after it signed in | the account exists on LinkedIn and its record could not be saved: {lower} | saving refused with {lowerCause}: {said}; its fix: {lowerFix} | {lowerFix}; the LinkedIn account {handle} exists without a vault record and its password was not kept, so recover it by LinkedIn's password reset to its mailbox once the vault takes writes | infra_down, exit 69, HTTP 503, retryable | tests/trajectories/linkedin-register-failures.mjs::an_account_that_cannot_be_saved_is_named |
linkedin-fingerprint-inconsistent | checking the early fingerprint of the LinkedIn registration browser | the browser shows {critical} critical and {warning} warning findings against the baseline, at or past the limit of {limit} warnings, so LinkedIn would see the inconsistency | findings {findings}; baseline {baseline}; the report is {report} | read the findings in {report}; raise WELES_EARLY_FP_WARNINGS only when they are known to be harmless, or set WELES_EARLY_FP=0 to register without the check | refused, exit 1, HTTP 403 | tests/trajectories/linkedin-register-failures.mjs::an_inconsistent_fingerprint_is_named |
linkedin-fingerprint-unchecked | checking the early fingerprint of the LinkedIn registration browser | there is no baseline directory {directory} to compare the early fingerprint with | WELES_BASELINE_DIR {stated}; the directory does not exist | set WELES_BASELINE_DIR to the directory of fingerprint baselines, or WELES_EARLY_FP=0 to register without the check | config, exit 1, HTTP 503 | tests/trajectories/linkedin-register-failures.mjs::a_fingerprint_check_without_baselines_is_named |
linkedin-verdict-unkept | keeping the verdict {signal} of the LinkedIn registration | the verdict could not be kept ({keptCause}), so the worker reads no signal for this run | keeping said: {kept}; the run itself ended with {runCause}: {run} | {keptFix}; then read the run itself by its cause {runCause} | infra_down, exit 69, HTTP 503, retryable | tests/trajectories/linkedin-register-failures.mjs::a_verdict_that_cannot_be_kept_is_named |
linkedin-entry-blank | reading the entry page LINKEDIN_REGISTER_ENTRY_URL names | LINKEDIN_REGISTER_ENTRY_URL is set and holds no address | LINKEDIN_REGISTER_ENTRY_URL of {length} characters, all blank; no browser was opened | set LINKEDIN_REGISTER_ENTRY_URL to a linkedin.com page that links to the signup form, or leave it unset to open https://www.linkedin.com/signup directly | refused, exit 2, HTTP 403 | tests/trajectories/linkedin-register-failures.mjs::a_blank_entry_page_is_refused |
linkedin-entry-not-url | reading the entry page LINKEDIN_REGISTER_ENTRY_URL names | the entry page is no absolute URL, so the browser cannot open it | LINKEDIN_REGISTER_ENTRY_URL {entry}; no browser was opened | set LINKEDIN_REGISTER_ENTRY_URL to a linkedin.com page that links to the signup form, or leave it unset to open https://www.linkedin.com/signup directly | refused, exit 2, HTTP 403 | tests/trajectories/linkedin-register-failures.mjs::an_entry_page_that_is_no_url_is_refused |
linkedin-entry-host-foreign | reading the entry page LINKEDIN_REGISTER_ENTRY_URL names | the entry page is on {host}, not linkedin.com, so the walk would not reach the LinkedIn signup form | LINKEDIN_REGISTER_ENTRY_URL {entry}; no browser was opened | set LINKEDIN_REGISTER_ENTRY_URL to a linkedin.com page that links to the signup form, or leave it unset to open https://www.linkedin.com/signup directly | refused, exit 2, HTTP 403 | tests/trajectories/linkedin-register-failures.mjs::an_entry_page_of_another_site_is_refused |
linkedin-entry-cold-join | reading the entry page LINKEDIN_REGISTER_ENTRY_URL names | the entry page carries trk=cold_join_sign_in, the sign-in page's join link, which LinkedIn reads as a cold signup | LINKEDIN_REGISTER_ENTRY_URL {entry}; no browser was opened | set LINKEDIN_REGISTER_ENTRY_URL to a linkedin.com page that links to the signup form, or leave it unset to open https://www.linkedin.com/signup directly | refused, exit 2, HTTP 403 | tests/trajectories/linkedin-register-failures.mjs::an_entry_page_of_the_cold_join_link_is_refused |
linkedin-warm-manifest-unread | reading the warm profile manifest {path} | the manifest exists and the system refused to read it ({errno}): it is not readable by this user, or it is a directory | the system said: {problem}; no browser was opened | give this user read access to {path} (`ls -l {path}`), or warm the profile again with trajectories/linkedin/recover/signup/warm_signup_profile.mjs, which writes the manifest, or point LINKEDIN_REGISTER_WARM_PROFILE_DIR at another warm profile | config, exit 1, HTTP 503 | tests/trajectories/linkedin-register-failures.mjs::a_warm_manifest_that_cannot_be_read_is_refused |
linkedin-warm-manifest-not-json | reading the warm profile manifest {path} | the manifest is not JSON: it was cut short while written, or something else wrote it | {bytes} characters; the parser said: {problem}; no browser was opened | warm the profile again with trajectories/linkedin/recover/signup/warm_signup_profile.mjs, which writes the manifest, or point LINKEDIN_REGISTER_WARM_PROFILE_DIR at another warm profile | config, exit 1, HTTP 503 | tests/trajectories/linkedin-register-failures.mjs::a_warm_manifest_that_is_not_json_is_refused |
linkedin-warm-manifest-schema-other | reading the warm profile manifest {path} | the manifest is not a {expected} manifest, so the proxy and persona the profile was warmed with cannot be replayed | schema {schema}; members {members}; no browser was opened | warm the profile again with trajectories/linkedin/recover/signup/warm_signup_profile.mjs, which writes the manifest, or point LINKEDIN_REGISTER_WARM_PROFILE_DIR at another warm profile | config, exit 1, HTTP 503 | tests/trajectories/linkedin-register-failures.mjs::a_warm_manifest_of_another_schema_is_refused |
run#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
apple-authorization-invalid | reading the authorization id of an Apple run | the authorization id the capabilities are bound to is no UUID (RFC 9562), so no capability can be checked against it | authorization id {authorization} | start the run with the authorization id `weles login --provider apple` answered | refused, exit 2, HTTP 400 | tests/runs/apple-capabilities-failures.mjs::an_authorization_that_is_no_uuid_is_refused |
apple-capabilities-not-json | reading {what} of the Apple run {authorization} | {what} is text that is not JSON | {length} characters, not shown (it holds capabilities); the parser refused it with a {kind}, whose words quote the text and are not shown | send the capabilities exactly as `weles login`, `weles agreement` or `weles certificate` issued them for this run | refused, exit 2, HTTP 400 | tests/runs/apple-capabilities-failures.mjs::capabilities_that_are_not_json_are_refused |
apple-capabilities-not-object | reading {what} of the Apple run {authorization} | {what} is {type}, not a JSON object | {what}: {type} | send the capabilities exactly as `weles login`, `weles agreement` or `weles certificate` issued them for this run | refused, exit 2, HTTP 400 | tests/runs/apple-capabilities-failures.mjs::capabilities_that_are_no_object_are_refused |
apple-capabilities-fields-other | reading {what} of the Apple run {authorization} | {what} must carry exactly {expected}, and carries other fields | fields: {found}; missing: {missing}; not taken: {extra} | send the capabilities exactly as `weles login`, `weles agreement` or `weles certificate` issued them for this run | refused, exit 2, HTTP 400 | tests/runs/apple-capabilities-failures.mjs::capabilities_with_other_fields_are_refused |
apple-two-factor-mode-other | reading {what} of the Apple run {authorization} | the two-factor source of an Apple run is one authorization-bound capability (mode capability), and it names another mode | two_factor.mode: {mode} | send the capabilities exactly as `weles login`, `weles agreement` or `weles certificate` issued them for this run | refused, exit 2, HTTP 400 | tests/runs/apple-capabilities-failures.mjs::a_two_factor_mode_other_than_capability_is_refused |
action-missing | reading the action of POST /run | the body names no action, so there is no trajectory to run | action: {value} | send "action": "<trajectory action>", one of the actions this worker serves | refused, exit 2, HTTP 400 | tests/runs/submitting/request-failures.mjs::a_run_without_an_action_is_refused |
creds-mode-unknown | reading creds of POST /run | creds is {mode}; it must be one of {modes} | creds: {mode} | send creds as one of {modes}, or leave it out to redact the run | refused, exit 2, HTTP 400 | tests/runs/submitting/request-failures.mjs::a_run_with_an_unknown_creds_mode_is_refused |
raw-creds-forbidden | answering POST /run with raw output | this deployment does not return a run verbatim: its output may hold a credential, and WELES_API_ALLOW_RAW_CREDS is not set to 1 | creds: raw | send creds redact to read the run without its secrets, or store to read the account items it saved | refused, exit 2, HTTP 403 | tests/runs/submitting/request-failures.mjs::a_raw_run_where_the_deployment_forbids_it_is_refused |
login-unnamed | reading the login of {action} | the run names its Skarbiec login by exactly one of login_role or login_item, a nonempty string, and takes no other key: {reason} | params: {keys} | send "login_role": "<role>" (the item tagged stado:role:<role>) or "login_item": "<item id>", and nothing else the action does not take | refused, exit 2, HTTP 400 | tests/runs/submitting/request-failures.mjs::a_google_run_naming_both_a_role_and_an_item_is_refused |
mailbox-unnamed | reading the mailbox of google_app_password | {reason} | params: {keys} | send "organization": "<Skrzynka organization>" and "expected_account": "<mailbox email>" beside the login | refused, exit 2, HTTP 400 | tests/runs/submitting/request-failures.mjs::an_app_password_run_without_its_organization_is_refused |
oauth-client-unnamed | reading the OAuth client and callback of google_oauth_callback | {reason} | params: {keys} | send exactly one of "client_role": "<role>" or "client_item": "<item id>" and "callback": "<https URL with a host>" beside the login | refused, exit 2, HTTP 400 | tests/runs/submitting/request-failures.mjs::an_oauth_callback_run_without_its_client_is_refused |
fresh-profile-without-account | reading fresh_profile of POST /run | a fresh browser profile is attached to an account, and the request names none, so it would sign in as nobody | fresh_profile: true; account_id: {account} | send "account_id" with "fresh_profile": true, or leave fresh_profile out | refused, exit 2, HTTP 400 | tests/runs/submitting/request-failures.mjs::a_fresh_profile_without_an_account_is_refused |
run-id-invalid | reading run_id of a detached POST /run | run_id is not a run id: letters, digits, ., _ and -, starting with a letter or digit | run_id: {value} | send run_id as such a name, or leave it out for one the worker makes | refused, exit 2, HTTP 400 | tests/runs/submitting/request-failures.mjs::a_detached_run_named_with_a_path_is_refused |
param-missing | reading params.{param} of a {action} run | params.{param} is not set, or blank, and {action} needs it: {what} | params.{param}: {found} | send params.{param} as {expected} | refused, exit 2, HTTP 400 | tests/runs/submitting/request-failures.mjs::a_run_without_a_parameter_its_action_needs_is_refused |
param-invalid | reading params.{param} of a {action} run | params.{param} is not {expected} | params.{param}: {found} | send params.{param} as {expected} | refused, exit 2, HTTP 400 | tests/runs/submitting/request-failures.mjs::a_run_with_a_parameter_of_another_shape_is_refused |
analytics-action-undeclared | reading the inputs of the analytics action {action} | no analytics-service catalog this worker reads declares {action}, so its inputs are unknown | declared analytics actions: {declared} | run one of the declared analytics actions, or declare {action} in its analytics-service catalog first | refused, exit 2, HTTP 400 | tests/runs/submitting/request-failures.mjs::an_analytics_action_no_catalog_declares_is_refused |
enrolment-unordered | admitting the authenticator enrolment of {login} | an enrolment signs in as the login, adds a sign-in method to the account (Google's security alert) and, from a browser profile Google has not seen, raises a prompt on the owner's phone, so it runs only on the vault owner's order: an order the owner makes in Skarbiec Desktop after Touch ID, signed by the order key its Secure Enclave keeps, which the run spends once in Skarbiec before anything opens; this request names no order; nothing was started | params.enrol_order: {found} | only the vault's owner orders an enrolment, in Skarbiec Desktop after Touch ID (https://skarbiec.wisent.com/docs/login/enrol-order); no automaton or agent orders one, and nothing in a request stands in for the order | refused, exit 2, HTTP 400 | tests/runs/submitting/request-failures.mjs::an_enrolment_no_order_names_is_refused |
action-unknown | running {action} | this release holds no trajectory for that action: it is misspelled, or it belongs to another release | action: {action} | name an action this release's src/worker/deploy/weles-action-allowlist.txt holds, or install the release that carries it | not_found, exit 1, HTTP 404 | tests/runs/answers/answer-failures.mjs::a_run_of_an_action_no_trajectory_answers_is_not_found |
run-failed | running {action} to save its accounts (run {run_id}) | the trajectory ended without success, so it saved no account; its exit status and the end of its error output say why | exit {exit} | read the run with `weles runs show {run_id}` for the step that failed, correct it and run again | refused, exit 1, HTTP 502 | tests/runs/answers/answer-failures.mjs::a_store_run_that_fails_names_its_run |
no-account-saved | reading the accounts run {run_id} of {action} saved | the run ended in success and saved no account item to the vault: the trajectory registers nothing in store mode, or the vault refused its write | run {run_id} saved no account | run {action} with creds redact for its outcome, and `weles runs show {run_id}` for what it wrote | refused, exit 1, HTTP 422 | tests/runs/answers/answer-failures.mjs::a_store_run_that_saves_no_account_is_refused |
builder-bootstrap-unset | starting the trajectory builder | WELES_BUILDER_BOOTSTRAP_URL is not set, and it is the neutral page the builder agent starts on | WELES_BUILDER_BOOTSTRAP_URL: not set | declare WELES_BUILDER_BOOTSTRAP_URL in the worker's environment (`stado service env com.wisent.weles`), then `stado service restart com.wisent.weles` | config, exit 1, HTTP 500 | tests/runs/submitting/builder-failures.mjs::a_builder_without_its_bootstrap_page_is_named |
builder-instructions-empty | reading the instructions of POST /weles-builder | the body states no goal: it is empty, or its instructions member is blank | instructions: empty | send the goal as the body, as text or as {"instructions": "<goal>"} | refused, exit 2, HTTP 400 | tests/runs/submitting/builder-failures.mjs::a_builder_request_without_a_goal_is_refused |
builder-trajectory-missing | starting the trajectory builder | this release holds no generic_browser_task trajectory, which the builder runs: the release was built without it | action: generic_browser_task | install a weles-worker release that carries generic_browser_task through Stado (`stado product install weles --surface service`) | config, exit 1, HTTP 500 | tests/runs/submitting/builder-failures.mjs::a_release_without_the_builder_trajectory_is_named |
identity#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
sms-country-unsupported | renting a {country} number for {service} | Weles knows no calling code of {country}, so no number of it can be written in international form | country {country}; supported: {supported}; no number was bought | ask one of {supported} | refused, exit 2, HTTP 403 | tests/identity/sms-failures.mjs::a_number_of_an_unsupported_country_is_refused |
sms-providers-unconfigured | renting a {country} number for {service} | the vault holds the API key of no SMS provider Weles rents from | juicySms/api_key absent; smsActivate/api_key absent; no provider was asked | store one key: juicySms/api_key or smsActivate/api_key, as the Weles service secret (`stado capabilities resolve secrets --consumer weles` names the vault) | config, exit 1, HTTP 503 | tests/identity/sms-failures.mjs::a_number_without_any_provider_key_is_refused |
sms-number-unavailable | renting a {country} number for {service} | no configured provider held a {country} number for {service} | {answers} | run again later, or top up the provider whose answer names its balance (NO_BALANCE); each provider's answer is named above | infra_down, exit 69, HTTP 503, retryable | tests/identity/sms-failures.mjs::a_number_no_provider_holds_is_named |
sms-order-provider-unknown | reading order {order} | the order names provider {provider}, which this Weles does not rent from: it was rented by another release | providers here: {providers} | release the order in {provider}'s dashboard and rent a new number | unknown, exit 1, HTTP 500 | tests/identity/sms-failures.mjs::an_order_of_an_unknown_provider_is_named |
sms-provider-unreachable | asking {provider} to {step} (order {order}) | no HTTP answer came back from {host} ({errno}): the provider is down, or this host cannot reach it | {problem}; nothing of the order changed on this side | run again once {host} answers (`curl -sI https://{host}/` shows whether it does) | infra_down, exit 69, HTTP 503, retryable | tests/identity/sms-failures.mjs::an_sms_provider_that_does_not_answer_is_named |
sms-provider-key-refused | asking {provider} to {step} (order {order}) | {host} refused the API key with HTTP {status}: the key was revoked, or the account is closed | HTTP {status}; {bytes} bytes, beginning {head} | store the provider's API key as the Weles service secret {secret} in the vault Stado names for Weles (`stado capabilities resolve secrets --consumer weles`), with a key the provider's dashboard shows as active | auth, exit 1, HTTP 401 | tests/identity/sms-failures.mjs::an_sms_key_the_provider_refuses_is_named |
sms-provider-unavailable | asking {provider} to {step} (order {order}) | {host} answered HTTP {status}: it is overloaded or failing on its side | HTTP {status}; {bytes} bytes, beginning {head} | run again later; the provider's status page says when it answers again | infra_down, exit 69, HTTP 503, retryable | tests/identity/sms-failures.mjs::an_sms_provider_failing_on_its_side_is_named |
sms-provider-refused | asking {provider} to {step} (order {order}) | {host} refused the request with HTTP {status}: its API changed the request it takes | HTTP {status}; {bytes} bytes, beginning {head} | compare the request in src/utils/identity/sms-providers/{provider}.ts with the provider's API documentation | unknown, exit 1, HTTP 500 | tests/identity/sms-failures.mjs::an_sms_request_the_provider_refuses_is_named |
sms-key-absent | asking {provider} to {step} (order {order}) | the vault holds no {secret}, so {provider} cannot be asked about an order rented with it | {secret}: absent; the order {order} is left as the provider holds it | store the provider's API key as the Weles service secret {secret} in the vault Stado names for Weles (`stado capabilities resolve secrets --consumer weles`) | config, exit 1, HTTP 503 | tests/identity/sms-failures.mjs::an_order_whose_provider_key_is_gone_is_named |
sms-answer-not-json | asking {provider} to {step} (order {order}) | {provider} answered a number as text that opens like JSON and is not JSON: its API changed the answer | {bytes} bytes; the parser said: {problem}; no number was taken | compare the answer read in src/utils/identity/sms-providers/{provider}.ts with the provider's API documentation | unknown, exit 1, HTTP 500 | tests/identity/sms-failures.mjs::a_number_answer_that_is_not_json_is_named |
sms-code-unreadable | asking {provider} to {step} (order {order}) | {provider} said the SMS arrived and handed over no digits: the service texted no code, or the provider's answer changed | {provider} answered {answer} | read the order in the provider's dashboard; ask a new number and run again | unknown, exit 1, HTTP 500 | tests/identity/sms-failures.mjs::a_code_without_digits_is_named |
sms-order-ended | asking {provider} to {step} (order {order}) | {provider} says order {order} will receive no SMS ({state}): it expired, was cancelled, or the service sent nothing | {provider} answered {answer} | rent a new number (the run asks again when started again); the order is not reused | refused, exit 1, HTTP 403 | tests/identity/sms-failures.mjs::an_order_that_will_receive_no_sms_is_named |
symbols-empty | generating an account password | the trajectory's rule for the provider's form names an empty alphabet of special characters, so no password can carry one | symbols: "" | name the special characters the provider form accepts in the trajectory (registrationPassword({ symbols })) | config, exit 1, HTTP 503 | tests/identity/password.mjs::a_rule_with_no_symbols_is_refused |
policy-unstated | reading the length of a generated account password | the Skarbiec vault policy states no min_generated_length, which is where that length is stated | the vault policy holds no min_generated_length | state it once with `skarbiec policy set min_generated_length <length>` | config, exit 1, HTTP 503 | tests/identity/password.mjs::a_vault_policy_without_a_length_is_named |
length-unstated | reading the length of a generated account password | the vault is {store}, which keeps no policy, so the length is WELES_GENERATED_PASSWORD_LENGTH, and it is not set | WELES_GENERATED_PASSWORD_LENGTH: not set | set WELES_GENERATED_PASSWORD_LENGTH to the length every generated account password takes, in the environment Weles runs with | config, exit 1, HTTP 503 | tests/identity/password.mjs::a_store_without_policy_and_no_length_is_named |
length-invalid | reading the length of a generated account password | WELES_GENERATED_PASSWORD_LENGTH is not a whole number above zero | WELES_GENERATED_PASSWORD_LENGTH: {value} | set WELES_GENERATED_PASSWORD_LENGTH to a whole number of characters, such as 24 | config, exit 1, HTTP 503 | tests/identity/password.mjs::a_length_that_is_not_a_number_is_named |
length-short | generating an account password | the stated length is shorter than the number of character classes a password carries one of each (capitals, small letters, digits, special characters) | {source} is {length}; a password carries {classes} classes | state a length of at least {classes} in {source} | config, exit 1, HTTP 503 | tests/identity/password.mjs::a_length_shorter_than_the_classes_is_refused |
captcha#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
captcha-callback-unset | giving {service} the address it pushes its answer to | {name} is {state}, so no provider can push its answer to this run ({meaning}) | {name} {state}; no task was created | start the run through the Weles worker, which states WELES_CAPTCHA_CALLBACK_URL and WELES_CAPTCHA_ANSWERS_DIR for every run (it needs WELES_CAPTCHA_CALLBACK_ORIGIN, its public HTTPS origin) | config, exit 1, HTTP 503 | tests/captcha/providers/provider-failures.mjs::a_solve_without_its_callback_is_refused |
captcha-callback-url-invalid | giving {service} the address it pushes its answer to | WELES_CAPTCHA_CALLBACK_URL is no absolute URL, so no address can be made from it | WELES_CAPTCHA_CALLBACK_URL of {length} characters, beginning {head}; no task was created | set WELES_CAPTCHA_CALLBACK_ORIGIN of the worker to its public HTTPS origin (the worker states the run's callback URL from it) | config, exit 1, HTTP 503 | tests/captcha/providers/provider-failures.mjs::a_callback_that_is_no_url_is_refused |
captcha-callback-unwatched | waiting for the answer of {service} in {directory} | the answers directory could not be {step} ({errno}), so the answer the worker writes cannot be seen | {path}: {problem} | give the worker's user read and write access to {directory} (WELES_CAPTCHA_ANSWERS_DIR), then run again | infra_down, exit 69, HTTP 503, retryable | tests/captcha/providers/provider-failures.mjs::an_answers_directory_that_cannot_be_used_is_named |
captcha-callback-answer-not-json | reading the answer of {service} the worker wrote to {path} | the file is not the JSON the worker writes ({contentType, body}): another release of the worker wrote it | {bytes} bytes; the parser said: {problem} | run the run on a worker of the same release as its trajectory (`weles worker version`) | unknown, exit 1, HTTP 500 | tests/captcha/providers/provider-failures.mjs::a_callback_answer_that_is_not_json_is_named |
captcha-provider-unreachable | asking {service} ({call}) | no HTTP answer came back from {host} ({errno}): the service is down or this host cannot reach it | {problem} | run again once {host} answers; the chain asks the next service whose key Weles holds meanwhile | infra_down, exit 69, HTTP 503, retryable | tests/captcha/providers/provider-failures.mjs::a_service_that_does_not_answer_is_named |
captcha-answer-not-json | reading what {service} answered ({call}) | the service answered HTTP {status} with a body that is not JSON: an error page of the service or of a proxy before it | {bytes} bytes as {kind}, beginning {head}; the parser said: {problem} | run again later; the service's status page says whether it is failing | infra_down, exit 69, HTTP 503, retryable | tests/captcha/providers/provider-failures.mjs::a_service_answer_that_is_not_json_is_named |
captcha-service-refused | asking {service} ({call}) | {service} refused {call}: {code} {why} | {service} answered {answer} | act on the service's error code ({code}): a key it refuses (ERROR_KEY_DOES_NOT_EXIST, ERROR_WRONG_USER_KEY) is replaced in the vault, a balance it lacks (ERROR_ZERO_BALANCE) is topped up in its dashboard, a domain it does not ping back to is registered there | refused, exit 1, HTTP 403 | tests/captcha/providers/provider-failures.mjs::a_task_the_service_refuses_is_named |
captcha-task-unnamed | creating a {type} task at {service} | the service took the request and answered no task or job id, so its answer cannot be waited for | answer members: {members}; HTTP {status} | compare the request in src/captcha/providers/ with the service's API documentation; the chain asks the next service meanwhile | unknown, exit 1, HTTP 500 | tests/captcha/providers/provider-failures.mjs::a_task_without_an_id_is_named |
captcha-task-failed | {service} solving task {task} ({type}) | the service took the task and then gave up on it ({code}): {description} | pushed errorId {error_id}, errorCode {code} | run again; a task that fails for the page (ERROR_CAPTCHA_UNSOLVABLE) is solved by another service of the chain, or by the page from another exit | refused, exit 1, HTTP 403 | tests/captcha/providers/provider-failures.mjs::a_task_the_service_gives_up_is_named |
captcha-task-unready | {service} solving task {task} ({type}) | the service pushed its answer with status {status}, not ready: it pushes only finished tasks, so its callback protocol changed | pushed members: {members} | compare the callback reading in src/captcha/providers/api.ts with the service's callback documentation; the run waits at the callback until it is cancelled (`weles runs cancel <run-id>`) | unknown, exit 1, HTTP 500 | tests/captcha/providers/provider-failures.mjs::a_task_pushed_unready_is_named |
captcha-solution-empty | {service} solving task {task} ({type}) | the service says the task is solved and its solution lacks {missing}, so nothing can be handed to the page | solution members: {members}; values are not shown (they unlock the page) | run again; when the service keeps answering so, compare the solution read in src/captcha/providers/ with the service's documentation | unknown, exit 1, HTTP 500 | tests/captcha/providers/provider-failures.mjs::a_solution_without_its_token_is_named |
captcha-pingback-unmatched | {service} solving task {task} ({type}) | the pingback carried {found}, not this task's id and its code | {kind} with fields {fields} | the pingback route answers each run its own address; the run waits at the callback until it is cancelled (`weles runs cancel <run-id>`) | unknown, exit 1, HTTP 500 | tests/captcha/providers/provider-failures.mjs::a_pingback_of_another_task_is_named |
captcha-unconfigured | solving {resource} | no captcha service solves {resource} with a key Weles holds | services with a key: {services}; none solves {resource} | store a key of a service that solves it (capsolver, anticaptcha, capmonster, twocaptcha, nopecha or nocaptcha) as the Weles service secret <service>/api_key in the vault Stado names for Weles (`stado capabilities resolve secrets --consumer weles`) | config, exit 1, HTTP 503 | tests/captcha/providers/provider-failures.mjs::a_challenge_no_held_key_solves_is_refused |
captcha-unsolved | solving {resource} | no captcha service solved {resource}: each service whose key Weles holds was asked and none answered a solution | {causes} | act on each service's cause above; the run's captcha_events.json records the attempt as all_failed | infra_down, exit 69, HTTP 503, retryable | tests/captcha/providers/provider-failures.mjs::a_challenge_no_service_solves_names_each |
captcha-cost-not-number | recording the price of the {provider} {resource} solve | the cost table priced it at {price}, which is not a number of dollars, and a solve is never recorded as free | price {price} ({type}) | correct the {provider} {resource} price in the captcha table of @wisent/cost-tracker, the package src/utils/runtime/cost.ts prices with, and release Weles with it | config, exit 1, HTTP 503 | tests/captcha/providers/provider-failures.mjs::a_solve_priced_at_no_number_is_refused |
captcha-proxy-url-invalid | naming the proxy of the {resource} task | the proxy is no URL, so no host, port or scheme can be handed to a service | proxy of {length} characters; the parser said: {problem}; no service was asked | state the run's proxy as scheme://host:port (with user and password together when it logs in); the services take it so | config, exit 1, HTTP 503 | tests/captcha/proxy-failures.mjs::a_proxy_that_is_no_url_is_refused |
captcha-proxy-port-absent | naming the proxy of the {resource} task | the proxy {host} names no port, and the services take a proxy as address and port, never one assumed for its scheme | proxy {scheme}://{host} without a port; no service was asked | state the run's proxy as scheme://host:port (with user and password together when it logs in); the services take it so | config, exit 1, HTTP 503 | tests/captcha/proxy-failures.mjs::a_proxy_without_a_port_is_refused |
captcha-proxy-unresolved | naming the proxy of the {resource} task | the proxy host {host} did not resolve ({errno}), and the services take the proxy by IP address | {problem}; no service was asked | `dig {host}` shows what the resolver answers; state the proxy by an address that resolves, then run again | infra_down, exit 69, HTTP 503, retryable | tests/captcha/proxy-failures.mjs::a_proxy_whose_host_does_not_resolve_is_refused |
captcha-proxy-password-absent | naming the proxy of the {resource} task | the proxy {host} names the user {user} and no password, and {service} takes the proxy login as user and password together | user {user}; password not given; no service was asked | state the run's proxy as scheme://host:port (with user and password together when it logs in); the services take it so | config, exit 1, HTTP 503 | tests/captcha/proxy-failures.mjs::a_proxy_user_without_a_password_is_refused |
captcha-page-unstated | solving a reCAPTCHA v2 token | neither options.url nor the page states the address the token is solved for, and the services solve for a page address | options.url {url}; page.url() answered {page} | pass the address of the page that shows the challenge as options.url | unknown, exit 1, HTTP 500 | tests/captcha/recaptcha-failures.mjs::a_token_solve_without_a_page_address_is_refused |
recaptcha-image-unloaded | solving the reCAPTCHA image grid on {page} | a tile image of the challenge finished loading with no picture: Google refused it to this exit, or it was cut off | image {image} has no natural width | run again; when the images keep failing from this exit, run from another proxy exit. the run's recordings (vision/captcha_challenge_page.png, extracted_grid_latest.png) show the challenge | infra_down, exit 69, HTTP 503, retryable | tests/captcha/recaptcha-failures.mjs::a_tile_image_that_did_not_load_is_named |
recaptcha-instruction-empty | solving the reCAPTCHA image grid on {page} | the visible image challenge states no instruction, so nothing says which tiles to choose | instruction text {instruction}; grid {grid} | the run's recordings (vision/captcha_challenge_page.png, extracted_grid_latest.png) show the challenge | unknown, exit 1, HTTP 500 | tests/captcha/recaptcha-failures.mjs::a_challenge_without_an_instruction_is_named |
recaptcha-grid-unseen | solving the reCAPTCHA image grid on {page} | the challenge frame holds no grid element, so no image can be taken of it: reCAPTCHA changed its markup | no element matches {selector} in the frame {frame} | the run's recordings (vision/captcha_challenge_page.png, extracted_grid_latest.png) show the challenge; correct GRID_SELECTOR in src/captcha/grid/classify_grid.ts to the grid it shows | unknown, exit 1, HTTP 500 | tests/captcha/recaptcha-failures.mjs::a_challenge_frame_without_its_grid_is_named |
recaptcha-tiles-unnamed | solving the reCAPTCHA image grid on {page} | the vision model answered no list of tile numbers | the model answered {length} characters, beginning {head} | run again; `stado capabilities resolve inference --consumer weles` shows which model Weles asks | unknown, exit 1, HTTP 500 | tests/captcha/recaptcha-failures.mjs::a_model_that_names_no_tiles_is_named |
recaptcha-tiles-invalid | solving the reCAPTCHA image grid on {page} | the vision model listed tiles that are not whole tile numbers of the grid | listed {listed}; the grid has tiles 0 to {last} | run again; `stado capabilities resolve inference --consumer weles` shows which model Weles asks | unknown, exit 1, HTTP 500 | tests/captcha/recaptcha-failures.mjs::a_model_that_names_impossible_tiles_is_named |
recaptcha-tile-unclicked | solving the reCAPTCHA image grid on {page} | tile {tile} of the {count} chosen could not be clicked: the grid was replaced or covered while it was being solved | tiles chosen {tiles}; clicked before it {clicked}; Playwright said: {problem} | run again; the run's recordings (vision/captcha_challenge_page.png, extracted_grid_latest.png) show the challenge | unknown, exit 1, HTTP 500 | tests/captcha/recaptcha-failures.mjs::a_tile_that_cannot_be_clicked_is_named |
recaptcha-verify-disabled | solving the reCAPTCHA image grid on {page} | reCAPTCHA kept its verify button disabled after the tiles were chosen: it wants more tiles, or new tiles loaded | tiles clicked {tiles}; #recaptcha-verify-button visible and disabled | run again; the run's recordings (vision/captcha_challenge_page.png, extracted_grid_latest.png) show the challenge | refused, exit 1, HTTP 403 | tests/captcha/recaptcha-failures.mjs::a_verify_button_left_disabled_is_named |
recaptcha-outcome-unobserved | solving the reCAPTCHA image grid on {page} | the challenge frame disappeared before a checked anchor was seen, so whether the grid passed is not known | tiles clicked {tiles}; no challenge frame and no checked anchor on {page} | read the page state before submitting; the run's recordings (vision/captcha_challenge_page.png, extracted_grid_latest.png) show the challenge | unknown, exit 1, HTTP 500 | tests/captcha/recaptcha-failures.mjs::a_challenge_that_vanishes_unchecked_is_named |
recaptcha-rejected | solving the reCAPTCHA image grid on {page} | reCAPTCHA rejected the tiles chosen ({state}); the solve is not repeated, since a retried image flags the session | tiles clicked {tiles}; reCAPTCHA said {said} | start a new run, from another exit when it keeps being rejected; the run's recordings (vision/captcha_challenge_page.png, extracted_grid_latest.png) show the challenge | refused, exit 1, HTTP 403 | tests/captcha/recaptcha-failures.mjs::a_grid_recaptcha_rejects_is_named |
secrets#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
managed-path-relative | reading the managed password declarations WELES_MANAGED_PASSWORD_CONTRACTS_FILE names ({path}) | WELES_MANAGED_PASSWORD_CONTRACTS_FILE names a relative path, which would be read against whatever directory the worker started in | WELES_MANAGED_PASSWORD_CONTRACTS_FILE: {path}; nothing was read | set WELES_MANAGED_PASSWORD_CONTRACTS_FILE to the absolute path of the declarations file | config, exit 1, HTTP 503 | tests/secrets/skarbiec_vault/managed-failures.mjs::a_relative_declarations_path_is_refused |
managed-file-unread | reading the managed password declarations WELES_MANAGED_PASSWORD_CONTRACTS_FILE names ({path}) | the file could not be {step} ({errno}): it is absent, or the worker user may not read it | {problem} | place the declarations at {path} readable by the worker user, or unset WELES_MANAGED_PASSWORD_CONTRACTS_FILE | config, exit 1, HTTP 503 | tests/secrets/skarbiec_vault/managed-failures.mjs::a_declarations_file_that_is_absent_is_named |
managed-file-unfit | reading the managed password declarations WELES_MANAGED_PASSWORD_CONTRACTS_FILE names ({path}) | the declarations decide which credentials Weles writes, so only a regular owner-only file of the worker user is read, and this one is not: {why} | {state}; worker uid {uid} | make {path} a regular file owned by the worker user with mode 0600 (`chmod 600 {path}`) | config, exit 1, HTTP 503 | tests/secrets/skarbiec_vault/managed-failures.mjs::a_declarations_file_others_may_read_is_refused |
managed-file-not-json | reading the managed password declarations WELES_MANAGED_PASSWORD_CONTRACTS_FILE names ({path}) | the file is not JSON | {bytes} bytes; the parser said: {problem} | write {path} as {"schema": "weles.managed-passwords.v1", "accounts": [{"item": "<item or role:<role>>", "provider": "microsoft|microsoft_entra|parp"}]} | config, exit 1, HTTP 503 | tests/secrets/skarbiec_vault/managed-failures.mjs::a_declarations_file_that_is_not_json_is_refused |
managed-file-shape | reading the managed password declarations WELES_MANAGED_PASSWORD_CONTRACTS_FILE names ({path}) | the file is JSON without schema weles.managed-passwords.v1 and an accounts list, or with members of no such file | members: {members}; schema {schema}; accounts {accounts} | write {path} as {"schema": "weles.managed-passwords.v1", "accounts": [{"item": "<item or role:<role>>", "provider": "microsoft|microsoft_entra|parp"}]} | config, exit 1, HTTP 503 | tests/secrets/skarbiec_vault/managed-failures.mjs::a_declarations_file_of_another_shape_is_refused |
managed-account-invalid | reading the managed password declarations WELES_MANAGED_PASSWORD_CONTRACTS_FILE names ({path}) | account {index} is no exact item or role:<role> (1-128 ASCII letters, digits, dots, underscores or hyphens, starting with a letter or digit) with provider microsoft, microsoft_entra or parp | account {index}: {found} | correct account {index} of {path} | config, exit 1, HTTP 503 | tests/secrets/skarbiec_vault/managed-failures.mjs::a_declared_account_of_another_shape_is_refused |
managed-item-repeated | reading the managed password declarations WELES_MANAGED_PASSWORD_CONTRACTS_FILE names ({path}) | the item {item} is declared twice, so which provider writes it is not one answer | accounts {first} and {index} both name {item} | keep one declaration of {item} in {path} | config, exit 1, HTTP 503 | tests/secrets/skarbiec_vault/managed-failures.mjs::an_item_declared_twice_is_refused |
managed-item-taken | resolving the acquisition contract of {secret} | the declarations name {item}, which this release's own {owner} already holds, and a declaration cannot replace a contract the release carries | {path} declares {item} as a managed password; nothing was written or read | remove {item} from {path}, or declare the password under an item of its own | config, exit 1, HTTP 503 | tests/secrets/skarbiec_vault/managed-failures.mjs::a_declared_item_the_release_holds_is_refused |
capability-literal-environment | checking a literal the model gave for a field | the literal names an environment variable ({reference}), and model input never reads the environment | {length} characters, naming {reference} | pass the value itself, or fill the field with fill_credential and a capability reference | refused, exit 2, HTTP 403 | tests/secrets/scoping/capability-failures.mjs::a_literal_naming_an_environment_variable_is_refused |
capability-literal-redeemed | checking a literal the model gave for a field | [credential_literal] the literal equals secret material a capability redeemed in this process, so typing it would put the secret where no capability bounds it | {length} characters; its SHA-256 is among the {redeemed} digests this process redeemed; the value is not shown | fill credential fields with fill_credential and a capability reference, never with a literal | refused, exit 2, HTTP 403 | tests/secrets/scoping/capability-failures.mjs::a_literal_equal_to_redeemed_material_is_refused |
capability-reference-invalid | checking the capability handed for {purpose} on {resource} | invalid capability reference: {problem} | members {members}; target {target} | pass the capability object exactly as it was handed to the run: capability_id (64 hex), purpose, resource, target "weles", and an optional authorization_id (a UUID) | refused, exit 2, HTTP 403 | tests/secrets/scoping/capability-failures.mjs::a_reference_of_another_shape_is_refused |
capability-operation-mismatch | checking the capability handed for {purpose} on {resource} | the capability was minted for {given}, and this operation redeems one for {expected} | given {given}; expected {expected} | pass the capability the run was handed for this operation; a capability is bound to one purpose, resource and authorization | refused, exit 2, HTTP 403 | tests/secrets/scoping/capability-failures.mjs::a_reference_for_another_operation_is_refused |
capability-provider-resource-invalid | checking the {purpose} capability handed to a provider call | its resource {resource} names no provider: a provider capability's resource is provider:<name> | resource {resource} | pass the provider capability the run was handed, whose resource is provider:<name> | refused, exit 2, HTTP 403 | tests/secrets/scoping/capability-failures.mjs::a_provider_capability_naming_no_provider_is_refused |
capability-setting-invalid | reading the capability broker setting {name} | {name} is {state}, and the broker cannot be asked without it | {name}: {state} | set {name} in the worker's service unit as Stado declares it for this workload, with no surrounding spaces | config, exit 1, HTTP 503 | tests/secrets/scoping/capability-failures.mjs::a_broker_setting_that_is_unset_is_refused |
capability-path-relative | reading the capability broker setting {name} | {name} is a relative path, which would be read from whatever directory the run started in | {name} of {length} characters, not starting with / | set {name} in the worker's service unit as Stado declares it for this workload, with no surrounding spaces | config, exit 1, HTTP 503 | tests/secrets/scoping/capability-failures.mjs::a_relative_broker_path_is_refused |
capability-id-invalid | asking the capability broker to {operation} a capability | the {what} is not {shape}, so the broker would refuse it unread | {what} of {length} characters | pass the capability exactly as it was handed to the run | refused, exit 1, HTTP 403 | tests/secrets/scoping/capability-failures.mjs::a_capability_id_of_another_shape_is_refused |
capability-workload-id-invalid | signing the workload proof to {operation} capability {capability} | SKARBIEC_WORKLOAD_ID is {problem}, which the proof cannot carry | SKARBIEC_WORKLOAD_ID of {length} characters (at most 128, no NUL, CR or LF) | set SKARBIEC_WORKLOAD_ID in the worker's service unit as Stado declares it for this workload, with no surrounding spaces | config, exit 1, HTTP 503 | tests/secrets/scoping/capability-failures.mjs::a_workload_id_the_proof_cannot_carry_is_refused |
capability-key-unreadable | reading the workload signing key {path} | the key cannot be read ({errno}): it does not exist, or the worker's user may not read it | {path}: {problem} | point SKARBIEC_WORKLOAD_SIGNING_KEY_FILE at this workload's Ed25519 key, an absolute path to a regular file owned by the worker's user with mode 600, as its service unit declares it | config, exit 1, HTTP 503 | tests/secrets/scoping/capability-failures.mjs::a_signing_key_that_does_not_exist_is_named |
capability-key-unsafe | reading the workload signing key {path} | the key is {problem}, and a key another user can read proves nothing of this workload | {path}: {kind}, mode {mode}, owner uid {owner}; this process runs as uid {uid} | point SKARBIEC_WORKLOAD_SIGNING_KEY_FILE at this workload's Ed25519 key, an absolute path to a regular file owned by the worker's user with mode 600, as its service unit declares it | config, exit 1, HTTP 503 | tests/secrets/scoping/capability-failures.mjs::a_signing_key_others_may_read_is_refused |
capability-key-unusable | signing the workload proof with {path} | the file holds no private key this process can sign with ({problem}), or one that is not Ed25519 (its proof is {proof} characters, an Ed25519 one 86) | {path}: {bytes} bytes; the key is not shown | point SKARBIEC_WORKLOAD_SIGNING_KEY_FILE at this workload's Ed25519 key, an absolute path to a regular file owned by the worker's user with mode 600, as its service unit declares it | config, exit 1, HTTP 503 | tests/secrets/scoping/capability-failures.mjs::a_signing_key_that_is_no_key_is_refused |
capability-broker-unreachable | asking the capability broker at {socket} to {operation} capability {capability} | the broker's socket did not take the request ({errno}): no broker listens at that path, or the worker's user may not open it | {socket}: {problem} | `stado service status skarbiec` shows whether the broker runs; SKARBIEC_CAP_SOCKET must be the socket it listens on | infra_down, exit 69, HTTP 503, retryable | tests/secrets/scoping/capability-failures.mjs::a_broker_socket_nothing_listens_on_is_named |
capability-answer-truncated | reading the broker's answer to {operation} capability {capability} at {socket} | the broker closed the socket before it ended its answer, so what it sent is not read | {bytes} bytes received before the socket closed | ask again; `stado service logs skarbiec` shows why the broker closed it | infra_down, exit 69, HTTP 503, retryable | tests/secrets/scoping/capability-failures.mjs::a_broker_answer_cut_off_is_named |
capability-control-line-invalid | reading the broker's answer to {operation} capability {capability} at {socket} | the answer does not begin with one control line ended by LF | {bytes} bytes; first LF at {newline} | compare the broker's Skarbiec release with the protocol this workload speaks (skarbiec.redeem.v1: one JSON control line, then the secret_len bytes it names); `stado service logs skarbiec` shows its side | config, exit 1, HTTP 503 | tests/secrets/scoping/capability-failures.mjs::capability_control_line_invalid_is_named |
capability-control-not-json | reading the broker's answer to {operation} capability {capability} at {socket} | the control line is not JSON | control line of {length} bytes; the parser said: {problem} | compare the broker's Skarbiec release with the protocol this workload speaks (skarbiec.redeem.v1: one JSON control line, then the secret_len bytes it names); `stado service logs skarbiec` shows its side | config, exit 1, HTTP 503 | tests/secrets/scoping/capability-failures.mjs::capability_control_not_json_is_named |
capability-control-not-object | reading the broker's answer to {operation} capability {capability} at {socket} | the control line is JSON but not an object | the control line is {kind} | compare the broker's Skarbiec release with the protocol this workload speaks (skarbiec.redeem.v1: one JSON control line, then the secret_len bytes it names); `stado service logs skarbiec` shows its side | config, exit 1, HTTP 503 | tests/secrets/scoping/capability-failures.mjs::capability_control_not_object_is_named |
capability-version-other | reading the broker's answer to {operation} capability {capability} at {socket} | the control line speaks {version}, not skarbiec.redeem.v1 | version {version} | compare the broker's Skarbiec release with the protocol this workload speaks (skarbiec.redeem.v1: one JSON control line, then the secret_len bytes it names); `stado service logs skarbiec` shows its side | config, exit 1, HTTP 503 | tests/secrets/scoping/capability-failures.mjs::capability_version_other_is_named |
capability-status-framing-invalid | reading the broker's answer to {operation} capability {capability} at {socket} | a {status} answer carries more than version and status, or a body | members {members}; body of {bytes} bytes | compare the broker's Skarbiec release with the protocol this workload speaks (skarbiec.redeem.v1: one JSON control line, then the secret_len bytes it names); `stado service logs skarbiec` shows its side | config, exit 1, HTTP 503 | tests/secrets/scoping/capability-failures.mjs::capability_status_framing_invalid_is_named |
capability-status-unknown | reading the broker's answer to {operation} capability {capability} at {socket} | the control line's status is {status}, none of ok, denied or pending | status {status} | compare the broker's Skarbiec release with the protocol this workload speaks (skarbiec.redeem.v1: one JSON control line, then the secret_len bytes it names); `stado service logs skarbiec` shows its side | config, exit 1, HTTP 503 | tests/secrets/scoping/capability-failures.mjs::capability_status_unknown_is_named |
capability-success-framing-invalid | reading the broker's answer to {operation} capability {capability} at {socket} | an ok answer carries other members, or a secret_len that is not the length of its body | members {members}; secret_len {declared}; body of {bytes} bytes | compare the broker's Skarbiec release with the protocol this workload speaks (skarbiec.redeem.v1: one JSON control line, then the secret_len bytes it names); `stado service logs skarbiec` shows its side | config, exit 1, HTTP 503 | tests/secrets/scoping/capability-failures.mjs::capability_success_framing_invalid_is_named |
capability-cancel-material | reading the broker's answer to {operation} capability {capability} at {socket} | the broker answered a cancellation with secret material, which a cancellation never returns | body of {bytes} bytes, wiped unread | compare the broker's Skarbiec release with the protocol this workload speaks (skarbiec.redeem.v1: one JSON control line, then the secret_len bytes it names); `stado service logs skarbiec` shows its side | config, exit 1, HTTP 503 | tests/secrets/scoping/capability-failures.mjs::capability_cancel_material_is_named |
capability-denied | asking the capability broker at {socket} to {operation} capability {capability} | the broker refused to {operation} capability {capability}: capability denied; no retry is permitted (it was spent, expired, cancelled, or minted for another workload) | status denied; {refusal} | read why with the command the observed state names, then ask whoever handed the run the capability for a new one; a denied capability is never redeemed | refused, exit 1, HTTP 403 | tests/secrets/scoping/capability-failures.mjs::a_capability_the_broker_denies_is_named |
capability-pending | asking the capability broker at {socket} to {operation} capability {capability} | capability material is pending: the broker holds the capability and its material is not ready yet (an approval or code still awaited) | status pending | ask again once the material is ready; the run waiting on it retries | timeout, exit 69, HTTP 504, retryable | tests/secrets/scoping/capability-failures.mjs::a_capability_still_pending_is_named |
alternative-unanswered | finding the secrets alternative Stado names for Weles | no alternative of the secrets family answers Weles: the Skarbiec service of this host is stopped or absent from the service directory, and no other alternative is configured | {answer} | run `stado capabilities resolve secrets --consumer weles`, which names each alternative and why it did not answer; start the one it names with `stado service ensure skarbiec --host <this host> --reason <why>` | infra_down, exit 69, HTTP 503, retryable | tests/secrets/alternative-failures.mjs::a_host_where_no_secrets_alternative_answers_names_each |
alternative-invalid | reading the secrets alternative Stado names for Weles | Stado answered an alternative with no provider, or with a route Weles may not use: for Skarbiec not an http(s) origin, carrying credentials, a query, a fragment or a path, or plain HTTP to an address that is not this machine's loopback; for the owner file not a file:// URL of an absolute path; an older Stado, or something writing to its standard output | Stado answered {answer} | upgrade Stado to the version this Weles release names as minimum_stado_version, then run `stado capabilities resolve secrets --consumer weles` again | config, exit 1, HTTP 503 | tests/secrets/alternative-failures.mjs::an_alternative_without_a_route_is_named |
provider-unsupported | {need} through the {provider} alternative at {route} | it needs Skarbiec's operator API, and Stado handed out another provider first: the owner-only credential file answers item reads and writes only (no scoped grants, acquisitions or worker vault endpoint), and a provider this Weles does not know answers nothing it speaks | Stado named {provider} ({kind}) at {route}; the order is {order} | make Skarbiec answer Weles on this host (`stado capabilities resolve secrets --consumer weles` names why it did not), so it is handed out first | config, exit 1, HTTP 503 | tests/secrets/alternative-failures.mjs::an_alternative_that_is_not_skarbiec_is_refused |
vault-unreachable | skarbiec {command} through {route} | nothing answered at the route Stado proved, even after Stado proved the alternative again: Skarbiec stopped between the proof and the request, or Stado then handed out another store, which holds other items, so the request is not sent there | {answer} | start Skarbiec with `stado service ensure skarbiec --host <this host> --reason <why>`, then run again | infra_down, exit 69, HTTP 503, retryable | tests/secrets/alternative-failures.mjs::a_route_nothing_answers_on_is_named |
vault-refused | skarbiec {command} through {route} | Skarbiec answered and refused; its own words name the vault, the item or the key it could not use | HTTP {status}: {answer} | run `stado credentials doctor` for whether a key on this machine opens the vault, correct what Skarbiec names, then run again | refused, exit 1, HTTP 403 | tests/secrets/alternative-failures.mjs::a_skarbiec_that_refuses_is_quoted |
vault-answer-invalid | reading what skarbiec {command} answered through {route} | it answered success without the JSON {expected}: what answers at the route is not Skarbiec, or an older Skarbiec | {answer} | run `stado capabilities resolve secrets --consumer weles` for what answers there, and install Skarbiec through Stado (`stado product install skarbiec --surface service`) | config, exit 1, HTTP 503 | tests/secrets/alternative-failures.mjs::a_skarbiec_answer_that_is_not_json_is_named |
bridge-missing | starting the request thread {path} | the file a vault request runs in is absent from this install: it was built or copied without it | {path} does not exist | install the Weles release again through Stado (`stado product install weles --surface cli`) | config, exit 1, HTTP 503 | tests/secrets/alternative-failures.mjs::an_install_without_its_request_thread_is_named |
owner-file-read-refused | `stado {command}` reading the owner-file credential store {route} | Stado refused to read the store in its own words below: the file was removed since Stado named it, this user may not read it, others may read or write it (Stado refuses a store that is not owner-only), or it is not the JSON object of items Stado keeps | exit {status}: {answer} | correct what Stado names (`stado credentials ls` reads the store, `stado config get credentials.store` names its file), restore the file from its backup or point credentials.store at the right one (`stado config set credentials.store file://<path>`) | config, exit 1, HTTP 503 | tests/secrets/owner-file.mjs::a_store_stado_refuses_to_read_quotes_stado |
owner-file-item-absent | {command} item {id} in the owner-file credential store {route} | the store holds no item of that id: it was never written there, it was removed, or it lives in another store (a Skarbiec vault the host used before) | Stado said: {answer} | `stado credentials ls` lists the items the store holds; write the item with `stado credentials put {id}` or name one it holds | not_found, exit 1, HTTP 404 | tests/secrets/owner-file.mjs::an_item_the_store_lacks_is_named |
owner-file-item-not-document | reading item {id} of the owner-file credential store {route} as a JSON document | the item holds text that is not JSON: it was written with `stado credentials put` by another program, or by hand, as a bare value, while Weles keeps every record as one JSON document | {answer} | write the item again as the JSON document Weles keeps (`stado credentials get {id}` shows what it holds now), or remove it with `stado credentials rm {id}` when it is not a Weles record | config, exit 1, HTTP 503 | tests/secrets/owner-file.mjs::an_item_holding_bare_text_is_named |
owner-file-tags-malformed | reading the tags of item {id} in the owner-file credential store {route} | the store has no tags, so Weles keeps an item's tags in its document's `tags` member as a list of text; this item's member is something else, written by hand or by another program | tags: {tags} | write the item again with `tags` a list of text, or without the member (`stado credentials get {id}` shows it, `stado credentials put {id}` replaces it) | config, exit 1, HTTP 503 | tests/secrets/owner-file.mjs::an_item_whose_tags_are_not_text_is_named |
owner-file-item-changed | replacing item {id} in the owner-file credential store {route} only if it is unchanged | the item no longer holds the document the caller read: another writer changed it in between, so writing would lose that change | item {id} differs from what was read | read item {id} again (`stado credentials get {id}`) and run the operation again against what it holds now | refused, exit 1, HTTP 403 | tests/secrets/owner-file.mjs::an_item_changed_since_it_was_read_is_not_replaced |
owner-file-write-refused | `stado {command}` on the owner-file credential store {route} | Stado refused the write in its own words below: the store's directory does not let this user replace the file, the item id is one Stado reserves, or STADO_CREDENTIALS_STORE names another store than credentials.store (a migration not finished) | exit {status}: {answer} | correct what Stado names (`stado credentials ls` reads the store, `stado credentials migrate` finishes a migration), then run again | refused, exit 1, HTTP 403 | tests/secrets/owner-file.mjs::a_write_stado_refuses_quotes_stado |
identity-undeclared | finding the consumer and bearer Weles presents to the Skarbiec at {route} | Skarbiec serves its routes only to a caller that proves who it is, and Stado declares no grant of the Skarbiec consumer weles with one bearer file by its absolute path for Weles on the skarbiec service: none is declared, several name different files, or the file is not named by an absolute path | `stado capabilities resolve secrets --consumer weles` declares: {found} | declare where Weles's bearer is kept with `stado service directory consumer add skarbiec weles --grant-consumer weles --grant <one capability of it, action:item#field> --token-file <absolute owner-only file>`; `stado capabilities resolve secrets --consumer weles` then lists it under grants; the grant itself is issued on the vault owner (`skarbiec grant issue weles --capabilities <...> --token-file <that file> --until-revoked`) | config, exit 1, HTTP 503 | tests/secrets/skarbiec_vault/skarbiec-identity.mjs::a_skarbiec_with_no_grant_declared_for_weles_is_named |
bearer-unsafe | reading Weles's Skarbiec bearer from {file} | the file Stado declares for the bearer cannot be trusted or read: it is absent (the grant's file was declared and no bearer written into it), a link, not a regular file, another account's, or open to its group or others | {file} {reason} | write a new random bearer alone on one line into {file} with mode 0600, then register it on the vault owner: `skarbiec grant issue weles --capabilities <its capabilities> --replace-capabilities --token-file {file} --until-revoked`; a file others could read held a bearer another account may have, which the new one replaces on the vault owner | config, exit 1, HTTP 503 | tests/secrets/skarbiec_vault/skarbiec-identity.mjs::a_bearer_file_open_to_others_is_refused_and_nothing_is_sent |
bearer-malformed | reading Weles's Skarbiec bearer from {file} | the file holds {held}, and a bearer is one word on one line, read as Skarbiec reads a --token-file: trailing line breaks off and nothing else trimmed | {file} holds {held} | write a new random bearer alone on one line into {file} with mode 0600, then register it on the vault owner: `skarbiec grant issue weles --capabilities <its capabilities> --replace-capabilities --token-file {file} --until-revoked` | config, exit 1, HTTP 503 | tests/secrets/skarbiec_vault/skarbiec-identity.mjs::a_bearer_file_holding_whitespace_inside_is_refused |
scoped-service-undeclared | using the scoped credentials of service {service} | the service catalog src/secrets/scoped-service/catalog/services.json declares no service of that name: the trajectory names a service this release does not carry | the catalog declares {count} services | install the Weles release whose catalog declares {service} (`stado product install weles --surface cli`), or declare it in src/secrets/scoped-service/catalog/services.json | config, exit 1, HTTP 503 | tests/secrets/scoping/scoped.mjs::a_service_the_catalog_lacks_is_named |
scoped-service-not-login | using service {service} as a login | the catalog declares the service without both a username and a password field, so it holds no login to sign in with | its fields are {fields} | declare username and password fields for {service} in src/secrets/scoped-service/catalog/services.json, or read its fields one by one | config, exit 1, HTTP 503 | tests/secrets/scoping/scoped.mjs::a_service_without_a_login_is_refused_as_one |
scoped-field-undeclared | reading field {field} of service {service} | the catalog does not declare that field for the service, so no grant or item carries it | its fields are {fields} | read one of the declared fields, or declare {field} for {service} in src/secrets/scoped-service/catalog/services.json | config, exit 1, HTTP 503 | tests/secrets/scoping/scoped.mjs::a_field_the_service_lacks_is_named |
scoped-field-absent | reading field {field} of service {service} from item {item} of the {provider} store {route} | the store holds no non-empty value of that field: the credential was never written there, or it was written without it | item {item} holds no {field} | write the credential with the trajectory that acquires it, or with `stado credentials put --role {item} --type <kind>`, then run again | not_found, exit 1, HTTP 404 | tests/secrets/scoping/scoped.mjs::a_field_the_store_lacks_is_named |
scoped-identity-absent | reading field {field} of service {service} from Skarbiec | the vault is a Skarbiec, which hands a field out only to a workload identity, and this process carries none | SKARBIEC_WORKLOAD_ID or SKARBIEC_WORKLOAD_SIGNING_KEY_FILE is not set | run the trajectory as the Weles worker does, with SKARBIEC_WORKLOAD_ID and SKARBIEC_WORKLOAD_SIGNING_KEY_FILE naming its workload identity | config, exit 1, HTTP 503 | tests/secrets/scoping/scoped.mjs::a_skarbiec_read_without_a_workload_identity_is_named |
scoped-service-not-writable | writing the credentials of service {service} | Weles writes only a service that declares the kind its item is stored as, and this one declares none | service {service} declares no kind | declare the kind of {service}'s item in src/secrets/scoped-service/catalog/services.json, or write it outside Weles | config, exit 1, HTTP 503 | tests/secrets/scoping/scoped.mjs::a_service_weles_does_not_write_is_refused |
scoped-write-shape | writing the credentials of service {service} | a write carries exactly the fields the catalog declares for the service, and this one carries others, fewer or none | it carries {carried}; the service declares {fields} | pass every declared field and no other to the write | config, exit 1, HTTP 503 | tests/secrets/scoping/scoped.mjs::a_write_with_other_fields_is_refused |
scoped-value-invalid | writing field {field} of service {service} | a field holds text that is not blank and carries no NUL character, and this value is blank, carries a NUL or is not text | the value is {shape} | pass the credential the provider issued as non-blank text | config, exit 1, HTTP 503 | tests/secrets/scoping/scoped.mjs::a_blank_value_is_refused |
scoped-write-refused | `stado {command}` writing the credentials of service {service} | Stado ran and refused the write in its own words below (every written value redacted): the vault is locked or absent, or this host's Stado may not write the role | exit {status}: {answer} | correct what Stado names (`stado credentials doctor` says whether a key opens the vault), then run the trajectory again | refused, exit 1, HTTP 403 | tests/secrets/scoping/scoped.mjs::a_write_stado_refuses_quotes_stado_redacted |
managed-contract-unreadable | reading field {field} of the managed credential {secret} | no acquisition contract of this release lets Weles read that field of it: {why} | the contract of {secret}: {contract} | read the field the contract names, or declare the credential in WELES_MANAGED_PASSWORD_CONTRACTS_FILE with its reader | config, exit 1, HTTP 503 | tests/secrets/scoping/scoped-contract-failures.mjs::a_managed_field_no_contract_reads_is_refused |
managed-reader-mismatch | reading field {field} of the managed credential {secret} for tenant {tenant} | the grant catalog in force grants this reader the item on another field or tenant, so the read would be refused as nothing granted | {mismatch} | align the reader grant of the item in the grant catalog with the contract, then run again | config, exit 1, HTTP 503 | tests/secrets/scoping/scoped-contract-failures.mjs::a_managed_read_the_catalog_grants_otherwise_is_named |
pinned-reference-invalid | reading the proxy login an account pin names | the pin's credential_ref is no lowercase hexadecimal reference, so no field of weles-account-proxy-credentials is named by it | credential_ref of {length} characters, beginning {head}; nothing was read | pin the account again; the pin writes the reference of the proxy login it stores | config, exit 1, HTTP 503 | tests/secrets/scoping/scoped-contract-failures.mjs::a_pin_reference_that_is_no_hexadecimal_is_refused |
binding-tenant-invalid | finding tenant {tenant}'s Skarbiec binding | invalid Weles tenant id for Skarbiec binding: {tenant} is not a UUID, so it names no directory Stado lays out | tenant {tenant} | name the tenant by the UUID Stado's binding directory under ~/.stado/weles-skarbiec-tenants carries | config, exit 1, HTTP 503 | tests/secrets/scoping/binding-failures.mjs::a_tenant_that_is_no_uuid_is_refused |
binding-unsafe | reading the Skarbiec {label} {path} | refusing unsafe Skarbiec {label}: {path} must be a {kind} only its owner can use, and it {reason} | {path}: {reason} | make it owned by the account the worker runs as and readable by it alone (`chmod 600 {path}` for a file, `chmod 700 {path}` for a directory), or have Stado issue it again (`stado credentials token mint`) | config, exit 1, HTTP 503 | tests/secrets/scoping/binding-failures.mjs::a_token_file_others_can_read_is_refused |
binding-uninspected | reading the Skarbiec {label} {path} | the system refused to read the metadata of {path} ({errno}), so whether only its owner can use it is unknown | {path}: {errno}; system: {answer} | give the account the worker runs as access to the directories above {path} (`ls -ld {path}`), then try again | config, exit 1, HTTP 503 | tests/secrets/scoping/binding-failures.mjs::a_token_file_under_a_closed_directory_is_named |
binding-absent | reading the Skarbiec {label} {path} | {path} does not exist: Stado lays out a tenant's binding when the tenant is bound to this worker | {path} is absent | `stado credentials token mint` for the tenant issues its bearer into the binding again; `ls -l {path}` shows what is there | config, exit 1, HTTP 503 | tests/secrets/scoping/binding-failures.mjs::a_tenant_without_its_binding_directory_is_named |
acquired-target-uncontracted | writing {secret}/{field} to the vault for {operation} request {request} | no acquisition contract of this release admits that item and field: {why} | the contract of {secret}: {contract}; nothing was written | declare it in WELES_MANAGED_PASSWORD_CONTRACTS_FILE (a password) or write one of the fields the contract names | config, exit 1, HTTP 503 | tests/secrets/acquiring/acquired-write-failures.mjs::a_write_no_contract_admits_is_refused |
acquired-value-unshaped | writing {secret}/{field} to the vault for {operation} request {request} | the value lacks the shape the contract admits ({shape}: {expected}), so it is not the credential the provider issues | a value of {bytes} bytes; it is not shown; nothing was written | capture the credential the provider shows again, or generate the password with the vault policy (`weles errors show identity` names its length) | refused, exit 1, HTTP 403 | tests/secrets/acquiring/acquired-write-failures.mjs::a_value_of_another_shape_is_refused |
acquired-request-unnamed | writing {secret}/{field} to the vault for {operation} request {request} | the write names no request id of 64 hexadecimal digits, and Skarbiec records every write against the credential operation's request | request id {given}; nothing was written | start the run as a credential operation, whose constraints carry request_id, then run again | config, exit 1, HTTP 503 | tests/secrets/acquiring/acquired-write-failures.mjs::a_write_without_a_request_id_is_refused |
acquired-operation-unknown | writing {secret}/{field} to the vault for {operation} request {request} | the write names no operation a credential is written for | operation {operation}; a write is one of {operations}; nothing was written | start the run with one of {operations} as its constraints' operation | config, exit 1, HTTP 503 | tests/secrets/acquiring/acquired-write-failures.mjs::a_write_for_an_unknown_operation_is_refused |
acquired-operation-password-only | writing {secret}/{field} to the vault for {operation} request {request} | {operation} writes a password whose predecessor Weles did not know, and {secret} is no password: its contract has shape {shape} | operation {operation} for a {shape} contract; nothing was written | acquire or rotate {secret} instead, or declare it as a managed password in WELES_MANAGED_PASSWORD_CONTRACTS_FILE | config, exit 1, HTTP 503 | tests/secrets/acquiring/acquired-write-failures.mjs::a_reset_of_a_token_is_refused |
acquired-value-not-utf8 | writing {secret}/{field} to the vault for {operation} request {request} | the vault keeps text fields, and the value stops being UTF-8 text at byte {offset} | a value of {bytes} bytes, valid UTF-8 up to byte {offset}; it is not shown; nothing was written | capture the credential as the text the provider shows, not its bytes | refused, exit 1, HTTP 403 | tests/secrets/acquiring/acquired-write-failures.mjs::a_value_that_is_not_utf8_is_refused |
acquired-origin-unstated | writing {secret}/{field} to the vault for {operation} request {request} | the write claims no exact https origin the value was captured on ({from}), and Skarbiec records that provenance with every write | captured origin {origin}; nothing was written | capture the value on the provider page, or state secret_source_origin as the bare https origin (https://host, no path or trailing slash) | config, exit 1, HTTP 503 | tests/secrets/acquiring/acquired-write-failures.mjs::a_write_without_its_origin_is_refused |
acquired-origin-other | writing {secret}/{field} to the vault for {operation} request {request} | the value was captured on another origin than the signup origin Skarbiec recorded for the operation, so Skarbiec would refuse the write as another provenance | captured on {captured}; the operation declared {declared}; nothing was written | capture the credential on {declared}, or start a new operation declaring {captured} | refused, exit 1, HTTP 403 | tests/secrets/acquiring/acquired-write-failures.mjs::a_value_captured_on_another_origin_is_refused |
acquired-account-email-invalid | writing {secret}/{field} to the vault for {operation} request {request} | a password is written as the login of one account, and the write names no valid account email | account email {email}; nothing was written | start the operation with account_email set to the address the account signs in with | config, exit 1, HTTP 503 | tests/secrets/acquiring/acquired-write-failures.mjs::a_password_without_its_account_is_refused |
acquired-writer-token-unissued | writing {secret}/{field} to the vault for {operation} request {request} through Skarbiec at {route} | Stado never issued the scoped writer token file {file} of consumer {consumer}, so this host may not write {item} | no file {file} in {where}; nothing was written | issue the writer grant of {consumer} for {item} on this host (`stado credentials doctor` names what it holds), then run again | config, exit 1, HTTP 503 | tests/secrets/acquiring/acquired-write-failures.mjs::a_write_without_its_writer_token_is_named |
acquired-helper-unstarted | writing {secret}/{field} to the vault for {operation} request {request} through the write helper {helper} | the write helper could not be started ({errno}): the release lacks it, or SKARBIEC_WELES_WRITER_COMMAND names a file that is not there | `{command}` in {directory}: {problem}; nothing was written | install the Weles release whose src/worker/deploy/acquire carries skarbiec-write.mjs (`stado product install weles --surface cli`), or correct SKARBIEC_WELES_WRITER_COMMAND | config, exit 1, HTTP 503 | tests/secrets/acquiring/acquired-write-failures.mjs::a_write_helper_that_cannot_start_is_named |
acquired-helper-unavailable | writing {secret}/{field} to the vault for {operation} request {request} through Skarbiec at {route} | the write helper could not reach Skarbiec or Stado, and says a later try may succeed | `{command}` in {directory} exited {status}: cause {helper_cause}: {said}; its fix: {helper_fix} | {helper_fix}; then run again | infra_down, exit 69, HTTP 503, retryable | tests/secrets/acquiring/acquired-write-failures.mjs::a_write_skarbiec_cannot_take_now_is_named |
acquired-helper-refused | writing {secret}/{field} to the vault for {operation} request {request} through Skarbiec at {route} | the write helper declared why Skarbiec did not take the write (its cause below): the grant, the item or the payload | `{command}` in {directory} exited {status}: cause {helper_cause}: {said}; its fix: {helper_fix} | {helper_fix}; `weles errors show skarbiec` lists the helper's causes | refused, exit 1, HTTP 403 | tests/secrets/acquiring/acquired-write-failures.mjs::a_write_skarbiec_refuses_is_named |
acquired-helper-unenveloped | writing {secret}/{field} to the vault for {operation} request {request} through Skarbiec at {route} | the write helper {ended} without its declared JSON answer on standard error: it crashed, was killed, or is of another release | `{command}` in {directory} {ended}; it said: {said} (the value redacted wherever it appears) | run the helper of this release (`stado product install weles --surface cli`), and read what it said above | unknown, exit 1, HTTP 500 | tests/secrets/acquiring/acquired-write-failures.mjs::a_write_helper_that_answers_nothing_is_named |
read-value-not-line | {what} | the reader printed no single line of text where a field value is one: it printed nothing, several lines or a NUL | {lines} lines, {bytes} bytes; the value is not shown | store the field as one line of text (`stado credentials put`), or run the reader of this release (`stado product install weles --surface cli`) | unknown, exit 1, HTTP 500 | tests/secrets/acquired-read-failures.mjs::a_read_that_prints_no_line_is_named |
role-read-unstarted | reading field {field} of the item playing role {role} as consumer {consumer} | Stado could not be started ({errno}), so no grant was presented | `{command}` in {directory}: {problem} | install Stado (`stado --version` must answer) or set WELES_STADO_BIN to its program, then run again | config, exit 1, HTTP 503 | tests/secrets/acquired-read-failures.mjs::a_role_read_stado_cannot_start_is_named |
role-read-refused | reading field {field} of the item playing role {role} as consumer {consumer} | Stado refused the read in its own words below: the consumer's grant does not reach the role or field, the grant file is another consumer's, or Skarbiec was not reached | `{command}` in {directory} {ended}: {said} | grant {consumer} read:role:{role}#{field} (`stado credentials doctor` says what this host holds), then run again | refused, exit 1, HTTP 403 | tests/secrets/acquired-read-failures.mjs::a_role_read_stado_refuses_is_named |
acquisition-helper-unstarted | acquiring {coordinate}/{field} as consumer {consumer} from Skarbiec at {route} | the acquisition helper {helper} could not be started ({errno}): the release lacks it, or SKARBIEC_WELES_READER_ACQUIRE_COMMAND names a file that is not there | `{command}` in {directory}: {problem} {record} | install the Weles release whose src/worker/deploy/acquire carries skarbiec-acquire.mjs (`stado product install weles --surface cli`), or correct SKARBIEC_WELES_READER_ACQUIRE_COMMAND | config, exit 1, HTTP 503 | tests/secrets/acquired-read-failures.mjs::an_acquisition_helper_that_cannot_start_is_named |
acquisition-helper-unenveloped | acquiring {coordinate}/{field} as consumer {consumer} from Skarbiec at {route} | the acquisition helper {ended} without its declared JSON answer on standard error: it crashed, was killed, or is of another release | `{command}` in {directory} {ended}; it said: {said} {record} | run the helper of this release (`stado product install weles --surface cli`), and read what it said above | unknown, exit 1, HTTP 500 | tests/secrets/acquired-read-failures.mjs::an_acquisition_helper_that_answers_nothing_is_named |
acquisition-route-refused | acquiring {coordinate}/{field} as consumer {consumer} from Skarbiec at {route} | the helper could not find Skarbiec through the secrets alternative, so the vault was never asked | `{command}` in {directory} {ended}: cause {helper_cause}: {said}; its fix: {helper_fix} {record} | {helper_fix}; then run again | infra_down, exit 69, HTTP 503, retryable | tests/secrets/acquired-read-failures.mjs::an_acquisition_without_a_route_is_named |
acquisition-scope-undeclared | acquiring {coordinate}/{field} as consumer {consumer} from Skarbiec at {route} | the deployment's acquisition scopes declare no line for this consumer, item and field, so the helper asks Skarbiec nothing | `{command}` in {directory} {ended}: cause {helper_cause}: {said}; its fix: {helper_fix} {record} | {helper_fix} | config, exit 1, HTTP 503 | tests/secrets/acquired-read-failures.mjs::an_acquisition_of_an_undeclared_scope_is_named |
acquisition-authority-unreachable | acquiring {coordinate}/{field} as consumer {consumer} from Skarbiec at {route} | Skarbiec did not answer the helper: it is down, or the address the secrets alternative names does not reach it | `{command}` in {directory} {ended}: cause {helper_cause}: {said}; its fix: {helper_fix} {record} | {helper_fix}; then run again | infra_down, exit 69, HTTP 503, retryable | tests/secrets/acquired-read-failures.mjs::an_acquisition_skarbiec_does_not_answer_is_named |
acquisition-workload-unauthorized | acquiring {coordinate}/{field} as consumer {consumer} from Skarbiec at {route} | Skarbiec does not admit the run's workload identity for this consumer: it is unregistered, or its signing key is another workload's | `{command}` in {directory} {ended}: cause {helper_cause}: {said}; its fix: {helper_fix} {record} | {helper_fix} | auth, exit 1, HTTP 401 | tests/secrets/acquired-read-failures.mjs::an_acquisition_of_an_unadmitted_workload_is_named |
acquisition-grant-ended | acquiring {coordinate}/{field} as consumer {consumer} from Skarbiec at {route} | the consumer's grant on the item ended: its window closed or it was revoked | `{command}` in {directory} {ended}: cause {helper_cause}: {said}; its fix: {helper_fix} {record} | {helper_fix} | refused, exit 1, HTTP 403 | tests/secrets/acquired-read-failures.mjs::an_acquisition_whose_grant_ended_is_named |
acquisition-field-absent | acquiring {coordinate}/{field} as consumer {consumer} from Skarbiec at {route} | Skarbiec holds the item without that field: the credential was never written with it | `{command}` in {directory} {ended}: cause {helper_cause}: {said}; its fix: {helper_fix} {record} | {helper_fix} | not_found, exit 1, HTTP 404 | tests/secrets/acquired-read-failures.mjs::an_acquisition_of_an_absent_field_is_named |
acquisition-refused | acquiring {coordinate}/{field} as consumer {consumer} from Skarbiec at {route} | Skarbiec refused the acquisition for a reason the helper names in its own cause below | `{command}` in {directory} {ended}: cause {helper_cause}: {said}; its fix: {helper_fix} {record} | {helper_fix} | refused, exit 1, HTTP 403 | tests/secrets/acquired-read-failures.mjs::an_acquisition_skarbiec_refuses_is_named |
acquisition-cause-other | acquiring {coordinate}/{field} as consumer {consumer} from Skarbiec at {route} | the helper declared a cause of skarbiec.acquisition this release does not know: it is of a newer release | `{command}` in {directory} {ended}: cause {helper_cause}: {said}; its fix: {helper_fix} {record} | {helper_fix}; run a helper and reader of the same release (`stado product install weles --surface cli`) | unknown, exit 1, HTTP 500 | tests/secrets/acquired-read-failures.mjs::an_acquisition_cause_this_release_does_not_know_is_named |
job-contract-absent | building the {provider} job that acquires {secret} | the release's acquisition registry names {secret}, which no contract of the same release holds: the registry and the contract table disagree | definition {secret} of provider {provider}, flow {flow}; nothing was queued | install one consistent Weles release (`stado product install weles --surface cli`) | unknown, exit 1, HTTP 500 | tests/secrets/acquiring/acquisition-job-failures.mjs::a_job_for_an_uncontracted_definition_is_named |
job-coordinate-absent | building the {provider} job that acquires {secret} | the job must name {what} to bind the run to one exact identity, and the request carries none | {what}: {given}; nothing was queued | send the request again with {what} | config, exit 1, HTTP 503 | tests/secrets/acquiring/acquisition-job-failures.mjs::a_job_without_its_identity_is_named |
job-item-unconstrained | building the {provider} job that acquires {secret} | the job this release built names no vault item in constraints.vault_item_id, so the run could not store what it acquires | constraints {constraints}; nothing was queued | install one consistent Weles release (`stado product install weles --surface cli`) | unknown, exit 1, HTTP 500 | tests/secrets/acquiring/acquisition-job-failures.mjs::a_job_without_its_item_is_named |
catalog-schema-undeclared | reading the service catalog src/secrets/scoped-service/catalog/services.json | service {service} names schema {schema}, which the same catalog does not declare: the catalog was edited inconsistently | the catalog declares schemas {schemas} | declare schema {schema} in services.json, or install one consistent Weles release (`stado product install weles --surface cli`) | unknown, exit 1, HTTP 500 | tests/secrets/acquiring/acquisition-job-failures.mjs::a_catalog_service_of_an_undeclared_schema_is_named |
answer-unstated | reading what the credential operation run stated in {where} | the file holds no credential_operation object, the one place a run states what it did to the credential: the run ended before stating it, or is of another release | {where} holds {found} | read the run's log and recordings (`weles runs show <run-id>`) for where it stopped, then run the operation again | unknown, exit 1, HTTP 500 | tests/secrets/acquiring/operation-answer-failures.mjs::an_answer_without_its_object_is_named |
answer-incomplete | reading what the credential operation run stated in {where} | the credential_operation object states no {member}, which every answer carries | {member}: {found}; it states {members} | run a trajectory of this release, which states status, message and providerEffect (`stado product install weles --surface cli`) | unknown, exit 1, HTTP 500 | tests/secrets/acquiring/operation-answer-failures.mjs::an_answer_without_a_member_is_named |
followup-count-unstated | reading the Semantic Scholar key mail for submission {source} | {name} states {what} as no whole number above zero, and Weles assumes none | {name}: {value} | set {name} to a whole number above zero in the Weles service environment (`stado service env set weles`) | config, exit 1, HTTP 503 | tests/secrets/semantic-scholar-followup-failures.mjs::a_followup_without_its_counts_is_refused |
followup-contract-absent | reading the Semantic Scholar key mail for submission {source} | the key was stored, and this release holds no contract of semantic_scholar.api_key to say where: the contract table and the follow-up disagree | key stored from mail {email}; no contract to name its item | install one consistent Weles release (`stado product install weles --surface cli`) | unknown, exit 1, HTTP 500 | tests/secrets/semantic-scholar-followup-failures.mjs::a_followup_without_its_contract_is_named |
followup-validation-unanswered | asking {url} whether a key found in the mail is valid | the Semantic Scholar API could not be reached ({errno}): the network or its address failed, so the candidate was neither accepted nor rejected | {problem}; {scanned} mails read so far; the candidate is not shown | run the follow-up again once https://api.semanticscholar.org answers | infra_down, exit 69, HTTP 503, retryable | tests/secrets/semantic-scholar-followup-failures.mjs::a_validation_the_api_does_not_answer_is_named |
runtime#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
cost-run-unset | keeping the cost records of this run | ACTION_LOG_ID is not set, so the {count} cost records of this run have no rollup file to go to | {count} records held in memory, {usd} USD; ACTION_LOG_ID unset | run it through the Weles worker, which sets ACTION_LOG_ID for every run it starts | config, exit 1, HTTP 503 | tests/runtime/cost-run-failures.mjs::a_run_without_its_log_id_names_its_costs |
cost-rollup-unreadable | keeping the cost records of run {run} in {path} | the rollup file exists and cannot be read ({errno}), so it is not overwritten | {path}: {problem} | give the worker's user read and write access to {path}, then run again | config, exit 1, HTTP 503 | tests/runtime/cost-failures.mjs::a_rollup_that_cannot_be_read_is_named |
cost-rollup-not-json | keeping the cost records of run {run} in {path} | the rollup file is not JSON, so it is not overwritten | {bytes} bytes; the parser said: {problem} | move {path} aside (it holds the run's earlier costs) and run again | config, exit 1, HTTP 503 | tests/runtime/cost-failures.mjs::a_rollup_that_is_not_json_is_named |
cost-rollup-shape | keeping the cost records of run {run} in {path} | the rollup file is JSON without cost_usd and service_costs, so it is no cost rollup and is not overwritten | members: {members} | move {path} aside (it is some other file) and run again | config, exit 1, HTTP 503 | tests/runtime/cost-failures.mjs::a_rollup_of_another_shape_is_named |
cost-rollup-unwritten | keeping the cost records of run {run} in {path} | the recordings directory refused the rollup ({errno}): it is not writable by the worker user, or its disk is full | {path}: {problem} | give the worker's user write access to {directory} and free space on its disk | config, exit 1, HTTP 503 | tests/runtime/cost-failures.mjs::a_rollup_the_directory_refuses_is_named |
human#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
timing-unseeded | drawing human timing for {use} | human timing was drawn before any session seeded it (seedHumanTiming runs at session start); the run would not be reproducible from its recorded seed | seedHumanTiming has not run in this process (pid {pid}) | start a WSession (or call seedHumanTiming with the run's seed) before the pointer or keyboard moves | unknown, exit 1, HTTP 500 | tests/human/timing-failures.mjs::a_draw_before_any_seed_is_refused |
timing-count-invalid | drawing one of {count} choices from the run's seeded timing | humanIndex needs a whole count of choices above zero, and {count} is not one | count {count} | pass the number of choices offered, a whole number above zero | unknown, exit 1, HTTP 500 | tests/human/timing-failures.mjs::a_draw_among_no_choices_is_refused |
reauth#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
bearer-rejected | admitting {method} {path} | the request carries no Authorization bearer equal to this API's BRAMA_WELES_REAUTH_TOKEN: it carries the general API token, another deployment's bearer, or one rotated after it was read | a request without the reauth bearer | send the token of the item playing brama-weles-reauth as the bearer (`stado credentials get --role brama-weles-reauth --field token`) | auth, exit 1, HTTP 401 | tests/reauth/failures.mjs::a_route_without_the_reauth_bearer_is_refused |
token-unset | admitting {method} {path} | this API was started without BRAMA_WELES_REAUTH_TOKEN, so no bearer can be admitted to its account routes: it was run outside its unit, whose launcher acquires the token | BRAMA_WELES_REAUTH_TOKEN: not set | start the API through its unit (`stado service ensure weles --host <this host> --reason <why>`), whose launcher acquires BRAMA_WELES_REAUTH_TOKEN | config, exit 1, HTTP 500 | tests/reauth/failures.mjs::an_api_started_without_the_reauth_token_names_it |
provider-unsupported | admitting {method} {path} | the body names a provider this Weles holds no {trajectory} trajectory for (src/trajectories/<provider>/{trajectory}.mjs), or names none | provider: {provider} | name one of the providers this route serves: {providers} | refused, exit 1, HTTP 400 | tests/reauth/failures.mjs::a_provider_without_a_sign_in_is_refused |
subscription-missing | admitting {method} {path} | the body names no subscription_id, and the route acts on exactly one subscription of the vault | subscription_id: not given | send subscription_id: the subscription id `weles accounts list` (or POST /reauth/accounts) shows | refused, exit 1, HTTP 400 | tests/reauth/failures.mjs::a_sign_in_naming_no_subscription_is_refused |
account-revision-changed | admitting {method} {path} | the account_revision the caller resolved differs from the revision the vault holds now: the subscription or its login was rewritten after the caller read it | asked for revision {asked}; the vault holds {account_revision} | resolve the account again (POST /reauth/resolve) and send the revision it answers | refused, exit 1, HTTP 409 | tests/reauth/failures.mjs::a_sign_in_for_an_account_that_changed_is_refused |
run-unstarted | running {trajectory} of {provider} on this host | the worker could not start the run once admitted: its process could not be spawned (no process slots, the trajectory program unreadable) or its progress could not be recorded | {answer} | run `weles doctor` on the Weles host for what it lacks, then ask for the run again; `weles worker version` names the release | infra_down, exit 69, HTTP 200, retryable | tests/reauth/failures.mjs::a_run_the_worker_cannot_start_is_named |
run-record-unwritable | recording the {trajectory} run of {provider} before it starts | the worker cannot write the run's result file, so the run is not started: the run results directory is missing, not writable by the worker's user, or its disk is full | {path}: {answer} | make the run results directory writable for the user the Weles unit runs as (`weles doctor` names it), then ask for the run again | config, exit 1, HTTP 200 | tests/reauth/failures.mjs::a_run_whose_record_cannot_be_written_is_named |
sign-in-cancelled | signing {provider} account in, run {run} | the run was cancelled before it ended, so no grant was taken | cancelled at stage {stage}: {message} | ask for the sign-in again once whatever cancelled it is settled; `weles runs show {run}` shows the run's stages and the end of its output | infra_down, exit 69, HTTP 200, retryable | tests/reauth/outcome-failures.mjs::a_sign_in_cancelled_while_it_runs_is_named |
sign-in-report-unreadable | reading why sign-in run {run} of {provider} failed | the run's AUTH_FAILURE line is not the JSON object of code, stage and message a sign-in trajectory writes, so why it failed is unknown | {message} | compare `weles worker version` with the release the trajectory came from; `weles runs show {run}` shows the run's stages and the end of its output | refused, exit 1, HTTP 200 | tests/reauth/outcome-failures.mjs::a_sign_in_whose_failure_line_cannot_be_read_is_named |
sign-in-acquisition-refused | reading field {field} of vault item {item} for sign-in run {run} of {provider} | the vault refused the run the item it signs in with ({reason}) | item {item}, field {field}, consumer {consumer}, at stage {stage} | grant consumer {consumer} field {field} of item {item} in the vault, or name another login; `stado credentials ls` lists what the store holds | refused, exit 1, HTTP 200 | tests/reauth/outcome-failures.mjs::a_sign_in_the_vault_refuses_its_login_is_named |
sign-in-unreported | signing {provider} account in, run {run} | the run ended without a grant and without an AUTH_FAILURE line or vault acquisition record saying why: it was killed, or crashed before it could report | last stage {stage}; exit {exit} | read the end of the run's output: `weles runs show {run}` shows the run's stages and the end of its output | refused, exit 1, HTTP 200 | tests/reauth/outcome-failures.mjs::a_sign_in_that_ends_without_saying_why_is_named |
sign-in-undeclared | signing {provider} account in, run {run}, at stage {stage} | the trajectory met an error no declared cause names, so only its own words say what broke | {message} | `weles runs show {run}` shows the run's stages and the end of its output; the error is a defect of the trajectory to declare | refused, exit 1, HTTP 200 | tests/reauth/outcome-failures.mjs::a_sign_in_failing_on_an_undeclared_error_is_named |
run-result-missing | reading the result of {trajectory} run {run} of {provider} | the run ended without printing its result line: the trajectory exited before answering, which is a defect of that trajectory | exit {exit}; last output: {tail} | run `weles runs show {run}` for what the run saw, and report it with `weles worker version` | config, exit 1, HTTP 200 | tests/reauth/failures.mjs::a_run_that_prints_no_result_is_named |
acquire-unsupported | admitting the purchase of a {provider} account | Weles has no src/trajectories/{provider}/account/acquire.mjs, so it cannot buy an account of that provider | provider: {provider}; it buys accounts of {providers} | name a provider Weles buys accounts of: {providers} | refused, exit 1, HTTP 400 | tests/reauth/failures.mjs::a_purchase_of_a_provider_weles_cannot_buy_is_refused |
acquisition-incomplete | admitting the purchase of a {provider} account | a purchase names the correlation subscription_id, the plan_tier to buy and the reason, and the body lacks some | missing: {missing} | send subscription_id, plan_tier and reason as non-empty text | refused, exit 1, HTTP 400 | tests/reauth/failures.mjs::a_purchase_lacking_its_plan_is_refused |
authorization-incomplete | reading the authorization {path} is asked to complete | an authorization names a provider with an authorize page, a subscription_id and an authorize_url, and the body lacks one | provider: {provider}; subscription_id: {subscription}; authorize_url: {url} | send provider (one of {providers}), subscription_id and the authorize_url the harness printed | refused, exit 1, HTTP 400 | tests/reauth/failures.mjs::an_authorization_lacking_its_url_is_refused |
authorize-url-invalid | reading the authorization {path} is asked to complete | authorize_url, or the redirect_uri it carries, is not an absolute URL, so it names no page to drive | {what}: {url} | send the authorize_url exactly as the harness printed it | refused, exit 1, HTTP 400 | tests/reauth/failures.mjs::an_authorize_url_that_is_not_a_url_is_refused |
authorize-page-foreign | reading the authorization {path} is asked to complete | authorize_url is not the provider's own authorize page, and Weles drives no other page with the account | authorize_url is {page}; the {provider} authorize page is {expected} | send the authorize_url the provider's own login printed for this harness | refused, exit 1, HTTP 400 | tests/reauth/failures.mjs::an_authorization_on_another_page_is_refused |
authorize-redirect-missing | reading the authorization {path} is asked to complete | authorize_url carries no redirect_uri, so the code the provider issues could not go back to the harness that asked | authorize_url: {url} | send the whole authorize_url the harness printed, with its redirect_uri | refused, exit 1, HTTP 400 | tests/reauth/failures.mjs::an_authorization_without_a_redirect_is_refused |
authorize-redirect-foreign | reading the authorization {path} is asked to complete | redirect_uri sends the code somewhere other than the harness's own localhost listener, and Weles answers a redirect to that listener only | redirect_uri goes to {host} | start the authorization from the harness on the machine that will hold the grant, and send its authorize_url | refused, exit 1, HTTP 400 | tests/reauth/failures.mjs::an_authorization_redirecting_elsewhere_is_refused |
Admission#
How POST /api/v1/tasks, GET /api/v1/tasks/<task-id> and POST /api/v1/tasks/<task-id>/cancel refuse a request. The answer keeps the error code a caller branches on and adds message (what failed, why, observed), cause, remedy, retryable and docs.
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
bearer-missing | authenticating {method} {path} | the request carries no Authorization: Bearer header, and every public task route takes the Spis admission bearer | Authorization: {authorization} | send Authorization: Bearer <token>, the token field of the Skarbiec item weles-spis-public-admission this worker was started with | auth, exit 1, HTTP 401 | tests/admission/request-failures.mjs::submit_refuses_a_request_without_a_bearer |
bearer-wrong | authenticating {method} {path} | the bearer is not the one this worker admits: it belongs to another deployment, or the item was rotated after the caller read it | a bearer of {length} characters that does not match WELES_PUBLIC_API_BEARER | read the token field of the Skarbiec item weles-spis-public-admission again and send it; if it was rotated, restart the worker so it reads the new one | auth, exit 1, HTTP 401 | tests/admission/request-failures.mjs::submit_refuses_a_bearer_this_worker_does_not_admit |
media-type-missing | reading the body of {method} {path} | the request states no Content-Type, and a public task body is read only as application/json | Content-Type: none | send the body with the header Content-Type: application/json | refused, exit 1, HTTP 415 | tests/admission/request-failures.mjs::submit_refuses_a_body_without_content_type |
media-type-wrong | reading the body of {method} {path} | the request states a Content-Type other than application/json | Content-Type: {media_type} | send the body as JSON with the header Content-Type: application/json | refused, exit 1, HTTP 415 | tests/admission/request-failures.mjs::submit_refuses_a_body_of_another_media_type |
idempotency-key-missing | reading the Idempotency-Key of {method} {path} | the request carries no Idempotency-Key header, so a retry could not be told from a new request | Idempotency-Key: none | send an Idempotency-Key header of letters, digits, _, ., : or -, the same one each time the same request is retried | refused, exit 1, HTTP 400 | tests/admission/request-failures.mjs::submit_refuses_a_request_without_an_idempotency_key |
idempotency-key-invalid | reading the Idempotency-Key of {method} {path} | the Idempotency-Key holds a character other than letters, digits, _, ., : or - | Idempotency-Key: {key} | send a key of only those characters, such as a UUID | refused, exit 1, HTTP 400 | tests/admission/request-failures.mjs::submit_refuses_an_idempotency_key_with_other_characters |
body-not-json | reading the body of {method} {path} | the body is not a JSON document (invalid_json), so the task it asks for cannot be read | {bytes} bytes; the parser said: {answer} | send one JSON object as the body | refused, exit 1, HTTP 400 | tests/admission/request-failures.mjs::submit_refuses_a_body_that_is_not_json |
organization-denied | admitting a task for organization {organization} | the bearer authenticates one tenant and the request names another organization | organizationId: {organization} | send the organization_id field of the Skarbiec item weles-spis-public-admission as organizationId | refused, exit 1, HTTP 403 | tests/admission/request-failures.mjs::submit_refuses_another_organization |
task-id-invalid | reading the task id in {path} | a task id is the UUID the submission answered as taskId | task id: {task_id} | use the taskId of the submission answer | refused, exit 1, HTTP 400 | tests/admission/records/record-failures.mjs::status_refuses_a_task_id_that_is_not_a_uuid |
task-not-found | reading public task {task_id} | this worker holds no task of that id: it was submitted to another worker, or its state was removed | no task-{task_id}.json under {root} | read the task from the worker that answered its submission (its serviceIdentity names the host) | not_found, exit 1, HTTP 404 | tests/admission/records/record-failures.mjs::status_refuses_a_task_this_worker_never_held |
worker-recovering | admitting a public task | the worker is recovering its durable task state, or its last recovery failed and left admission paused | dispatcher: {dispatcher} | read GET /worker/status for the recovery failure, repair the state it names under the run results directory, then POST /worker/restart | infra_down, exit 69, HTTP 503, retryable | tests/admission/identities/deployment-failures.mjs::submit_refuses_while_recovery_failed |
prerequisites-not-ready | admitting a public task | a prerequisite the deployment must state is missing: a release version, digest or source revision, the generic_browser_task trajectory, or the artifact store (STADO_API_URL and WELES_STADO_OBJECT_API_TOKEN) | prerequisites: {prerequisites} | start the worker through its release launcher with the Stado object store declared for it; GET /api/v1/version lists which prerequisite is false | config, exit 1, HTTP 503 | tests/admission/identities/deployment-failures.mjs::submit_refuses_when_the_artifact_store_is_not_declared |
service-draining | admitting a public task | the worker received a termination signal and stops admitting work while its running tasks settle | the service is draining after a termination signal | send the task again once the worker has restarted; GET /api/v1/version answers ready: true when it admits work | infra_down, exit 69, HTTP 503, retryable | tests/admission/identities/deployment-failures.mjs::submit_refuses_while_the_worker_drains |
task-schema-unsupported | reading the task | the body is not an object whose schema is {expected} | schema: {schema} | send a task object with "schema": "{expected}" | refused, exit 1, HTTP 400 | tests/admission/tasks/task-failures.mjs::submit_refuses_a_task_of_another_schema |
json-number-unsafe | reading {path} | it holds a number that is not a safe integer (a fraction, or beyond 2^53 - 1), which canonical public JSON does not carry | {path} = {value} | send that value as a whole number within 2^53 - 1, or as a string | refused, exit 1, HTTP 400 | tests/admission/tasks/task-failures.mjs::submit_refuses_a_number_that_is_not_a_safe_integer |
json-lone-surrogate | reading {path} | a string or key holds a lone UTF-16 surrogate, which is not text and has no canonical encoding | the lone surrogate is in {path} | send the text as valid Unicode: encode the character whole, or drop the stray half | refused, exit 1, HTTP 400 | tests/admission/tasks/task-failures.mjs::submit_refuses_a_lone_utf16_surrogate |
task-fields-undeclared | reading the task | its fields are not exactly the ones {schema} declares | {reasons} | send exactly the fields {declared}; a credential enters a task only as a reference in credentialRefs | refused, exit 1, HTTP 400 | tests/admission/tasks/task-failures.mjs::submit_refuses_a_task_with_undeclared_or_missing_fields |
origin-invalid | reading the task origin | origin is not an exact HTTPS origin (scheme and host, no path, no trailing slash) | origin: {origin}; {answer} | send origin as https://<host>, for example the origin of input.product_url | refused, exit 1, HTTP 400 | tests/admission/tasks/task-failures.mjs::submit_refuses_an_origin_that_is_not_https |
origin-denied | admitting a task for origin {origin} | the origin is not in the service policy WELES_PUBLIC_API_ALLOWED_ORIGINS | allowed origins: {allowed} | send a task for an allowed origin, or add the origin to WELES_PUBLIC_API_ALLOWED_ORIGINS in the worker unit and restart it | refused, exit 1, HTTP 403 | tests/admission/tasks/task-failures.mjs::submit_refuses_an_origin_the_policy_does_not_allow |
action-denied | admitting a task for action {action} | the public API runs only the action {allowed} | action: {action} | send "action": "{allowed}"; other actions run through the operator API, not the public one | refused, exit 1, HTTP 403 | tests/admission/tasks/task-failures.mjs::submit_refuses_an_action_the_policy_does_not_allow |
input-not-object | reading task.input | input is not a JSON object | input: {input} | send input as an object holding product_url, objective, constraints and spisBinding | refused, exit 1, HTTP 400 | tests/admission/tasks/task-failures.mjs::submit_refuses_an_input_that_is_not_an_object |
input-fields-undeclared | reading task.input | its fields are not exactly the ones the task schema declares for input | {reasons} | send exactly the fields {declared} | refused, exit 1, HTTP 400 | tests/admission/tasks/task-failures.mjs::submit_refuses_an_input_with_undeclared_fields |
product-url-invalid | reading input.product_url | it is not an exact canonical public HTTPS URL without credentials | product_url: {url}; {answer} | send the page as https://<host>/<path>, written as new URL() writes it (lowercase host, a path of at least /), with no user or password | refused, exit 1, HTTP 400 | tests/admission/tasks/task-failures.mjs::submit_refuses_a_product_url_that_is_not_canonical_https |
origin-target-mismatch | admitting a task for {url} | the task origin is not the origin of the page it asks the browser to open | origin {origin}, page origin {target} | send origin equal to the origin of input.product_url | refused, exit 1, HTTP 403 | tests/admission/tasks/task-failures.mjs::submit_refuses_a_page_outside_the_task_origin |
objective-empty | reading input.objective | objective is not a non-empty string, so the run would have nothing to do | objective: {objective} | send one sentence saying what the browser should record on the page | refused, exit 1, HTTP 400 | tests/admission/tasks/task-failures.mjs::submit_refuses_an_empty_objective |
constraints-mismatch | reading input.constraints | the constraints are not exactly the browser-evidence policy {policy} this worker enforces | sent {constraints}; the policy states {expected} | send input.constraints as exactly {expected}; GET /api/v1/version names the policy and its digest | refused, exit 1, HTTP 400 | tests/admission/tasks/task-failures.mjs::submit_refuses_constraints_other_than_the_policy |
spis-binding-not-object | reading input.spisBinding | spisBinding is not a JSON object | spisBinding: {value} | send the binding Spis issued for the crawl attempt as an object of schema weles.spis-browser-evidence-binding.v1 | refused, exit 1, HTTP 400 | tests/admission/identities/binding-failures.mjs::submit_refuses_a_spis_binding_that_is_not_an_object |
spis-binding-fields | reading input.spisBinding | its fields are not exactly the ones weles.spis-browser-evidence-binding.v1 declares | undeclared: {undeclared}; missing: {missing} | send exactly the fields {declared} | refused, exit 1, HTTP 400 | tests/admission/identities/binding-failures.mjs::submit_refuses_a_spis_binding_with_missing_fields |
spis-binding-schema | reading input.spisBinding | its schema is not weles.spis-browser-evidence-binding.v1 | schema: {schema} | send the binding as Spis writes it for this worker, schema weles.spis-browser-evidence-binding.v1 | refused, exit 1, HTTP 400 | tests/admission/identities/binding-failures.mjs::submit_refuses_a_spis_binding_of_another_schema |
spis-binding-attempt | reading input.spisBinding | attempt is not a whole number from 1 to 4294967295 | attempt: {attempt} | send the attempt number Spis counted for the record, starting at 1 | refused, exit 1, HTTP 400 | tests/admission/identities/binding-failures.mjs::submit_refuses_a_spis_attempt_of_zero |
spis-binding-source-revision | reading input.spisBinding | source_revision is not a full lowercase 40-character Git commit | source_revision: {value} | send the full commit of the Spis source the crawl ran from | refused, exit 1, HTTP 400 | tests/admission/identities/binding-failures.mjs::submit_refuses_a_spis_source_revision_that_is_not_a_commit |
spis-binding-digest | reading input.spisBinding | {field} is not a lowercase 64-character SHA-256 | {field}: {value} | send the SHA-256 Spis computed, in lowercase hex | refused, exit 1, HTTP 400 | tests/admission/identities/binding-failures.mjs::submit_refuses_a_spis_digest_that_is_not_sha256 |
spis-binding-service | reading input.spisBinding | service is not the exact authorized Weles service identity: weles-admission for consumer spis, capability browser-evidence and action generic_browser_task, with a directory generation, host, release id and source revision | service: {value} | send the service identity GET /api/v1/version answers as serviceIdentity, with its generation as directory_generation and its active_host as host | refused, exit 1, HTTP 400 | tests/admission/identities/binding-failures.mjs::submit_refuses_a_spis_service_of_another_capability |
spis-binding-endpoint | reading input.spisBinding | service.endpoint is not the exact absolute /api/v1 base URL, with no credentials, query or fragment | service.endpoint: {value} | send the endpoint GET /api/v1/version answers in serviceIdentity | refused, exit 1, HTTP 400 | tests/admission/identities/binding-failures.mjs::submit_refuses_a_spis_endpoint_that_is_not_the_task_api |
spis-binding-component | reading input.spisBinding | {field} is not one portable path component (letters, digits, ., _ or -, and not . or ..) | {field}: {value} | send the {field} Spis wrote, which is one such component | refused, exit 1, HTTP 400 | tests/admission/identities/binding-failures.mjs::submit_refuses_a_spis_component_that_is_a_path |
spis-binding-not-canonical | reading input.spisBinding | the record key, attempt id or artifact URIs are not the ones recomputed from the binding, so the evidence would land on another record | record_key {record_key} (expected {expected_record_key}); attempt_id {attempt_id} (expected {expected_attempt_id}); artifact_uri {artifact_uri} (expected {expected_artifact_uri}); output_uri {output_uri} (expected {expected_output_uri}) | send the binding exactly as Spis computed it; do not edit one field of it | refused, exit 1, HTTP 400 | tests/admission/identities/binding-failures.mjs::submit_refuses_a_spis_binding_whose_keys_do_not_recompute |
credential-refs-invalid | reading task.credentialRefs | credentialRefs is not an array of non-empty strings | credentialRefs: {value} | send "credentialRefs": [] for a browser-evidence task | refused, exit 1, HTTP 400 | tests/admission/tasks/task-failures.mjs::submit_refuses_credential_refs_that_are_not_a_list |
credential-refs-denied | admitting a task that names credentials | a browser-evidence task records a public page and is never handed a credential | credentialRefs: {value} | send "credentialRefs": []; a page behind a sign-in is not public evidence | refused, exit 1, HTTP 403 | tests/admission/tasks/task-failures.mjs::submit_refuses_a_task_that_names_credentials |
evidence-policy-invalid | reading task.evidencePolicy | a browser-evidence task keeps its full evidence, and evidencePolicy says otherwise | evidencePolicy: {value} | send "evidencePolicy": "full" | refused, exit 1, HTTP 400 | tests/admission/tasks/task-failures.mjs::submit_refuses_an_evidence_policy_other_than_full |
justification-empty | reading task.justification | justification is not a non-empty string, and every admitted task records why it runs | justification: {value} | send one sentence saying why this page is recorded | refused, exit 1, HTTP 400 | tests/admission/tasks/task-failures.mjs::submit_refuses_an_empty_justification |
idempotency-conflict | admitting the task under Idempotency-Key {key} | the key was already used for a request with different content, so this is not a retry of it | the key is bound to request digest {bound}; this request digests to {digest} | send a new Idempotency-Key for a new request, or resend the original request unchanged to read its task | refused, exit 1, HTTP 409 | tests/admission/identities/deployment-failures.mjs::submit_refuses_a_reused_key_for_another_request |
service-identity-unreadable | reading the deployed Weles service identity | the placement policy, the service-directory snapshot or the admission endpoint Stado publishes on this host could not be read as this release: the identity reader refused with {lower} | {answer} | {fix}; then send the task again (GET /worker/status names the failure and its cause as serviceIdentityFailure, and `weles errors show admission.service-identity` lists what the reader refuses) | config, exit 1, HTTP 503 | tests/admission/identities/binding-failures.mjs::submit_refuses_when_the_service_identity_is_unpublished |
service-identity-mismatch | admitting a task bound to service {bound} | spisBinding.service is not the service identity this worker is deployed as: Spis bound the attempt to another host, generation or release | bound {bound}; deployed {deployed} | have Spis read GET /api/v1/version again and bind the attempt to the identity it answers | refused, exit 1, HTTP 403 | tests/admission/identities/binding-failures.mjs::submit_refuses_a_binding_to_another_service_generation |
target-unresolved | resolving the page host {host} | the host has no DNS answer: it does not exist, or this worker cannot reach a resolver | {answer} | check the host name in input.product_url; if it resolves elsewhere, check the DNS configuration of the worker host | not_found, exit 1, HTTP 403 | tests/admission/identities/deployment-failures.mjs::submit_refuses_a_page_host_without_dns |
target-not-public | resolving the page host {host} | the host resolves to an address IANA does not declare globally reachable (loopback, private, link-local and the like), and a public task opens only public pages | {answer} | send a page whose host resolves only to public addresses; a private page is opened through the operator API | refused, exit 1, HTTP 403 | tests/admission/identities/deployment-failures.mjs::submit_refuses_a_page_host_on_a_private_address |
address-registry-unreadable | judging the addresses of {host} | the IANA special-purpose address registries could not be read, so no address can be judged public | {answer} | let the worker host reach https://www.iana.org/assignments/ (directly or through its HTTPS_PROXY), then send the task again | infra_down, exit 69, HTTP 503, retryable | tests/admission/identities/deployment-failures.mjs::submit_refuses_when_the_address_registry_is_unreachable |
task-state-unwritable | recording the public task under {root} | the worker cannot write its task state: the directory is not writable by the service user, or the disk is full | {answer} | give the service user write access to {root} and free space on its disk, then send the task again with the same Idempotency-Key | config, exit 1, HTTP 500 | tests/admission/identities/deployment-failures.mjs::submit_refuses_when_task_state_is_unwritable |
wait-invalid | reading the wait parameter of {path} | wait takes only the value terminal | wait={wait} | send ?wait=terminal to hold the answer until the task ends, or no wait to read it now | refused, exit 1, HTTP 400 | tests/admission/records/record-failures.mjs::status_refuses_a_wait_other_than_terminal |
task-record-unreadable | reading public task {task_id} | its record {file} is not one JSON document: the worker writes it whole and renames it into place, so it was truncated or written by something else | {answer} (cause {lower}) | restore {file} from the worker host backup, or remove it so the task reads as not found | config, exit 1, HTTP 500 | tests/admission/records/record-failures.mjs::status_refuses_a_task_record_that_is_not_json |
task-record-refused | reading public task {task_id} | the system refused to read its record {file}, so the account the worker runs as cannot answer it | {answer} (cause {lower}) | give the account the worker runs as read access to {file} (`ls -l {file}` shows its owner and mode) | config, exit 1, HTTP 500 | tests/admission/records/record-failures.mjs::status_refuses_a_task_record_the_worker_cannot_read |
task-record-foreign | reading public task {task_id} | its record {file} is not task {task_id} of this organization: {answer}; it was edited or written by another deployment sharing this home | {file}: {answer} | restore {file} from the worker host backup, or move it to the deployment it belongs to so the task reads as not found here | config, exit 1, HTTP 500 | tests/admission/records/record-failures.mjs::status_refuses_a_task_record_of_another_organization |
cancellation-schema-unsupported | reading the cancellation | the body is not an object whose schema is {expected} | schema: {schema} | send a cancellation object with "schema": "{expected}" | refused, exit 1, HTTP 400 | tests/admission/records/record-failures.mjs::cancel_refuses_a_cancellation_of_another_schema |
cancellation-fields-undeclared | reading the cancellation | its fields are not exactly the ones {schema} declares | {reasons} | send exactly the fields {declared} | refused, exit 1, HTTP 400 | tests/admission/records/record-failures.mjs::cancel_refuses_a_cancellation_with_undeclared_fields |
cancellation-reason-empty | reading the cancellation | reason is not a non-empty string, and every cancellation records why | reason: {value} | send one sentence saying who cancels the task and why | refused, exit 1, HTTP 400 | tests/admission/records/record-failures.mjs::cancel_refuses_a_cancellation_without_a_reason |
cancellation-conflict | cancelling public task {task_id} | the task already has a cancellation under another Idempotency-Key or reason, and one task is cancelled once | recorded cancellation: key {recorded_key}, reason {recorded_reason} | read the task with GET /api/v1/tasks/{task_id}; to retry the recorded cancellation, resend it with its key and reason | refused, exit 1, HTTP 409 | tests/admission/records/record-failures.mjs::cancel_refuses_a_second_cancellation_with_another_reason |
published-missing | reading the Stado-published document {path} | the file cannot be examined ({code}): Stado has not published it on this host, or the setting {setting} names another path | {path}: {answer} | republish it with Stado: `stado service directory publish --service weles-admission` writes the directory and transport; the placement policy is the one the fleet publishes for this host, which WELES_PLACEMENT_POLICY_FILE names, or set {setting} to where it is published | config, exit 1, HTTP 503 | tests/admission/identities/identity-failures.mjs::version_without_a_published_policy_names_the_file |
published-link | reading the Stado-published document {path} | it is a symbolic link, and Stado writes it as a regular file: a link could point at a document Stado never published | {path} is a symbolic link | republish it with Stado: `stado service directory publish --service weles-admission` writes the directory and transport; the placement policy is the one the fleet publishes for this host, which WELES_PLACEMENT_POLICY_FILE names | config, exit 1, HTTP 503 | tests/admission/identities/identity-failures.mjs::version_with_a_linked_policy_names_the_link |
published-not-file | reading the Stado-published document {path} | it is not a regular file (a directory or a device) | {path}: {kind} | move {path} aside and republish it with Stado: `stado service directory publish --service weles-admission` writes the directory and transport; the placement policy is the one the fleet publishes for this host, which WELES_PLACEMENT_POLICY_FILE names | config, exit 1, HTTP 503 | tests/admission/identities/identity-failures.mjs::version_with_a_directory_for_a_policy_names_it |
published-empty | reading the Stado-published document {path} | the file is empty: the publish that wrote it was cut off | {path}: 0 bytes | republish it with Stado: `stado service directory publish --service weles-admission` writes the directory and transport; the placement policy is the one the fleet publishes for this host, which WELES_PLACEMENT_POLICY_FILE names | config, exit 1, HTTP 503 | tests/admission/identities/identity-failures.mjs::version_with_an_empty_policy_names_it |
published-not-json | reading the Stado-published document {path} | it is not one JSON document: the publish that wrote it was cut off or another program wrote it | {path} ({bytes} bytes) begins {head}; parser: {answer} | republish it with Stado: `stado service directory publish --service weles-admission` writes the directory and transport; the placement policy is the one the fleet publishes for this host, which WELES_PLACEMENT_POLICY_FILE names | config, exit 1, HTTP 503 | tests/admission/identities/identity-failures.mjs::version_with_a_policy_that_is_not_json_names_it |
placement-identity | reading the placement policy {path} | its {field} is {found}, where a placement policy the fleet publishes for this host holds {expected} | {path}: {field} {found} | republish it with Stado: `stado service directory publish --service weles-admission` writes the directory and transport; the placement policy is the one the fleet publishes for this host, which WELES_PLACEMENT_POLICY_FILE names | config, exit 1, HTTP 503 | tests/admission/identities/identity-failures.mjs::version_with_a_policy_of_another_publisher_names_the_field |
placement-hosts | finding the host the placement policy {path} authorizes for generic_browser_task | {count} enabled host(s) carry generic_browser_task, where exactly one is the public service | hosts carrying it: {hosts} | enable generic_browser_task on exactly one host in the fleet's placement policy, and point WELES_PLACEMENT_POLICY_FILE at the policy published for this host | config, exit 1, HTTP 503 | tests/admission/identities/identity-failures.mjs::version_with_two_public_hosts_names_both |
directory-identity | reading the service-directory snapshot {path} | its {field} is {found}, where this worker expects {expected}: the directory names another host, release or service than the one running here | {path}: {field} {found} | move this host onto the release the directory names (`stado service update weles-admission`), or republish the directory once this release is active (`stado service directory publish --service weles-admission`) | config, exit 1, HTTP 503 | tests/admission/identities/identity-failures.mjs::version_with_a_directory_of_another_release_names_the_field |
directory-endpoint-invalid | reading service.endpoint of the service-directory snapshot {path} | the endpoint {why}; it is the exact absolute /api/v1 base URL of the public task API | service.endpoint: {found} | republish it with Stado: `stado service directory publish --service weles-admission` writes the directory and transport; the placement policy is the one the fleet publishes for this host, which WELES_PLACEMENT_POLICY_FILE names | config, exit 1, HTTP 503 | tests/admission/identities/identity-failures.mjs::version_with_a_directory_endpoint_of_another_path_names_it |
transport-differs | comparing the local admission transport {path} with the published endpoint | the transport {why}, so callers reaching the published endpoint would not reach this worker | transport: {found}; published endpoint: {expected} | republish it with Stado: `stado service directory publish --service weles-admission` writes the directory and transport; the placement policy is the one the fleet publishes for this host, which WELES_PLACEMENT_POLICY_FILE names | config, exit 1, HTTP 503 | tests/admission/identities/identity-failures.mjs::version_with_a_transport_elsewhere_names_both_addresses |
deployed-identity-mismatch | validating the deployed service identity against this release | its {field} is {found}, where the immutable release expects {expected} | {field}: {found} | move this host onto the release the directory names (`stado service update weles-admission`), or republish the directory for this release | config, exit 1, HTTP 503 | tests/admission/identities/identity-failures.mjs::version_of_an_identity_for_another_release_names_the_field |
state-entry-unexpected | recovering the public tasks under {root} | unexpected public task state entry: {entry}: the task directory holds only task-<uuid>.json files and the idempotency directory, and this is neither | {entry} under {root} ({kind}) | move the file named aside (the worker never removes what it did not write), then `weles worker restart`; GET /worker/status names the recovery failure | config, exit 1, HTTP 500 | tests/worker/failures.mjs::worker_restart_over_task_state_that_is_no_task_names_the_entry |
reservation-entry-unexpected | recovering the Idempotency-Key reservations under {root} | unexpected public task reservation entry: {entry}: the idempotency directory holds only reservation files, and this is none | {entry} under {root} ({kind}) | move the file named aside (the worker never removes what it did not write), then `weles worker restart`; GET /worker/status names the recovery failure | config, exit 1, HTTP 500 | tests/admission/records/state-failures.mjs::recovery_over_a_stray_reservation_entry_names_it |
reservation-invalid | reading the Idempotency-Key reservation {path} | its {field} is {found} where a reservation of this organization holds {expected}: it was edited, truncated or written by another deployment | {path}: {field} {found} | move the file named aside (the worker never removes what it did not write), then `weles worker restart`; GET /worker/status names the recovery failure | config, exit 1, HTTP 500 | tests/admission/records/state-failures.mjs::recovery_over_a_reservation_of_another_shape_names_the_field |
reservation-task-unmatched | reading public task {task} its reservation names | the task file under {root} is not the task its reservation names: {problem} | reservation digest {reserved}; task digest {recorded} | move the file named aside (the worker never removes what it did not write), then `weles worker restart`; GET /worker/status names the recovery failure | config, exit 1, HTTP 500 | tests/admission/records/state-failures.mjs::recovery_of_a_task_other_than_its_reservation_names_it |
reservation-unclosed | writing the Idempotency-Key reservation {path} | the write failed ({answer}) and its open file handle could not be closed either ({close}); the reservation was removed so the key can be sent again | {path}: write {answer}; close {close} | free space on the disk of {root} and check its file system (`df -h {root}`), then send the task again with the same Idempotency-Key | infra_down, exit 69, HTTP 500, retryable | tests/admission/records/state-failures.mjs::a_reservation_whose_handle_cannot_close_is_named |
reservation-vanished | reading the Idempotency-Key reservation another request created | the reservation another request created for this key disappeared under {root} before it could be read: something other than this worker removed it | no reservation for the key under {root} | find what removes files under {root} (a cleanup job, another worker sharing the home), then send the task again | config, exit 1, HTTP 500 | tests/admission/records/state-failures.mjs::a_reservation_removed_between_two_requests_is_named |
state-write-unpublished | writing {path} atomically | the {kind} write did not publish {path} ({answer}), and its temporary file {temporary} could not be removed either ({cleanup}) | {path}: {answer}; {temporary}: {cleanup} | free space on that disk and give the worker user write access to its directory, remove {temporary} by hand, then `weles worker restart` | infra_down, exit 69, HTTP 500, retryable | tests/admission/records/state-failures.mjs::an_atomic_write_whose_temporary_file_stays_is_named |
state-write-refused | writing {path} atomically | the system refused to {step} {target} ({errno}): the {kind} write creates the directory, writes and syncs a temporary file, renames it over {path} and syncs the directory, in that order, and {path} is unchanged | {target}: {errno}; system: {answer} | free space on that disk and give the account the worker runs as write access to {target}, then `weles worker restart`; GET /worker/status names the write recovery met | infra_down, exit 69, HTTP 500, retryable | tests/admission/records/state-failures.mjs::a_write_into_a_directory_the_worker_cannot_write_is_named |
state-read-refused | reading the public task state file {path} | the system refused to {step} it ({errno}), so the task or reservation it holds cannot be answered or recovered | {path}: {errno}; system: {answer} | give the account the worker runs as read access to {path} (`ls -l {path}` shows its owner and mode), then `weles worker restart` | infra_down, exit 69, HTTP 500, retryable | tests/admission/records/state-failures.mjs::a_task_file_the_worker_cannot_read_is_named |
task-status-unwired | answering the status of public task {task} | its status {status} is outside the wire vocabulary Spis reads ({statuses}), so it is not answered mid-crawl as one Spis cannot read | task {task}: status {status} | compare `weles worker version` with the release that wrote the task; a task of another release is read by that release | config, exit 1, HTTP 500 | tests/admission/records/state-failures.mjs::a_task_of_a_status_outside_the_wire_is_named |
task-receipt-missing | answering the terminal status of public task {task} | the task ended ({status}) without the retained-evidence receipt every terminal answer carries | task {task}: status {status}, no receipt | GET /worker/status names the retention failure of the task; retain its evidence again once the object store answers | config, exit 1, HTTP 500 | tests/admission/records/state-failures.mjs::a_terminal_task_without_its_receipt_is_named |
canonical-json-uncarried | writing {path} as canonical JSON | canonical JSON cannot carry {what}, and the value was built by Weles, not admitted from a request: a defect of this release | {path}: {what} | report it with `weles worker version` and the request that built the value; nothing was signed or answered with it | unknown, exit 1, HTTP 500 | tests/admission/records/state-failures.mjs::canonical_json_of_a_value_it_cannot_carry_is_named |
state-file-not-json | reading the public task state file {path} | it is not one JSON document: the worker writes it whole and renames it into place, so it was truncated or written by something else | {path} ({bytes} bytes) begins {head}; parser: {answer} | move the file named aside (the worker never removes what it did not write), then `weles worker restart`; GET /worker/status names the recovery failure | config, exit 1, HTTP 500 | tests/admission/records/state-failures.mjs::recovery_over_a_task_file_that_is_not_json_names_it |
evidence-root-unsafe | hashing task {task}'s evidence under {root} | the run directory is {kind}, not a directory of its own: a receipt is signed only over files the run wrote under it | {root} is {kind} | nothing to do for this task: it settled as failed with this code, its evidence is kept aside under {root}/../.public-task-retention-failures; fix what wrote the entry into the run directory, so the next run does not meet it | config, exit 1, HTTP 500 | tests/admission/retention-failures.mjs::a_run_directory_that_is_a_link_is_quarantined |
evidence-path-unsafe | hashing task {task}'s evidence under {root} | evidence path component is not portable: the entry {name} holds a character outside A-Z, a-z, 0-9, dot, underscore and hyphen, which a locator cannot carry unchanged | {name} in {directory} | nothing to do for this task: it settled as failed with this code, its evidence is kept aside under {root}/../.public-task-retention-failures; fix what wrote the entry into the run directory, so the next run does not meet it | config, exit 1, HTTP 500 | tests/admission/retention-failures.mjs::an_entry_whose_name_is_not_portable_is_quarantined |
evidence-symlink | hashing task {task}'s evidence under {root} | symlink is forbidden in evidence: {name} points outside the bytes the run wrote, so hashing it would sign whatever it points at | {name} in {directory} is a symbolic link to {target} | nothing to do for this task: it settled as failed with this code, its evidence is kept aside under {root}/../.public-task-retention-failures; fix what wrote the entry into the run directory, so the next run does not meet it | config, exit 1, HTTP 500 | tests/admission/retention-failures.mjs::a_link_inside_the_run_directory_is_quarantined |
evidence-entry-unsafe | hashing task {task}'s evidence under {root} | non-regular evidence entry is forbidden: {path} is {kind}, which holds no bytes a receipt could name | {path} under {root} is {kind} | nothing to do for this task: it settled as failed with this code, its evidence is kept aside under {root}/../.public-task-retention-failures; fix what wrote the entry into the run directory, so the next run does not meet it | config, exit 1, HTTP 500 | tests/admission/retention-failures.mjs::a_socket_inside_the_run_directory_is_quarantined |
evidence-file-unsafe | hashing task {task}'s evidence under {root} | evidence path is not a regular file: {path} was listed as a file but is {kind} when opened, so something replaced it while the run directory was read | {path} is {kind} | nothing to do for this task: it settled as failed with this code, its evidence is kept aside under {root}/../.public-task-retention-failures; fix what wrote the entry into the run directory, so the next run does not meet it | config, exit 1, HTTP 500 | tests/admission/retention-failures.mjs::a_file_replaced_while_listed_is_quarantined |
evidence-file-unstable | hashing task {task}'s evidence under {root} | evidence file changed while hashing: {path} has another size, inode or time after its bytes were read, so the digest names no one version of it | {path}: {before} before hashing, {after} after | nothing to do for this task: it settled as failed with this code, its evidence is kept aside under {root}/../.public-task-retention-failures; fix what wrote the entry into the run directory, so the next run does not meet it | config, exit 1, HTTP 500 | tests/admission/retention-failures.mjs::a_file_written_while_hashed_is_quarantined |
evidence-file-empty | hashing task {task}'s evidence under {root} | retained evidence file is empty: {path} holds 0 bytes, and the Spis bridge refuses an inventory entry of no bytes, so the receipt could never be verified | {path} under {root}: 0 bytes | nothing to do for this task: it settled as failed with this code, its evidence is kept aside under {root}/../.public-task-retention-failures; fix what wrote the entry into the run directory, so the next run does not meet it | config, exit 1, HTTP 500 | tests/admission/retention-failures.mjs::an_empty_evidence_file_is_quarantined |
withheld-edge-not-json | hashing task {task}'s evidence under {root} | withheld-edge evidence is not valid NDJSON: record {record} of {path} is not a JSON document, so the withheld edges the receipt counts cannot be read | {path} record {record} begins {head}; parser: {answer} | nothing to do for this task: it settled as failed with this code, its evidence is kept aside under {root}/../.public-task-retention-failures; fix what wrote the entry into the run directory, so the next run does not meet it | config, exit 1, HTTP 500 | tests/admission/retention-failures.mjs::a_withheld_edge_that_is_not_json_is_quarantined |
withheld-edge-not-object | hashing task {task}'s evidence under {root} | withheld-edge evidence is not valid NDJSON: record {record} of {path} is {kind}, not one object per edge | {path} record {record}: {head} | nothing to do for this task: it settled as failed with this code, its evidence is kept aside under {root}/../.public-task-retention-failures; fix what wrote the entry into the run directory, so the next run does not meet it | config, exit 1, HTTP 500 | tests/admission/retention-failures.mjs::a_withheld_edge_that_is_no_object_is_quarantined |
required-evidence-missing | hashing task {task}'s evidence under {root} | successful browser-evidence task lacks {missing}: a succeeded task is signed only over its final screenshot (artifacts/browser_evidence_final.png) and accessibility tree (artifacts/browser_evidence_accessibility_tree.txt) | {root} holds {count} file(s): {files} | nothing to do for this task: it settled as failed with this code, its evidence is kept aside under {root}/../.public-task-retention-failures; fix what wrote the entry into the run directory, so the next run does not meet it | config, exit 1, HTTP 500 | tests/admission/retention-failures.mjs::a_succeeded_task_without_its_screenshot_is_quarantined |
policy-evidence-missing | hashing task {task}'s evidence under {root} | successful browser-evidence task has no retained policy document: no browser_evidence_policy.json is under the run directory, so the receipt could not say which policy the capture obeyed | {root} holds {count} file(s): {files} | nothing to do for this task: it settled as failed with this code, its evidence is kept aside under {root}/../.public-task-retention-failures; fix what wrote the entry into the run directory, so the next run does not meet it | config, exit 1, HTTP 500 | tests/admission/retention-failures.mjs::a_succeeded_task_without_its_policy_is_quarantined |
evidence-identity-duplicate | hashing task {task}'s evidence under {root} | evidence inventory kind and URI identities must be unique, and {duplicates} repeat: each is derived from a distinct relative path, so this is a defect of this release | {count} inventory entries, {kinds} distinct kinds, {uris} distinct URIs | report it with `weles worker version` and the task id; the task settled as failed and its evidence is kept aside | unknown, exit 1, HTTP 500 | tests/admission/retention-failures.mjs::an_inventory_with_a_repeated_identity_is_quarantined |
evidence-manifest-unsafe | retaining task {task}'s evidence manifest {manifest} | existing evidence manifest is not a regular file: {manifest} is {kind}, and only a manifest this worker wrote can be compared with the one it computes | {manifest} is {kind} | nothing to do for this task: it settled as failed with this code, its evidence is kept aside under {root}/../.public-task-retention-failures; fix what wrote the entry into the run directory, so the next run does not meet it | config, exit 1, HTTP 500 | tests/admission/retention-failures.mjs::a_manifest_that_is_a_directory_is_quarantined |
evidence-manifest-conflict | retaining task {task}'s evidence manifest {manifest} | existing evidence manifest disagrees with recomputed immutable task identity: a manifest once written is never rewritten, and the one on disk is not the one this task computes | {manifest}: {found} bytes on disk, {expected} bytes computed (sha256 {digest}) | nothing to do for this task: it settled as failed with this code, its evidence is kept aside under {root}/../.public-task-retention-failures; fix what wrote the entry into the run directory, so the next run does not meet it | config, exit 1, HTTP 500 | tests/admission/retention-failures.mjs::a_manifest_of_another_task_is_quarantined |
evidence-changed | retaining task {task}'s evidence manifest {manifest} | evidence changed between hashing and durable retention: the run directory read again after the upload holds another inventory, so storage may hold bytes the manifest does not name | hashed {before}; after the upload {after} | nothing to do for this task: it settled as failed with this code, its evidence is kept aside under {root}/../.public-task-retention-failures; fix what wrote the entry into the run directory, so the next run does not meet it | config, exit 1, HTTP 500 | tests/admission/retention-failures.mjs::evidence_changed_during_the_upload_is_quarantined |
withheld-edge-changed | retaining task {task}'s evidence manifest {manifest} | withheld-edge evidence changed before receipt signing: the edges read after the readback are not the ones counted before the upload | {before} edge(s) before the upload, {after} after | nothing to do for this task: it settled as failed with this code, its evidence is kept aside under {root}/../.public-task-retention-failures; fix what wrote the entry into the run directory, so the next run does not meet it | config, exit 1, HTTP 500 | tests/admission/retention-failures.mjs::withheld_edges_changed_before_signing_are_quarantined |
evidence-unreadable | hashing task {task}'s evidence under {root} | the system refused to {step} {path}, so the inventory cannot be hashed; the evidence is left where it is | {path}: {errno}; system: {answer} | make {path} readable by the account the worker runs as; the task stays pending and is retried on its next read (`weles tasks show {task}`), sweep or `weles worker restart`; `weles doctor` shows whether the Stado object API answers | config, exit 1, HTTP 500 | tests/admission/retention-failures.mjs::an_unreadable_evidence_directory_leaves_the_task_pending |
evidence-quarantine-unmoved | moving task {task}'s evidence aside after {code} | the system refused to {step} {path}: create the quarantine directory, move the run directory into it, sync, create artifacts/ and write the diagnostic run in that order, and the steps before this one were done | {path}: {errno}; system: {answer} | free or make writable the file system holding {path}, then `weles worker restart`: recovery retains the task again; the read or recovery that met this answered it, and the task file was not changed | config, exit 1, HTTP 500 | tests/admission/retention-failures.mjs::evidence_that_cannot_be_moved_aside_is_named |
manifest-unacknowledged | retaining task {task}'s evidence manifest {manifest} | durable artifact storage did not acknowledge the exact evidence manifest: the upload answered locators, but none of them is {manifest} | locators answered: {locators} | the task stays pending and is retried on its next read (`weles tasks show {task}`), sweep or `weles worker restart`; `weles doctor` shows whether the Stado object API answers | infra_down, exit 69, HTTP 502, retryable | tests/admission/retention-failures.mjs::an_upload_that_omits_the_manifest_leaves_the_task_pending |
manifest-readback-differs | retaining task {task}'s evidence manifest {manifest} | durable evidence manifest readback differs from the retained canonical bytes: storage answers another object at {manifest} | read back {bytes} bytes, sha256 {sha256}; written {expected_bytes} bytes, sha256 {expected_sha256} | the task stays pending and is retried on its next read (`weles tasks show {task}`), sweep or `weles worker restart`; `weles doctor` shows whether the Stado object API answers | infra_down, exit 69, HTTP 502, retryable | tests/admission/retention-failures.mjs::a_manifest_read_back_otherwise_leaves_the_task_pending |
evidence-readback-differs | retaining task {task}'s evidence manifest {manifest} | durable evidence readback differs from inventory: {kind}: storage answers another object at {uri} | read back {bytes} bytes, sha256 {sha256}; inventory {expected_bytes} bytes, sha256 {expected_sha256} | the task stays pending and is retried on its next read (`weles tasks show {task}`), sweep or `weles worker restart`; `weles doctor` shows whether the Stado object API answers | infra_down, exit 69, HTTP 502, retryable | tests/admission/retention-failures.mjs::an_evidence_file_read_back_otherwise_leaves_the_task_pending |
tasks#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
document-unreadable | reading {what} from {path} | the file cannot be read: it does not exist, it is a directory, or this user may not read it | {answer} | name a file this user can read, or - for standard input | config, exit 1, HTTP 503 | tests/tasks/failures.mjs::a_request_file_that_cannot_be_read_is_named |
document-not-object | reading {what} from {path} | the file does not hold one JSON object: it is not JSON, or JSON of another shape | {answer} | write {what} as one JSON object | refused, exit 1, HTTP 403 | tests/tasks/failures.mjs::a_request_that_is_not_a_json_object_is_refused |
route-unnamed | finding the public task API for `weles tasks {verb}` | the command names neither --consumer (whose route Stado places) nor --endpoint, or names both | --consumer: {consumer}; --endpoint: {endpoint} | name exactly one: --consumer <the Stado consumer you call as> or --endpoint <the API address> | refused, exit 2, HTTP 403 | tests/tasks/failures.mjs::a_task_command_naming_no_route_is_refused |
route-unresolved | finding the public task API for consumer {consumer} | Stado places no weles-admission service consumer {consumer} can connect to from here | {answer} | run where `stado service directory connect weles-admission --consumer {consumer}` answers a url, or name the address with --endpoint | not_found, exit 1, HTTP 404 | tests/tasks/failures.mjs::a_consumer_stado_places_no_api_for_is_named |
task-id-invalid | reading the task id of `weles tasks {verb}` | a public task id is the lower-case UUID `weles tasks submit` answered as taskId, and this is not one | task id: {id} | name the taskId the submission answered | refused, exit 2, HTTP 403 | tests/tasks/failures.mjs::a_task_id_that_is_no_uuid_is_refused |
receipts#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
keys-invalid | reading the receipt keys {path} | a key map is one JSON object of key id to PEM public key, with at least one key; this one is empty or holds a key that is not text | {answer} | write the published receipt key set as {"<key id>": "-----BEGIN PUBLIC KEY-----..."} | refused, exit 1, HTTP 403 | tests/tasks/receipt-failures.mjs::a_key_map_without_keys_is_refused |
receipt-malformed | verifying the receipt {receipt} | a receipt is one object with schema, keyId, signature and signedPayload as text, and this one lacks one of them | {answer} | pass the receipt object a terminal task answered, whole and unchanged | refused, exit 1, HTTP 403 | tests/tasks/receipt-failures.mjs::a_receipt_lacking_its_signature_is_refused |
receipt-schema-unsupported | verifying the receipt {receipt} | the receipt names a schema this Weles does not verify (weles.receipt.current): another product wrote it, or a newer Weles | schema: {schema} | verify it with the Weles release that signed it (`weles version` names this one) | refused, exit 1, HTTP 403 | tests/tasks/receipt-failures.mjs::a_receipt_of_another_schema_is_refused |
receipt-key-unknown | verifying the receipt {receipt} | the key map holds no public key under the id the receipt was signed with: the key set was rotated after it was copied, or it is another deployment's | keyId: {key}; the map holds: {keys} | read the published receipt key set again (the role's receipt_public_keys_json) and pass it with --keys | refused, exit 1, HTTP 403 | tests/tasks/receipt-failures.mjs::a_receipt_signed_with_a_key_the_map_lacks_is_refused |
receipt-key-unusable | verifying the receipt {receipt} | the public key the map holds under the receipt's key id is not a PEM public key the verifier can use | keyId: {key}: {answer} | pass the published PEM public key under that id, unchanged | config, exit 1, HTTP 503 | tests/tasks/receipt-failures.mjs::a_key_that_is_not_a_public_key_is_named |
receipt-signature-invalid | verifying the receipt {receipt} | the signature does not verify against the signed payload with that key: the receipt was altered, or signed by another key under the same id | keyId: {key} | do not trust this receipt; read the task again (`weles tasks show <task-id>`) for the receipt Weles signed | refused, exit 1, HTTP 403 | tests/tasks/receipt-failures.mjs::an_altered_receipt_is_refused |
receipt-payload-not-json | verifying the receipt {receipt} | the signature verifies and the signed payload is not one JSON object, so the receipt states no claims | {answer} | report the receipt with `weles version`: a Weles that signs such a payload has a defect | refused, exit 1, HTTP 403 | tests/tasks/receipt-failures.mjs::a_receipt_whose_signed_payload_is_no_object_is_refused |
receipt-claim-mismatch | verifying the receipt {receipt} | a field the receipt displays differs from the claim its signed payload carries, so what it shows is not what was signed | field: {field} | trust only the signed claims; read the task again (`weles tasks show <task-id>`) for the receipt Weles signed | refused, exit 1, HTTP 403 | tests/tasks/receipt-failures.mjs::a_receipt_displaying_another_outcome_is_refused |
receipt-refused-other | verifying the receipt {receipt} | the verifier (weles-client) refused the receipt with {code}, a refusal this release does not know | it said: {answer} | install the Weles release made for the weles-client it carries (`stado product install weles --surface cli`) | unknown, exit 1, HTTP 500 | tests/tasks/receipt-failures.mjs::a_refusal_of_a_newer_verifier_is_named |
receipt-claim-missing | verifying the receipt {receipt} | the receipt verifies and its signed claims carry no non-empty {field}, which the first-use journey records | the claims hold {members} | report the receipt with `weles version`: a Weles signs every claim the journey records | refused, exit 1, HTTP 403 | tests/onboarding/stages/steps-failures.mjs::signed_claims_without_an_evidence_digest_are_refused |
Browser#
How a session driven through weles mcp fails. The JSON-RPC error message is the sentence; error.data carries the cause, remedy and docs. A failure the browser engine reports is classified by the engine's own words and quoted whole.
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
release-family-unknown | finding the selected release of browser family "{engine}" | Weles launches only a released chromium or firefox, and "{engine}" is neither | browser family "{engine}"; released families: {families} | name the browser as chromium or firefox | refused, exit 2, HTTP 403 | tests/browser/launching/release-failures.mjs::a_browser_family_weles_does_not_release_is_refused |
release-platform-unsupported | finding the selected weles-{engine} release | weles-{engine} releases are published for darwin-arm64, darwin-amd64 and linux-amd64 only, and this host is {platform} | process.platform {os}, process.arch {arch} | run the browser on a darwin-arm64, darwin-amd64 or linux-amd64 host | config, exit 1, HTTP 503 | tests/browser/launching/release-failures.mjs::a_host_of_another_platform_is_refused |
release-root-unnamed | finding the selected weles-{engine} release | {variable} is unset and HOME is unset or empty, so there is no install root to look in (it is {variable}, else $HOME/.local/share/weles-{engine}) | {variable} unset; HOME "{home}" | set {variable} to the directory weles-{engine} is installed in, or run with HOME set | config, exit 1, HTTP 503 | tests/browser/launching/release-failures.mjs::a_host_without_home_or_install_root_is_refused |
release-pin-partial | finding the selected weles-{engine} release | the deployment pins a release only with both {versionVariable} and {digestVariable}, and only {given} is set | {given} set; {missing} unset | set {missing} too, or unset {given} so the release {root}/selected records is used | config, exit 1, HTTP 503 | tests/browser/launching/release-failures.mjs::a_pin_with_only_its_version_is_refused |
release-unselected | finding the selected weles-{engine} release | no weles-{engine} release is selected: neither {versionVariable} and {digestVariable} are set nor does {record} exist | {record} absent; {variable} {rootSource} | install weles-{engine} with that release's release/install.sh, which verifies the archive, writes {root}/{version}/.weles-release and records the release in {root}/selected, or pin it with {versionVariable} and {digestVariable} | config, exit 1, HTTP 503 | tests/browser/launching/release-failures.mjs::a_host_without_the_browser_release_names_where_it_looked |
release-selection-unreadable | reading the selection record {record} | the selection record {record} exists and cannot be read as a file | {code}: {problem} | make {record} a file this user may read; install weles-{engine} with that release's release/install.sh, which verifies the archive, writes {root}/{version}/.weles-release and records the release in {root}/selected | config, exit 1, HTTP 503 | tests/browser/launching/release-failures.mjs::a_selection_record_that_is_a_directory_is_named |
release-selection-incomplete | reading the selection record {record} | the selection record {record} names no {missing}, and a release is used only by its version and its sha256 together | {record} reads {fields} | install weles-{engine} with that release's release/install.sh, which verifies the archive, writes {root}/{version}/.weles-release and records the release in {root}/selected; it writes version=<version> and sha256=<archive digest> | config, exit 1, HTTP 503 | tests/browser/launching/release-failures.mjs::a_selection_without_its_digest_is_named |
release-digest-invalid | finding the selected weles-{engine} release | the selected weles-{engine} {version} release names a checksum that is not 64 hex digits of a SHA-256, so no receipt can be checked against it | sha256 "{digest}" ({length} characters) from {source} | give the archive SHA-256 the weles-{engine} {version} release was published with, in {source} | config, exit 1, HTTP 503 | tests/browser/launching/release-failures.mjs::a_selection_whose_digest_is_no_sha256_is_named |
release-executable-absent | finding the selected weles-{engine} release | the selected weles-{engine} {version} release has no executable at {binary}: that version is not installed under {root}, or is installed for another platform layout | {binary} absent; {installDir} {installState} | install weles-{engine} with that release's release/install.sh, which verifies the archive, writes {root}/{version}/.weles-release and records the release in {root}/selected | config, exit 1, HTTP 503 | tests/browser/launching/release-failures.mjs::a_selected_release_without_its_executable_is_named |
release-executable-unexaminable | examining the executable {binary} | the executable of the selected release cannot be examined: a directory on its path may not be searched by this user | {code}: {problem} | let this user search every directory on the path of {binary}, or install weles-{engine} with that release's release/install.sh, which verifies the archive, writes {root}/{version}/.weles-release and records the release in {root}/selected into a directory it may | config, exit 1, HTTP 503 | tests/browser/launching/release-failures.mjs::an_executable_behind_a_closed_directory_is_named |
release-executable-not-file | finding the selected weles-{engine} release | {binary} is a {kind}, not the executable file the release installs | {binary}: {kind} | remove {binary} and install weles-{engine} with that release's release/install.sh, which verifies the archive, writes {root}/{version}/.weles-release and records the release in {root}/selected | config, exit 1, HTTP 503 | tests/browser/launching/release-failures.mjs::an_executable_that_is_a_directory_is_named |
release-receipt-absent | reading the receipt of weles-{engine} {version} | the install at {installDir} has no receipt {receipt}, which the install writes only after it verified the archive, so its executable is not used | {receipt} absent; {binary} present | install weles-{engine} with that release's release/install.sh, which verifies the archive, writes {root}/{version}/.weles-release and records the release in {root}/selected | config, exit 1, HTTP 503 | tests/browser/launching/release-failures.mjs::a_release_without_its_receipt_is_named |
release-receipt-unreadable | reading the receipt {receipt} | the receipt {receipt} exists and cannot be read as a file | {code}: {problem} | make {receipt} a file this user may read; install weles-{engine} with that release's release/install.sh, which verifies the archive, writes {root}/{version}/.weles-release and records the release in {root}/selected | config, exit 1, HTTP 503 | tests/browser/launching/release-failures.mjs::a_receipt_that_is_a_directory_is_named |
release-receipt-mismatch | checking the receipt {receipt} against the selected release | the receipt records another release than the one selected: the install under {installDir} is of another version, platform or archive digest | expected {expected}; found {found} | install weles-{engine} with that release's release/install.sh, which verifies the archive, writes {root}/{version}/.weles-release and records the release in {root}/selected | config, exit 1, HTTP 503 | tests/browser/launching/release-failures.mjs::a_receipt_of_another_release_is_named |
browser-engine-unknown | starting a browser | browser names no engine Weles launches | browser: {value} | give browser as chromium or firefox | refused, exit 2, HTTP 403 | tests/browser/launching/launch-failures.mjs::an_unknown_engine_is_refused_before_launch |
browser-os-unknown | starting a browser | os names no operating system a Weles persona presents | os: {value} | give os as macos, windows or linux | refused, exit 2, HTTP 403 | tests/browser/launching/launch-failures.mjs::an_unknown_os_is_refused_before_launch |
browser-window-unsized | starting a browser | the launch states no window size: WELES_VIEWPORT is unset, no persona was given, and the host screen is not read | Weles said: {answer} | set WELES_VIEWPORT=<width>x<height> in the environment of weles mcp (1280x800, say), or turn the honest host on so Weles reads the screen | config, exit 1, HTTP 503 | tests/browser/launching/launch-failures.mjs::a_launch_without_a_window_size_names_the_variable |
browser-launch-refused | starting a browser | Weles refused the launch before starting the engine: a setting the launch needs is missing or contradicts another, in Weles' own words below | Weles said: {answer} | set what the sentence names in the environment of weles mcp, then start the browser again | config, exit 1, HTTP 503 | tests/browser/launching/launch-failures.mjs::a_launch_weles_refuses_quotes_its_reason |
browser-profile-unusable | opening the browser profile {profile} | userDataDir cannot hold a profile: a file stands at that path, its parent is not a directory, or the server user may not write there | Weles said: {answer} | give userDataDir as a directory this server can create and write, or leave it out for a fresh profile | config, exit 1, HTTP 503 | tests/browser/launching/launch-failures.mjs::a_profile_path_that_is_a_file_is_named |
browser-launch-failed | launching {engine} | the browser executable started but exited or never opened its control channel: a damaged install, a missing system library, or a sandbox that refuses it | the engine said: {answer} | run the executable the message names by hand to see why it exits, then reinstall the selected weles-{engine} release through Stado | config, exit 1, HTTP 503 | tests/browser/launching/launch-failures.mjs::a_browser_that_exits_at_launch_is_named_with_the_engine_words |
recording-encoder-missing | {tool} in a recorded browser | the session records video and the ffmpeg encoder Playwright records with is not installed where PLAYWRIGHT_BROWSERS_PATH points | the engine said: {answer} | install it with `npx playwright install ffmpeg` (the release ships it under browser-runtime), or start weles mcp with WELES_DISABLE_RECORDING=1 | config, exit 1, HTTP 503 | tests/browser/launching/launch-failures.mjs::a_recorded_session_without_ffmpeg_names_the_encoder |
not-placed-here | {entrypoint} starting a browser on this host | the Stado registry places the Weles service on other hosts, and a browser started off its placement runs where nobody routes work | {entrypoint} is not placed on registry target '{target}'; Weles is placed on {placed} | run it where `stado service status com.wisent.weles --json` places Weles, or set WELES_STANDALONE=1 on a machine that runs Weles without Stado | refused, exit 1, HTTP 403 | tests/keeper/control/control-failures.mjs::a_browser_off_its_placement_is_refused |
navigation-url-invalid | opening {url} | the engine cannot navigate to it: it is not an absolute URL with a scheme the browser opens | the engine said: {answer} | give url as an absolute address such as https://example.com/ | refused, exit 2, HTTP 403 | tests/browser/navigation-failures.mjs::a_url_the_browser_cannot_open_is_refused |
navigation-host-unresolved | opening {url} | the host has no DNS answer: it does not exist, or this machine reaches no resolver (or the proxy the browser uses cannot resolve it) | the engine said: {answer} | check the host name; if it resolves elsewhere, check this machine's DNS or the proxy given to weles_browser_start | not_found, exit 1, HTTP 404 | tests/browser/navigation-failures.mjs::a_host_without_dns_is_named |
navigation-connection-refused | opening {url} | nothing accepts connections at that address and port: the site is down, or a firewall rejects the browser | the engine said: {answer} | start the service at that address or correct the port, then call again | infra_down, exit 69, HTTP 503, retryable | tests/browser/navigation-failures.mjs::a_closed_port_is_named |
navigation-certificate-refused | opening {url} | the browser refused the site's TLS certificate: it is self-signed, expired, issued for another host, or signed by an authority the browser does not trust | the engine said: {answer} | serve the site with a certificate a public authority issued for its host; Weles does not open a page whose certificate it cannot verify | refused, exit 1, HTTP 403 | tests/browser/navigation-failures.mjs::a_self_signed_certificate_is_named |
navigation-network-failed | opening {url} | the connection was made but broke before a response: the server closed it, reset it or sent no HTTP answer | the engine said: {answer} | check the server at that address answers HTTP (curl -v the URL), then call again | infra_down, exit 69, HTTP 503, retryable | tests/browser/navigation-failures.mjs::a_server_that_closes_the_connection_is_named |
page-request-failed | sending {method} {url} from page {page} | the request got no HTTP answer: the host has no DNS answer, nothing listens there, or the connection broke | the engine said: {answer} | check the address answers (curl -v the URL), then call again | infra_down, exit 69, HTTP 503, retryable | tests/browser/navigation-failures.mjs::an_in_page_request_to_a_closed_port_is_named |
storage-file-unreadable | reading the storage state {path} | the file cannot be read: it does not exist, or the server user may not read it | {answer} | give path as a storage-state file this server can read, such as one context.storageState({ path }) wrote | not_found, exit 1, HTTP 404 | tests/browser/page-failures.mjs::a_storage_file_that_does_not_exist_is_named |
storage-file-not-state | reading the storage state {path} | the file is not a Playwright storage state: it is not JSON, or it lacks the cookies and origins arrays | {answer} | give a file of the form { "cookies": [...], "origins": [...] }, as context.storageState() writes it | refused, exit 1, HTTP 403 | tests/browser/page-failures.mjs::a_storage_file_that_is_not_a_state_is_named |
selector-invalid | {tool} on page {page} | the selector is not one the engine parses | the engine said: {answer} | give a Playwright selector: CSS, role=button[name="Start"], text=Continue or internal:label="Email" | refused, exit 2, HTTP 403 | tests/browser/page-failures.mjs::a_selector_the_engine_cannot_parse_is_named |
page-script-threw | {tool} on page {page} | the expression threw inside the page | the page said: {answer} | correct the expression; run it in the page console to see the same error | refused, exit 1, HTTP 403 | tests/browser/page-failures.mjs::an_expression_that_throws_is_answered_with_the_page_error |
browser-closed | {tool} on page {page} | the page or its browser is gone: it was closed, or the browser process exited or crashed | the engine said: {answer} | open the browser and page again with weles_browser_start and weles_page_new | not_found, exit 1, HTTP 404 | tests/browser/page-failures.mjs::a_page_whose_browser_closed_is_named |
upload-file-missing | {tool} on page {page} | a file to upload does not exist or cannot be read by the server user | the engine said: {answer} | give files as absolute paths of readable files on the machine weles mcp runs on | not_found, exit 1, HTTP 404 | tests/browser/page-failures.mjs::an_upload_of_a_missing_file_is_named |
engine-refused | {tool} on page {page} | the browser engine refused the operation for a reason Weles does not classify further, stated in its own words below | the engine said: {answer} | read the engine's words: they name the element, state or value it refused; correct that argument and call again | refused, exit 1, HTTP 403 | tests/browser/page-failures.mjs::an_engine_refusal_is_quoted_whole |
secret-field-missing | reading the secret weles_page_fill_secret fills | Skarbiec item {item} holds no text in field {field}; it holds {fields} | the fields it holds: {fields} | name a field the item holds, or store the secret in field {field} | not_found, exit 1, HTTP 404 | tests/browser/page-failures.mjs::a_secret_fill_from_a_field_the_item_lacks_names_its_fields |
audit-unavailable | auditing the accessibility of page {page} | axe-core could not be put in the page: {reason} | the auditor this release carries, {path}; no audit was performed and nothing is reported as passing | install a Weles release whose vendor/axe-core/axe.min.js is present and readable, or open a page whose policy lets an injected script run | not_found, exit 1, HTTP 404 | tests/browser/page-failures.mjs::an_audit_without_its_auditor_names_the_file |
request-not-json | reading one request line | the line is not a JSON document, so it names no method and no id to answer | the parser said: {answer} | write one JSON-RPC 2.0 request per line, such as {"jsonrpc":"2.0","id":1,"method":"tools/list"} | refused, exit 2, HTTP 403 | tests/browser/protocol-failures.mjs::a_line_that_is_not_json_is_answered_with_the_parser_reason |
method-unknown | answering method {method} | weles mcp answers only initialize, ping, tools/list and tools/call | method: {method} | send one of initialize, ping, tools/list or tools/call | refused, exit 2, HTTP 403 | tests/browser/protocol-failures.mjs::an_unknown_method_names_the_methods_served |
call-without-params | calling a tool | tools/call carries no params, so it names no tool | params: none | send params { "name": "<tool>", "arguments": { ... } }; tools/list names every tool | refused, exit 2, HTTP 403 | tests/browser/protocol-failures.mjs::a_tool_call_without_params_names_what_it_needs |
tool-unknown | calling tool {tool} | weles mcp serves no tool of that name | tools served: {tools} | call one of the tools tools/list answers | refused, exit 2, HTTP 403 | tests/browser/protocol-failures.mjs::an_unknown_tool_names_the_tools_served |
argument-missing | reading argument {name} of {tool} | {name} is required and was not given, or was given empty | {name}: {value} | give {name} as its schema in tools/list states | refused, exit 2, HTTP 403 | tests/browser/protocol-failures.mjs::a_missing_argument_is_named |
argument-type | reading argument {name} of {tool} | {name} must be {expected} | {name}: {value} | give {name} as {expected} | refused, exit 2, HTTP 403 | tests/browser/protocol-failures.mjs::an_argument_of_the_wrong_type_is_named |
browser-id-unknown | finding browser {id} | this server never handed out that browserId, or the browser was closed, or the server restarted since | browsers open in this server: {open} | start a browser with weles_browser_start and use the browserId it answers | not_found, exit 1, HTTP 404 | tests/browser/protocol-failures.mjs::an_unknown_browser_id_lists_the_open_ones |
page-id-unknown | finding page {id} | this server never handed out that pageId, or its browser was closed, or the server restarted since | pages open in this server: {open} | open a page with weles_page_new and use the pageId it answers | not_found, exit 1, HTTP 404 | tests/browser/protocol-failures.mjs::an_unknown_page_id_lists_the_open_ones |
open-url-missing | reading the weles open command line | weles open needs the <url> of the page to open | positional arguments: {given} | run `weles open <url> --os macos|windows|linux --browser chromium|firefox` | refused, exit 2, HTTP 403 | tests/browser/launching/open-failures.mjs::open_without_a_url_names_what_it_takes |
open-locale-invalid | reading --locale of weles open | --locale is not a BCP 47 language tag | --locale: {value}; {answer} | give --locale a tag such as pl-PL or de | refused, exit 2, HTTP 403 | tests/browser/launching/open-failures.mjs::open_with_a_locale_that_is_no_language_tag_is_refused |
open-close-failed | closing the browser weles open launched | the page was read, and closing its browser failed in the engine words below, so its recording may be unfinished | the engine said: {answer} | read the engine words; a browser process left running is ended with `pkill -f <its profile>` | config, exit 1, HTTP 503 | tests/browser/launching/open-failures.mjs::open_whose_browser_cannot_close_names_it |
observed-target-ambiguous | resolving the observed control {target} on {url} | [observed_target_ambiguous] Multiple controls share this description; include the observed index: {count} control(s) in {where} are described alike | {target}: {count} matching controls in {where} | observe the page again and name the control by the description, with its [n] index, the new observation prints | refused, exit 2, HTTP 403 | tests/browser/controls-failures.mjs::a_description_two_controls_share_is_refused |
observed-target-invalid | resolving the observed control {target} on {url} | [observed_target_invalid] Invalid control index: {index} is beyond the integers a page can number | index {index} in {target} | observe the page again and name the control by the description, with its [n] index, the new observation prints | refused, exit 2, HTTP 403 | tests/browser/controls-failures.mjs::an_index_no_page_numbers_is_refused |
observed-target-stale | resolving the observed control {target} on {url} | [observed_target_stale] The control is stale or absent; observe again: none of the {frames} frame(s) on {origin} holds a control with this description now | {target}: {frames} frame(s) searched, {skipped} on other origins skipped | observe the page again and name the control by the description, with its [n] index, the new observation prints | refused, exit 2, HTTP 403 | tests/browser/controls-failures.mjs::a_control_the_page_replaced_is_stale |
observed-target-frames | resolving the observed control {target} on {url} | [observed_target_ambiguous] The control matches multiple frames; use a frame-specific description: {count} frames hold it | {target}: frames {frames} | observe the page again and name the control by the description, with its [n] index, the new observation prints | refused, exit 2, HTTP 403 | tests/browser/controls-failures.mjs::a_description_two_frames_share_is_refused |
selector-target-invalid | resolving the selector {target} on {url} | [selector_target_invalid] Cannot resolve {target}: the browser refused the selector in frame {frame}; no pointer input was sent | frame {frame}: {answer} | name the control with a selector the browser takes (css=, xpath=, text=, id=, or button[label="..."]), or by its observed description | refused, exit 2, HTTP 403 | tests/browser/controls-failures.mjs::a_selector_the_browser_refuses_is_named |
selector-target-ambiguous | resolving the selector {target} on {url} | [selector_target_ambiguous] Multiple visible controls match {target}; no pointer input was sent | {visible} visible of {count} matching element(s) in {frames} frame(s) | narrow the selector to one visible control, or name it by its observed description with its [n] index | refused, exit 2, HTTP 403 | tests/browser/controls-failures.mjs::a_selector_two_visible_controls_match_is_refused |
selector-target-absent | resolving the selector {target} on {url} | [selector_target_absent] No visible control matches {target}; no pointer input was sent. Use a current observed control or a correct selector | {count} matching element(s) in {frames} frame(s), none visible | observe the page again and name the control by the description, with its [n] index, the new observation prints | refused, exit 2, HTTP 403 | tests/browser/controls-failures.mjs::a_selector_no_visible_control_matches_is_refused |
session#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
proxy-unavailable | resolving the proxy {requested} for {target} | the run asked for a proxy and none of its exits resolved, so the browser is not launched without one | requested {requested} {targetState}; persona {browser} on {os}; the pre-launch session_meta.json of {label} records the request | run again once a {requested} exit for {target} answers, or start the session with another proxy request | infra_down, exit 69, HTTP 503, retryable | tests/session/launch-failures.mjs::a_session_whose_proxy_resolves_to_nothing_is_named |
browser-path-override-retired | finding the {engine} executable for session {label} | CHROMIUM_PATH is set, and an explicit browser path override is retired: a browser is launched only from the selected, checksum-verified weles-{engine} release | CHROMIUM_PATH={path} | unset CHROMIUM_PATH; pin the release with WELES_{ENGINE}_RELEASE_VERSION and WELES_{ENGINE}_RELEASE_SHA256, or let the host selection record name it | config, exit 1, HTTP 503 | tests/session/launch-failures.mjs::a_session_with_a_browser_path_override_is_refused |
profile-platform-unnamed | filing the browser profile of account session ACCOUNT_ID | ACCOUNT_PROFILE_PLATFORM_UNNAMED: an account session files its browser profile under its platform, and neither WSession.start platform, the ACTION it runs, nor targetHost names one | platform {platform}; ACTION {action}; targetHost {targetHost} | pass platform to WSession.start (or targetHost), or run it under an ACTION whose first word is the platform | refused, exit 1, HTTP 403 | tests/session/launch-failures.mjs::an_account_session_without_a_platform_is_refused |
profile-exists | making a fresh browser profile at {directory} | WELES_FRESH_PROFILE=1 asks for a fresh browser profile, and the profile directory of this account already exists, so the run would not start fresh | {directory} exists (account key {accountKey}, platform {platform}, browser {browser}) | remove {directory} to start the account from a fresh profile, or unset WELES_FRESH_PROFILE to reuse it | refused, exit 1, HTTP 403 | tests/session/launch-failures.mjs::a_fresh_profile_that_already_exists_is_refused |
profile-unmade | {step} the browser profile directory {path} | the browser profile directory cannot be made owner-only: a file stands on its path, or this user may not write {root} | {code}: {problem}; profile root from {rootSource} | make {root} a directory this user may write (or name another with WELES_BROWSER_PROFILE_ROOT), then start the session again | config, exit 1, HTTP 503 | tests/session/launch-failures.mjs::a_profile_root_that_is_a_file_is_named |
operator-cdp-token-shared | reading the operator browser WELES_OPERATOR_CDP_URL names | WELES_OPERATOR_CDP_TOKEN holds the same secret as {sibling}, and one secret must not admit two services | WELES_OPERATOR_CDP_TOKEN equals {sibling} ({bytes} bytes; the value is withheld) | give WELES_OPERATOR_CDP_TOKEN a secret of its own (`openssl rand -hex 32` prints one) and the operator browser the same, then start again | config, exit 1, HTTP 503 | tests/session/operator-cdp-failures.mjs::an_operator_token_shared_with_another_service_is_refused |
operator-cdp-url-invalid | reading the operator browser WELES_OPERATOR_CDP_URL names | WELES_OPERATOR_CDP_URL is not an absolute URL, so there is no browser endpoint to connect to | WELES_OPERATOR_CDP_URL: {length} characters, scheme {scheme}, {userPart} (the value is withheld: it may hold a password) | set WELES_OPERATOR_CDP_URL to the operator browser endpoint, wss://<host>/<path> or ws://127.0.0.1:<port>/<path> on loopback | config, exit 1, HTTP 503 | tests/session/operator-cdp-failures.mjs::an_operator_address_that_is_no_url_is_refused |
operator-cdp-url-insecure | reading the operator browser WELES_OPERATOR_CDP_URL names | WELES_OPERATOR_CDP_URL must use TLS, except for authenticated loopback (localhost or an address of one of this machine's internal network interfaces), and {endpoint} is neither | scheme {scheme}, host {host}, loopback {loopback} | serve the operator browser over wss:// (or https://), or reach it on a loopback address with ws:// | config, exit 1, HTTP 503 | tests/session/operator-cdp-failures.mjs::an_operator_address_without_tls_is_refused |
operator-cdp-url-extras | reading the operator browser WELES_OPERATOR_CDP_URL names | WELES_OPERATOR_CDP_URL must not contain credentials, query, or fragment: the token is WELES_OPERATOR_CDP_TOKEN, and anything else in the address would be sent to the browser endpoint | {endpoint} carries {extras} | remove {extras} from WELES_OPERATOR_CDP_URL and put the secret in WELES_OPERATOR_CDP_TOKEN | config, exit 1, HTTP 503 | tests/session/operator-cdp-failures.mjs::an_operator_address_with_credentials_is_refused |
protocol-session-absent | attaching the protocol observers of session {label} | the protocol setup of the session ended without a protocol session and without saying why, a defect of the release: the constructor records either one or its failure | session {label}: no protocol session, no attach error | run the session again; if it repeats, report it with the run's session_meta.json and the [wsession] lines of its log | unknown, exit 1, HTTP 500 | tests/session/launch-failures.mjs::a_protocol_setup_that_names_nothing_is_named |
context-protocol-unavailable | opening a protocol session on the page of session {label} | the browser context does not expose newCDPSession: the engine is not Chromium, so the protocol observers (proxy bytes, network capture) have nothing to attach to | browser {browser}; context without newCDPSession | run sessions that need the protocol observers in weles-chromium (browser chromium) | config, exit 1, HTTP 503 | tests/session/launch-failures.mjs::a_context_without_a_protocol_session_is_named |
sessions-not-map | reading the saved browser sessions in the vault setting browser_sessions | the vault setting browser_sessions does not map session labels to cookie lists, and answering it as empty would make the next save drop every other label | it holds {shape}{labels} | rewrite the value_json of the setting record browser_sessions as {"<label>": [<cookie>, ...]}, or remove that record to start with no saved sessions; `stado credentials ls --json` lists the records of the vault Weles uses | config, exit 1, HTTP 503 | tests/session/cookies-failures.mjs::saved_sessions_that_are_no_map_are_refused |
cookies-unanswered | capturing the cookies of session {label} | Network.getAllCookies answered no cookies list for session {label}; the saved session is kept, since saving none would sign the account out of every later run | the answer is {shape} | save the session again once its page is open; the browser answers the list only while its context lives | unknown, exit 1, HTTP 500 | tests/session/cookies-failures.mjs::a_browser_that_answers_no_cookie_list_is_named |
cookies-incomplete | putting the saved cookies of session {label} into the browser | session {label} in the vault setting browser_sessions holds cookies without a name, value or domain, which Playwright cannot set, and a session missing one of its cookies signs in as nobody | {count} of {total} cookies lack one: {cookies} | sign the session in again and save it, which rewrites its cookies whole | config, exit 1, HTTP 503 | tests/session/cookies-failures.mjs::a_saved_cookie_without_its_domain_is_refused |
persona-unsigned | signing the persona a cookie jar is bound to | COOKIE_PERSONA_UNSIGNED: the persona carries no canvas seed to bind a cookie jar to, so it cannot be told from another persona | persona fields: {fields} | generate the persona through the persona generator, which gives every persona a canvas seed | refused, exit 1, HTTP 403 | tests/session/cookies-failures.mjs::a_persona_without_a_canvas_seed_is_refused |
proxy-binding-unparsed | signing the proxy a cookie jar is bound to | the {form} the jar is bound to is not a URL, so it names no host and port to sign | {form}: {length} characters, {userPart} (the value is withheld: it may hold a password) | give the proxy as scheme://user:password@host:port, or as a record of host and port | config, exit 1, HTTP 503 | tests/session/cookies-failures.mjs::a_proxy_that_is_no_url_is_refused |
click-target-empty | clicking on {url} | [target_empty] A click requires a non-empty target; no pointer input was sent | target {target} | name the control to click: its observed description, a selector, or its text | refused, exit 2, HTTP 403 | tests/session/input-failures.mjs::a_click_without_a_target_is_refused |
fill-target-empty | filling a field on {url} | [target_empty] A fill requires a non-empty target; no input was sent | target {target} | name the field to fill: its label, placeholder, form name or a selector | refused, exit 2, HTTP 403 | tests/session/input-failures.mjs::a_fill_without_a_target_is_refused |
input-target-stale | filling {target} on {url} | [input_target_stale] The field disappeared before literal input: the page replaced or removed it after it was found | {target} found by {foundBy}, then gone | wait for the page to settle (wait_for the field), then fill again | refused, exit 1, HTTP 403 | tests/session/input-failures.mjs::a_field_the_page_removes_before_input_is_named |
fill-origin-not-web | filling {target} | fill requires an HTTP(S) origin, and the page is a {protocol} page, where no web origin bounds what is typed | page {page} | navigate to the http(s) page that holds the field, then fill | refused, exit 1, HTTP 403 | tests/session/input-failures.mjs::a_fill_on_a_page_that_is_no_web_page_is_refused |
focus-element-absent | checking the focused field before typing on {url} | literal input requires an observable focused element, and after the click nothing on the page holds focus, so what would be typed into cannot be checked | document.activeElement is none in frame {frame} | click the field itself (name it by its label or a selector) before typing | refused, exit 1, HTTP 403 | tests/session/input-failures.mjs::typing_with_nothing_focused_is_refused |
focus-frame-absent | checking the focused field before typing on {url} | literal input requires an observable focused frame, and the page answered no main frame to read focus in, a state a live page does not reach | page {url} without a main frame | open the page again and repeat the step; if it repeats, report it with the run log | unknown, exit 1, HTTP 500 | tests/session/input-failures.mjs::a_page_without_a_main_frame_is_named |
existing-text-unselected | selecting the text {target} holds before typing on {url} | the keyboard could not select the text the field holds, and typing after a failed select-all would append to it, so nothing is typed | the engine said: {answer} | clear the field another way (fill it by its label or selector) and fill again | refused, exit 1, HTTP 403 | tests/session/input-failures.mjs::a_field_whose_text_cannot_be_selected_is_named |
selector-unmatched | filling the field {css} on {url} | no element matches {css} on the page; nothing was filled | 0 elements match {css} on {url} | observe the page and name the field by a selector it has, or wait for it first | not_found, exit 1, HTTP 404 | tests/session/input-failures.mjs::a_fill_of_a_selector_nothing_matches_is_named |
account-unpatched | updating account {account} | account {account} was not updated: writing its {fields} raised an error the vault layer declares no cause for, and the token the run just received is not kept | {answer} | run `weles doctor` to see whether the vault Weles uses answers, then run the step again; the observed error names what the write met | unknown, exit 1, HTTP 500 | tests/session/input-failures.mjs::an_account_update_the_vault_refuses_is_named |
identity-unavailable | filling {target} from the generated identity | generated identity is unavailable: the session generated none, since it was started without a platform and no step called generateIdentity | field {field}; session {label} holds no identity | start the session with its platform, or generate the identity first | refused, exit 1, HTTP 403 | tests/session/input-failures.mjs::an_identity_fill_without_an_identity_is_refused |
identity-placeholder-unresolved | putting the session values into a step argument | unresolved generated identity placeholder: the argument names a $<PLATFORM>_NEW_<FIELD> value this session did not generate | unresolved {placeholders}; generated for {generated} | generate the identity of that platform first, or name the platform the session generated | refused, exit 1, HTTP 403 | tests/session/input-failures.mjs::a_placeholder_of_an_identity_never_generated_is_refused |
sms-order-missing | reading the code texted to the session number | SMS_ORDER_MISSING: this session rented no number (checkSms rents one), so there is no number a code was texted to | session {label}: no rented number | call checkSms for the service and country first, then textedCode | refused, exit 1, HTTP 403 | tests/session/input-failures.mjs::a_texted_code_without_a_rented_number_is_refused |
credential-origin-not-web | filling {target} from {source} | credential fill requires an HTTP(S) origin, and the page is a {protocol} page, which bounds no web origin a capability is minted for | page {page}; the capability was not consumed | navigate to the http(s) sign-in page that holds the field, then fill it | refused, exit 1, HTTP 403 | tests/session/credential-fill-failures.mjs::a_credential_fill_on_a_page_that_is_no_web_page_is_refused |
credential-target-unusable | filling the {fieldClass} field {target} at {origin} | [credential_target_unusable] {target} is not a visible editable field; capability not consumed | visible {visible}, editable {editable} | wait for the field to be shown and enabled (wait_for it), then fill it again | refused, exit 1, HTTP 403 | tests/session/credential-fill-failures.mjs::a_hidden_credential_field_is_refused |
credential-control-not-element | reading the markup of the control bound to {target} | the bound control is a {node} node, not an element, so it has no markup to declare a credential class | {target} bound to a {node} node | name the input element itself: observe the page and use its description, or a selector of the input | refused, exit 1, HTTP 403 | tests/session/credential-fill-failures.mjs::a_target_bound_to_a_text_node_is_refused |
credential-target-mismatch | filling the {fieldClass} field {target} at {origin} | [credential_target_mismatch] {target} is a {type} control with autocomplete {autocomplete}; a {fieldClass} goes only into a field whose markup declares input type {types} or autocomplete {names}{plainText}. The capability was not consumed | {target}: type {type}, autocomplete {autocomplete} | name the field whose markup declares a {fieldClass} (observe the page for it), or fill this field with the class its markup declares | refused, exit 1, HTTP 403 | tests/session/credential-fill-failures.mjs::a_credential_into_a_field_of_another_class_is_refused |
literal-into-credential-field | typing literal text into {target} | [credential_target] a {type} control with autocomplete {autocomplete} declares a {fieldClass} field; credential fields require fill_credential with a capability reference | {target}: type {type}, autocomplete {autocomplete} | fill the field with fill_credential and a capability reference for the {fieldClass}, never with literal text | refused, exit 1, HTTP 403 | tests/session/credential-fill-failures.mjs::literal_text_into_a_password_field_is_refused |
credential-target-ambiguous | filling the {fieldClass} field {target} at {origin} | [credential_target_ambiguous] Multiple editable fields match {target}; capability not consumed | {target} matched by {query} in {frames} frame(s) of {origin} | name the one field: observe the page and use its description with its [n] index, or a selector only it matches | refused, exit 1, HTTP 403 | tests/session/credential-fill-failures.mjs::a_target_two_editable_fields_match_is_refused |
credential-material-empty | typing into the credential field {target} at {origin} | the capability redeemed empty credential material, so there is nothing to type; the capability was spent and must not be retried | the value is empty ({source}) | store the credential in the item the capability names, then mint a new capability for it | refused, exit 1, HTTP 403 | tests/session/credential-fill-failures.mjs::an_empty_credential_is_refused |
credential-field-changed | typing into the credential field {target} at {origin} | the bound page or field changed before input: the field left the document, or the page moved to another origin; the capability was spent and must not be retried | field connected {connected}; field origin {fieldOrigin}; page origin {pageOrigin} | wait for the sign-in page to settle, then fill again with a new capability | refused, exit 1, HTTP 403 | tests/session/credential-fill-failures.mjs::a_field_the_page_replaces_before_input_is_named |
credential-not-retained | typing into the credential field {target} at {origin} | the bound field did not retain the credential after typing: the page rewrote or cleared it, or limits its length; the capability was spent and must not be retried | the field holds another value after typing ({length} characters typed; neither value is shown) | check the field's maxlength and the page's input handling, then fill again with a new capability | refused, exit 1, HTTP 403 | tests/session/credential-fill-failures.mjs::a_field_that_drops_the_typed_value_is_named |
credential-typing-threw | typing into the credential field {target} at {origin} | typing into the field or reading it back threw: the field left the page, the page navigated, or the browser closed while typing; the capability was spent and must not be retried | {thrown}; its message is withheld because it may hold the credential | open the sign-in page again and fill the field with a new capability | refused, exit 1, HTTP 403 | tests/session/credential-fill-failures.mjs::a_browser_closed_while_typing_is_named |
identity-input-failed | filling {target} at {origin} with the generated {field} | identity {field} input failed: the field could not be found, focused or typed into; the capability was spent and must not be retried | {thrown}; its message is withheld because it may hold the value | observe the page and name the field by its description or a selector, then fill it again | refused, exit 1, HTTP 403 | tests/session/credential-fill-failures.mjs::an_identity_field_that_refuses_input_is_named |
har-unread | withholding the request bodies of the HAR {path} | the HAR exists and cannot be read; the HAR was removed rather than kept with request bodies a password may be in | {code}: {problem} | make the recordings directory readable by the worker user; the next run records a new HAR | infra_down, exit 69, HTTP 503, retryable | tests/session/capture-failures.mjs::a_har_that_cannot_be_read_is_removed_and_named |
har-not-json | withholding the request bodies of the HAR {path} | the HAR is not one JSON document: Playwright had not sealed it, or it was cut off; the HAR was removed rather than kept with request bodies a password may be in | {bytes} bytes, not JSON (the parser's words are withheld: they quote the HAR, which may hold a typed password) | close the session before its HAR is scrubbed; a run that ends normally closes its context first | unknown, exit 1, HTTP 500 | tests/session/capture-failures.mjs::a_har_that_is_not_json_is_removed_and_named |
har-entries-absent | withholding the request bodies of the HAR {path} | the HAR holds no log.entries array, so its request bodies cannot be found; the HAR was removed rather than kept with request bodies a password may be in | the document holds {shape} | record the HAR with Playwright (recordHar), which writes log.entries | unknown, exit 1, HTTP 500 | tests/session/capture-failures.mjs::a_har_without_entries_is_removed_and_named |
har-unrewritten | withholding the request bodies of the HAR {path} | the scrubbed HAR could not be written beside it and moved into place; the HAR was removed rather than kept with request bodies a password may be in | {step} {scratch}: {code}: {problem} | free space on the disk of the recordings directory, or make it writable by the worker user | infra_down, exit 69, HTTP 503, retryable | tests/session/capture-failures.mjs::a_har_whose_rewrite_is_refused_is_removed_and_named |
har-unremoved | removing the HAR {path} whose request bodies could not be withheld | the HAR could not be scrubbed and could not be removed either, so it stays in the recordings with request bodies a typed password may be in | removal: {code}: {problem}; why it was removed: {why} | remove {path} by hand now (rm), then make the recordings directory writable by the worker user | infra_down, exit 69, HTTP 503, retryable | tests/session/capture-failures.mjs::a_har_that_cannot_be_removed_is_named |
artifact-circular | writing the instrumentation artifact {path} | the value holds itself at {member}, which JSON cannot carry (Converting circular structure to JSON); a defect of the recorder that built it | a circular structure at {member}; {path} is left as written so far | report the artifact {path} and the recorder that built it, naming the member above | unknown, exit 1, HTTP 500 | tests/session/capture-failures.mjs::a_circular_artifact_is_named_with_its_member |
artifact-unwritten | {step} the instrumentation artifact {path} | the artifact file could not be {stepDone}: its directory is missing or not writable, or the disk is full | {code}: {problem} | make the recordings directory exist and be writable by the worker user, and free space on its disk | infra_down, exit 69, HTTP 503, retryable | tests/session/capture-failures.mjs::an_artifact_whose_directory_is_missing_is_named |
instrumentation-flush-unavailable | reading the instrumentation log of frame {frame} | weles.inst.flush is unavailable in the frame: the instrumentation script was not injected there, or the page replaced it | frame {frame}: the engine said: {answer} | keep page diagnostics on for the run (WELES_PAGE_DIAGNOSTICS unset, WELES_NO_INSTRUMENT unset); the other frames are still read | unknown, exit 1, HTTP 500 | tests/session/capture-failures.mjs::a_frame_without_the_instrumentation_is_named |
instrumentation-log-not-list | reading the instrumentation log of frame {frame} | weles.inst.flush returned a non-array log: the page overwrote the instrumentation global, or another release injected it | frame {frame}: {shape} | run the page again; a page that defines Symbol.for("weles.inst") itself cannot be recorded | unknown, exit 1, HTTP 500 | tests/session/capture-failures.mjs::a_frame_whose_log_is_no_list_is_named |
instrumentation-log-not-json | reading the instrumentation log of frame {frame} | weles.inst.flush answered text that is not JSON, so the frame log is not kept | frame {frame}: {bytes} characters, not JSON (the parser's words are withheld: they quote the page's log) | run the page again; if it repeats, report the frame and the run log | unknown, exit 1, HTTP 500 | tests/session/capture-failures.mjs::a_frame_whose_log_is_no_json_is_named |
step-record-unwritten | recording that step {step} failed without its artifacts | step {step} failed and its missing-artifact record could not be written either, so the run directory says nothing of why its artifacts are missing | the step failed: {stepReason}; and the record write failed: {ledgerReason} | make the run directory writable by the worker user and free space on its disk, then run the trajectory again | infra_down, exit 69, HTTP 503, retryable | tests/session/capture-failures.mjs::a_step_whose_failure_record_cannot_be_written_is_named |
artifact-record-unwritten | recording that the {phase} {missing} of step {step} is missing | the {phase} {missing} of step {step} could not be taken, and the record naming why could not be written either, so the run directory would say nothing of the gap | the record write failed: {ledgerReason} | make the run directory writable by the worker user and free space on its disk, then run the trajectory again | infra_down, exit 69, HTTP 503, retryable | tests/session/capture-failures.mjs::a_step_whose_artifact_record_cannot_be_written_is_named |
agent#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
inference-unanswered | asking a model through `{command}` | no inference alternative in Stado's selected order answered Weles's request: each one is named below with why it did not | `{command}` exited {status}; Stado cause {functionality}.{stado} ({code}, retryable {retryable}): {answer}; alternatives: {alternatives} | repair what Stado names for each alternative ({fix}); `stado capabilities resolve inference --consumer weles` shows them again, and a run someone attends answers the kept request with `stado capabilities attestation record inference --consumer weles <request>` and WELES_AWAIT_STATEMENT=1 | infra_down, exit 69, HTTP 503, retryable | tests/agent/inference-failures.mjs::a_model_request_no_alternative_answers_names_each |
inference-refused | asking a model through `{command}` | Stado refused the request for a declared cause other than no alternative answering: its cause id and fix below say which | `{command}` exited {status}; Stado cause {functionality}.{stado} ({code}, retryable {retryable}): {answer} | do what Stado says ({fix}); `stado errors show {functionality}` lists every way it refuses | refused, exit 1, HTTP 403 | tests/agent/inference-failures.mjs::a_request_stado_refuses_relays_its_cause |
inference-unenveloped | asking a model through `{command}` | Stado exited refusing without the declared JSON envelope --json promises: a Stado too old for an option Weles passes (such as --operation), or one that failed before its own error handling | `{command}` exited {status} and printed {printed} characters that are no Stado envelope; its standard error ends: {said} | run `stado capabilities call --help` to see the options this Stado takes, and update Stado to the release named as minimum_stado_version in Weles's release manifest when it lacks one | config, exit 1, HTTP 503 | tests/agent/inference-failures.mjs::a_stado_without_the_operation_is_named |
inference-call-killed | asking a model through `{command}` | the Stado process was ended by a signal before it answered: the host stopped it, it ran out of memory, or the run was interrupted | `{command}` ended on {signal}; its standard error ends: {said} | run the request again; `stado capabilities resolve inference --consumer weles` shows whether an alternative answers now | infra_down, exit 69, HTTP 503, retryable | tests/agent/inference-failures.mjs::a_stado_ended_by_a_signal_is_named |
inference-answer-not-json | reading what `{command}` printed | Stado exited 0 and what it printed on standard output is not JSON: an older Stado that ignores --json, or something else writing to its standard output | {printed} characters; the parser said: {problem} | run `stado --version` and update Stado to the release named as minimum_stado_version in Weles's release manifest, then run again | config, exit 1, HTTP 503 | tests/agent/inference-failures.mjs::a_stado_printing_no_json_is_named |
inference-answer-absent | reading what `{command}` printed | Stado exited 0 with a JSON document that carries no answer member, where --json prints the resolution beside the answer | the document holds: {members} | run `stado --version` and update Stado to the release named as minimum_stado_version in Weles's release manifest, then run again | config, exit 1, HTTP 503 | tests/agent/inference-failures.mjs::a_stado_printing_no_answer_is_named |
model-image-unreadable | building the model request for {model} | image {index} of the request holds no picture sharp can read, so its media type cannot be named: the screenshot or capture that made it was cut short or is not an image | image {index}: {bytes} bytes; sharp said: {problem} | take the screenshot or capture again; the bytes it gave are kept where the caller recorded them | unknown, exit 1, HTTP 500 | tests/agent/model-failures.mjs::an_image_that_is_no_picture_is_named |
model-image-format-other | building the model request for {model} | image {index} of the request is {format}, a format the chat API takes no data URL of | image {index}: {format}, {bytes} bytes; the request takes {accepted} | capture the page as one of {accepted} (a Playwright screenshot is png or jpeg) and ask again | unknown, exit 1, HTTP 500 | tests/agent/model-failures.mjs::an_image_of_another_format_is_named |
model-completion-shape | reading the answer {provider} gave {model} through `{route}` | the answer is no chat completion: it carries no list of choices, so neither a message nor a function call can be read from it | the answer holds: {members} | run `stado capabilities inference` to see which alternative served weles; a statement recorded for this request must be the completion text, recorded again with `stado capabilities attestation record` | unknown, exit 1, HTTP 500 | tests/agent/model-failures.mjs::an_answer_that_is_no_completion_is_named |
model-function-other | reading the answer {provider} gave {model} through `{route}` | the request declared functions and required one call, and the answer did not call exactly one of them with its arguments as text | called: {called}; declared: {declared}; finish_reason {finish} | ask again; a model that keeps answering in text needs an alternative that supports function calling, chosen with `stado capabilities select inference --order ...` | unknown, exit 1, HTTP 500 | tests/agent/model-failures.mjs::an_answer_calling_no_declared_function_is_named |
model-content-empty | reading the answer {provider} gave {model} through `{route}` | the answer carries no text: the model stopped before writing any ({bound}) | finish_reason {finish}; usage {usage} | ask again; when finish_reason is length, the request needs a larger max_tokens or a shorter prompt | unknown, exit 1, HTTP 500 | tests/agent/model-failures.mjs::an_answer_without_text_is_named |
draft-not-json | reading the trajectory draft {provider} wrote through {model} for the objective {objective} | the arguments of its write_trajectory call are not JSON | {bytes} characters; the parser said: {problem}; the arguments: {raw} | run the task again, or start it with WELES_DISABLE_TRAJECTORY_WRITER=1 to let the objective alone guide the agent | unknown, exit 1, HTTP 500 | tests/agent/draft-failures.mjs::a_draft_that_is_not_json_fails_the_run |
draft-not-object | reading the trajectory draft {provider} wrote through {model} for the objective {objective} | the arguments of its write_trajectory call are {kind}, not an object of steps and notes | the arguments: {raw} | run the task again, or start it with WELES_DISABLE_TRAJECTORY_WRITER=1 to let the objective alone guide the agent | unknown, exit 1, HTTP 500 | tests/agent/draft-failures.mjs::a_draft_that_is_no_object_fails_the_run |
draft-field-not-list | reading the trajectory draft {provider} wrote through {model} for the objective {objective} | its {field} is {kind}, where write_trajectory declares a list of strings | the arguments: {raw} | run the task again, or start it with WELES_DISABLE_TRAJECTORY_WRITER=1 to let the objective alone guide the agent | unknown, exit 1, HTTP 500 | tests/agent/draft-failures.mjs::a_draft_field_that_is_no_list_fails_the_run |
draft-entry-blank | reading the trajectory draft {provider} wrote through {model} for the objective {objective} | entry {position} of its {field} is {kind}, where write_trajectory declares nonblank strings | the arguments: {raw} | run the task again, or start it with WELES_DISABLE_TRAJECTORY_WRITER=1 to let the objective alone guide the agent | unknown, exit 1, HTTP 500 | tests/agent/draft-failures.mjs::a_draft_entry_that_is_blank_fails_the_run |
draft-empty | reading the trajectory draft {provider} wrote through {model} for the objective {objective} | it holds no step and no note, so it would guide the agent with nothing | the arguments: {raw} | run the task again, or start it with WELES_DISABLE_TRAJECTORY_WRITER=1 to let the objective alone guide the agent | unknown, exit 1, HTTP 500 | tests/agent/draft-failures.mjs::a_draft_without_a_step_fails_the_run |
draft-recording-unreadable | reading the Chrome DevTools Recorder recording {file} WELES_TRAJECTORY_RECORDING names as the trajectory draft | the file cannot be read as a Recorder JSON export | {problem} | export the recording again from Chrome DevTools → Recorder → Export as JSON, or unset WELES_TRAJECTORY_RECORDING to let the writer draft the trajectory | unknown, exit 1, HTTP 500 | tests/agent/draft-failures.mjs::a_recording_that_cannot_be_read_fails_the_run |
draft-recording-empty | reading the Chrome DevTools Recorder recording {file} WELES_TRAJECTORY_RECORDING names as the trajectory draft | its steps hold no navigation, click, change or key step, so it would guide the agent with nothing | {steps} steps, of the types {types} | export the recording again from Chrome DevTools → Recorder → Export as JSON, or unset WELES_TRAJECTORY_RECORDING to let the writer draft the trajectory | unknown, exit 1, HTTP 500 | tests/agent/draft-failures.mjs::a_recording_without_a_step_fails_the_run |
flows-login-url-unread | reading the address of the login page before credentials are typed | the browser page did not answer its address: the page or its browser closed, or the connection to it broke | the page said: {problem} | run the login again; `weles doctor` shows whether the browser runtime answers | infra_down, exit 69, HTTP 503, retryable | tests/agent/flows-failures.mjs::a_login_page_that_gives_no_address_is_named |
flows-login-url-empty | reading the address of the login page before credentials are typed | the page answered an empty address, so its host cannot be checked before credentials are typed | address: {url} | navigate the page to the login form first, then run the login | unknown, exit 1, HTTP 500 | tests/agent/flows-failures.mjs::a_login_page_with_an_empty_address_is_refused |
flows-login-url-relative | checking the host of the login page {url} before credentials are typed | the address is not an absolute URL, so its host cannot be checked | address {url}; the URL parser said: {problem} | navigate the page to the login form at its absolute https address, then run the login | unknown, exit 1, HTTP 500 | tests/agent/flows-failures.mjs::a_login_page_whose_address_is_relative_is_refused |
flows-login-apple-refused | typing credentials into the login page at {host} | the generic vision login does not type passwords into Apple's sign-in; Apple accounts sign in only through the owner-authorized apple_login flow | host {host} | run `weles login --provider apple --confirm "AUTHORIZE ONE APPLE LOGIN" --execution-host <host> --expires-in-minutes <n>` instead | refused, exit 2, HTTP 403 | tests/agent/flows-failures.mjs::an_apple_login_page_is_refused |
flows-login-navigation-failed | waiting for the page to leave {url} after the login was submitted | the browser stopped answering while the page was watched: the page closed, crashed or its connection broke | the browser said: {problem} | run the login again; the run recording shows the page at the moment it was submitted | infra_down, exit 69, HTTP 503, retryable | tests/agent/flows-failures.mjs::a_login_whose_page_closes_after_submit_is_named |
flows-field-unstated | reading an account value with FetchAccountValue | the caller did not state {name}, which the flow never assumes | {name}: {given} | pass {name} to FetchAccountValue (service, url, what, osTarget and trajectoryCacheDir are all required) | config, exit 2, HTTP 503 | tests/agent/flows-failures.mjs::a_fetch_without_a_stated_field_is_refused |
flows-login-secret-absent | signing in to {service} to read {what} | {service} shows a login page, and the Weles service secret {secret} grants no {missing} | secret {secret}: {missing} absent | store the {missing} of the {service} account in the Skarbiec item the Weles service secret {secret} names, then run again | config, exit 1, HTTP 503 | tests/agent/flows-failures.mjs::a_login_page_without_its_secret_is_named |
flows-login-not-reached | signing in to {service} with {secret} | the login form was filled and submitted and the page did not reach the account: the credentials are wrong, or the site asked for a step the flow does not take | the page stayed at {url} | check the credentials in {secret}, sign in once through `weles open {url} --os macos --browser chromium` to see what the site asks, then run again | refused, exit 1, HTTP 403 | tests/agent/flows-failures.mjs::a_login_that_does_not_reach_the_account_is_named |
flows-cache-unread | reading the learned route of {service} from {path} | the file exists and the system refused to read it ({errno}): it is not readable by the worker user, or it is a directory | {path}: {problem} | give the worker's user read access to {path}, or remove it; the next run rediscovers the route and learns it again | config, exit 1, HTTP 503 | tests/agent/flows-failures.mjs::a_learned_route_that_cannot_be_read_is_named |
flows-cache-not-json | reading the learned route of {service} from {path} | the file is not JSON: it was cut short while written, or something else wrote it | {bytes} characters; the parser said: {problem} | remove {path}; the next run rediscovers the route and learns it again | config, exit 1, HTTP 503 | tests/agent/flows-failures.mjs::a_learned_route_that_is_not_json_is_named |
flows-cache-shape | reading the learned route of {service} from {path} | the file holds JSON that is no learned route (service, finalUrl, selector, lastSuccess, lastValue) | it holds: {members} | remove {path}; the next run rediscovers the route and learns it again | config, exit 1, HTTP 503 | tests/agent/flows-failures.mjs::a_learned_route_of_another_shape_is_named |
flows-value-mistyped | reading {what} off the page | the model called value with {kind} where the declared function takes {expected} | value: {value} | ask again; a model that keeps answering another type needs a clearer description of {what} | unknown, exit 1, HTTP 500 | tests/agent/flows-failures.mjs::a_value_of_another_type_is_named |
tool-argument-not-text | reading the {tool} call the model made | invalid {key}: expected a string, received {kind}; {tool} takes {key} as text | {tool} arguments: {keys} | call {tool} again with {key} as a string | refused, exit 2, HTTP 403 | tests/agent/tools-failures.mjs::a_tool_argument_that_is_not_text_is_refused |
tool-wait-state-invalid | reading the {tool} call the model made | invalid state: {state} is not one of {states} | state {state} | call wait_for with state one of {states}, or leave it out to wait until visible | refused, exit 2, HTTP 403 | tests/agent/tools-failures.mjs::a_wait_for_an_unknown_state_is_refused |
tool-capability-malformed | reading the {tool} call the model made | invalid capability reference: a capability is an object of capability_id, purpose and resource as text and target "weles", and this one {problem} | capability keys: {keys} | pass the capability object exactly as it was handed to the run, without changing or adding a member | refused, exit 2, HTTP 403 | tests/agent/tools-failures.mjs::a_capability_of_another_shape_is_refused |
tool-capability-purpose-unknown | reading the {tool} call the model made | invalid capability reference: its purpose {purpose} is not one Weles redeems ({purposes}) | purpose {purpose} | pass the capability the run was handed for this field; Weles redeems only {purposes} | refused, exit 2, HTTP 403 | tests/agent/tools-failures.mjs::a_capability_of_an_unknown_purpose_is_refused |
tool-field-class-unknown | reading the {tool} call the model made | invalid field_class: {given} is not a credential class fill_credential fills ({classes}) | field_class {given} | call fill_credential with field_class one of {classes} | refused, exit 2, HTTP 403 | tests/agent/tools-failures.mjs::a_credential_fill_of_an_unknown_class_is_refused |
tool-identity-field-unknown | reading the {tool} call the model made | invalid identity field: {given} is not a field of the generated identity ({fields}) | field {given} | call fill_identity with field one of {fields} | refused, exit 2, HTTP 403 | tests/agent/tools-failures.mjs::an_identity_fill_of_an_unknown_field_is_refused |
tool-storage-class-unknown | reading the {tool} call the model made | invalid credential storage field_class: {given} is not one store_credential keeps (token or api-key) | field_class {given} | call store_credential with field_class token or api-key | refused, exit 2, HTTP 403 | tests/agent/tools-failures.mjs::a_credential_store_of_an_unknown_class_is_refused |
tool-unknown | running the tool the model named | UNKNOWN_TOOL: {tool} is not one of the declared browser tools | tool {tool}; declared: {tools} | call one of the declared browser tools: {tools} | refused, exit 2, HTTP 403 | tests/agent/tools-failures.mjs::a_tool_no_declaration_names_is_refused |
decision-unanswered | reading step {step} the model chose for {goal} | the model call gave no function arguments, so there is no next step to run | the model call said: {answer} | run `stado capabilities resolve inference --consumer weles` to see which model route answers, then run the task again | infra_down, exit 69, HTTP 503, retryable | tests/agent/decision-failures.mjs::a_model_that_answers_nothing_gives_up_naming_it |
decision-function-undeclared | reading step {step} the model chose for {goal} | the model named the function {name}, which no browser tool declares | function {name}; declared: {tools} | run the task again; a model that keeps naming another function needs a prompt that lists the tools it may call | unknown, exit 1, HTTP 500 | tests/agent/decision-failures.mjs::a_model_naming_an_undeclared_function_gives_up |
decision-arguments-not-json | reading step {step} the model chose for {goal} | the arguments the model gave {name} are not JSON | {bytes} characters; the parser said: {problem} | run the task again; the step record loop_step{step}.json keeps the raw answer | unknown, exit 1, HTTP 500 | tests/agent/decision-failures.mjs::a_model_giving_arguments_that_are_not_json_gives_up |
decision-arguments-not-object | reading step {step} the model chose for {goal} | {name} arguments must be an object, and the model gave {kind} | arguments: {kind} | run the task again; the step record loop_step{step}.json keeps the raw answer | unknown, exit 1, HTTP 500 | tests/agent/decision-failures.mjs::a_model_giving_arguments_that_are_no_object_gives_up |
decision-argument-names-other | reading step {step} the model chose for {goal} | {name} argument names: unknown=[{unknown}], missing=[{missing}]; {name} takes {taken} | arguments given: {given} | run the task again; {name} takes exactly {taken} | unknown, exit 1, HTTP 500 | tests/agent/decision-failures.mjs::a_model_giving_other_argument_names_gives_up |
loop-max-steps-unset | starting a browser task at {url} | WELES_AGENT_MAX_STEPS is not set and the caller named no step limit, so nothing bounds how many model steps the task may take | WELES_AGENT_MAX_STEPS unset; {observed} initialization steps observed; the browser was not driven | set WELES_AGENT_MAX_STEPS to a whole number above zero in the environment the run starts with (the worker service unit, or the run's own environment), then run the task again | config, exit 1, HTTP 503 | tests/agent/loop-failures.mjs::a_task_without_a_step_limit_is_refused |
loop-max-steps-invalid | starting a browser task at {url} | WELES_AGENT_MAX_STEPS is {value}, which is not a whole number above zero | WELES_AGENT_MAX_STEPS={value} ({length} characters); the browser was not driven | set WELES_AGENT_MAX_STEPS to a whole number above zero in the environment the run starts with (the worker service unit, or the run's own environment), then run the task again | config, exit 1, HTTP 503 | tests/agent/loop-failures.mjs::a_step_limit_that_is_no_whole_number_is_refused |
loop-pages-closed | running step {step} of the browser task at {url} | every page of the browser session is closed, so there is no page to observe or act on: the site closed its last tab, or the browser ended | {pages} pages known to the session, all closed; {taken} steps taken; last address {last} | run the task again; when the site closes its window after a step, end the task with done at that step instead | unknown, exit 1, HTTP 500 | tests/agent/loop-failures.mjs::a_task_whose_pages_all_close_is_named |
loop-replay-ended | running step {step} of the browser task at {url} | the replayed flow ran all its {steps} steps and none of them was done, and a replay-only run takes no step the model chooses | replayed {steps} steps; last tool {last} | record the flow again with a run that is not replay-only, so its last step is done | unknown, exit 1, HTTP 500 | tests/agent/loop-failures.mjs::a_replay_without_done_is_named |
loop-replay-step-failed | replaying step {step} ({tool}) of the flow at {url} | the replayed step threw where the flow recorded it succeeding, and a replay-only run takes no other step: the page no longer matches the recording | {tool} threw: {problem} | record the flow again with a run that is not replay-only against the page as it is now | unknown, exit 1, HTTP 500 | tests/agent/loop-failures.mjs::a_replayed_step_that_fails_is_named |
loop-decision-tool-absent | running step {step} of the browser task at {url} | the {source} decision for the step names no tool, so there is nothing to dispatch | decision members: {members}; tool is {kind} | run the task again; a replayed flow holding such a step is recorded again with a run that is not replay-only | unknown, exit 1, HTTP 500 | tests/agent/loop-failures.mjs::a_decision_naming_no_tool_is_refused |
loop-decision-arguments-absent | running step {step} of the browser task at {url} | the {source} decision calls {tool} without an argument object, so the tool would be dispatched blank | decision members: {members}; args is {kind} | run the task again; a replayed flow holding such a step is recorded again with a run that is not replay-only | unknown, exit 1, HTTP 500 | tests/agent/loop-failures.mjs::a_decision_without_arguments_is_refused |
loop-gave-up | running step {step} of the browser task at {url} | the model gave up on the goal: {reason} | give_up at step {step} after {taken} steps; page {last} | do what the reason asks (a person approves the step, the account is repaired, the goal is restated), then run the task again | refused, exit 1, HTTP 403 | tests/agent/loop-failures.mjs::a_model_that_gives_up_is_named |
loop-gave-up-unsaid | running step {step} of the browser task at {url} | the model gave up on the goal without saying why: its give_up carries no reason | give_up at step {step} after {taken} steps with arguments {members}; page {last} | run the task again; the step records of the run show what the page showed when the model gave up | refused, exit 1, HTTP 403 | tests/agent/loop-failures.mjs::a_model_that_gives_up_without_a_reason_is_named |
loop-steps-exhausted | running the browser task at {url} | the task took all {limit} steps its limit allows without the model calling done or give_up | {limit} steps, {failed} of them failed; last tool {last} on {page} | read the step records of the run for where it went round; raise WELES_AGENT_MAX_STEPS (or the caller's step limit) only when the goal needs more steps | unknown, exit 1, HTTP 500 | tests/agent/loop-failures.mjs::a_task_that_uses_up_its_steps_is_named |
evidence-target-setting-unset | reading the browser-evidence target the run was admitted with | browser-evidence target network binding is invalid: {name} is not set | {name} unset or empty | set WELES_BROWSER_EVIDENCE_TARGET_ORIGIN, _HOST and _ADDRESSES_JSON as the admission states them (the worker sets them from the admitted task) | config, exit 1, HTTP 503 | tests/agent/evidence-target-failures.mjs::a_target_without_its_origin_is_refused |
evidence-target-addresses-not-json | reading the browser-evidence target the run was admitted with | browser-evidence target network binding is invalid: WELES_BROWSER_EVIDENCE_TARGET_ADDRESSES_JSON is not JSON | {bytes} characters; the parser said: {problem} | set WELES_BROWSER_EVIDENCE_TARGET_ADDRESSES_JSON to a JSON array of the admitted addresses, ["203.0.113.7"] say | config, exit 1, HTTP 503 | tests/agent/evidence-target-failures.mjs::target_addresses_that_are_not_json_are_refused |
evidence-target-addresses-not-list | reading the browser-evidence target the run was admitted with | browser-evidence target network binding is invalid: WELES_BROWSER_EVIDENCE_TARGET_ADDRESSES_JSON is not a non-empty array of address strings | it holds {shape} | set WELES_BROWSER_EVIDENCE_TARGET_ADDRESSES_JSON to a non-empty JSON array of address strings | config, exit 1, HTTP 503 | tests/agent/evidence-target-failures.mjs::target_addresses_that_are_no_list_are_refused |
evidence-target-origin-invalid | reading the browser-evidence target the run was admitted with | browser-evidence target network binding is invalid: WELES_BROWSER_EVIDENCE_TARGET_ORIGIN {origin} {problem} | origin {origin}; host {host} | set WELES_BROWSER_EVIDENCE_TARGET_ORIGIN to the https origin alone (https://<host>, no path) on WELES_BROWSER_EVIDENCE_TARGET_HOST | config, exit 1, HTTP 503 | tests/agent/evidence-target-failures.mjs::a_target_origin_with_a_path_is_refused |
evidence-target-hostname-empty | resolving the browser-evidence target | network hostname is empty: "{hostname}" names no host to resolve | hostname "{hostname}" | set WELES_BROWSER_EVIDENCE_TARGET_ORIGIN, _HOST and _ADDRESSES_JSON as the admission states them (the worker sets them from the admitted task) | config, exit 1, HTTP 503 | tests/agent/evidence-target-failures.mjs::a_target_with_an_empty_host_is_refused |
evidence-target-hostname-unresolved | resolving the browser-evidence target {hostname} | network hostname has no addresses: {hostname} did not resolve, so the run cannot be pinned to it | {code}: {answer} | check the name resolves on this host (`dig {hostname}`), then submit the task again | infra_down, exit 69, HTTP 503, retryable | tests/agent/evidence-target-failures.mjs::a_target_that_does_not_resolve_is_named |
evidence-target-url-invalid | admitting the browser-evidence target of a task | browser-evidence target must be public HTTPS without credentials, and the task names {problem} | target {shown} | submit the task with product_url an https URL without a user or password | refused, exit 2, HTTP 403 | tests/agent/evidence-target-failures.mjs::a_target_that_is_no_public_https_url_is_refused |
evidence-target-dns-changed | installing the browser-evidence policy of run {label} | browser-evidence target DNS changed before browser policy installation: {hostname} resolves to other addresses than were admitted | admitted {admitted}; now {current} | submit the task again, so it is admitted with the addresses the name resolves to now | refused, exit 1, HTTP 403 | tests/agent/evidence-target-failures.mjs::a_target_whose_addresses_changed_is_refused |
evidence-request-main-navigation-denied | admitting the request {method} {url} of a browser-evidence run | non-network main-frame navigation denied: the page moved its main frame to a {protocol} document, which the target origin does not serve | main-frame navigation to {protocol} | nothing to change: the ledger keeps this withheld edge as evidence of what the page tried | refused, exit 1, HTTP 403 | tests/agent/evidence-target-failures.mjs::a_main_frame_navigation_to_a_data_url_is_withheld |
evidence-request-not-http | admitting the request {method} {url} of a browser-evidence run | non-HTTP(S) browser request denied: the page asked for {protocol}{credentials}, and only pinned HTTP(S) leaves the browser | scheme {protocol}{credentials} | nothing to change: the ledger keeps this withheld edge as evidence of what the page tried | refused, exit 1, HTTP 403 | tests/agent/evidence-target-failures.mjs::a_request_of_another_scheme_is_withheld |
evidence-request-method-denied | admitting the request {method} {url} of a browser-evidence run | anonymous browser-evidence network policy permits only methods that carry no body (GET and HEAD), and the page sent {method} | method {method} | nothing to change: a browser-evidence run observes and never submits; the ledger keeps the withheld edge | refused, exit 1, HTTP 403 | tests/agent/evidence-target-failures.mjs::a_request_with_a_body_is_withheld |
evidence-request-origin-other | admitting the request {method} {url} of a browser-evidence run | browser request left the exact admitted target origin: {origin} is not {target} | origin {origin}; admitted {target} | nothing to change: only the admitted origin is reached; the ledger keeps the withheld edge | refused, exit 1, HTTP 403 | tests/agent/evidence-target-failures.mjs::a_request_to_another_origin_is_withheld |
evidence-request-dns-changed | admitting the request {method} {url} of a browser-evidence run | target DNS differs from the admitted pinned address set: {hostname} now resolves to other addresses | admitted {admitted}; now {current} | submit the task again, so it is admitted with the addresses the name resolves to now | refused, exit 1, HTTP 403 | tests/agent/evidence-target-failures.mjs::a_request_after_the_target_moved_is_withheld |
evidence-ledger-unlabelled | filing a withheld edge of a browser-evidence run | the browser-evidence ledger needs the run label that names its recordings directory, and the session has none; a defect of the caller | label "{label}" | report the caller: a browser-evidence session is started with its run label | unknown, exit 1, HTTP 500 | tests/agent/evidence-target-failures.mjs::a_ledger_without_its_run_is_refused |
evidence-tool-withheld | running {tool} in a browser-evidence run | policy_withheld:{category}:{reason} | tool {tool}; the withheld edge is in the run ledger | continue with non-interactive observation; the policy admits only the controls the evidence declaration names | refused, exit 1, HTTP 403 | tests/agent/evidence-target-failures.mjs::an_interactive_tool_the_policy_withholds_is_named |
vision#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
screenshot-capture-failed | taking the screenshot of the page at {url} | the page refused a css-scaled and a plain PNG capture alike: it was closed or navigated away while it was captured, or the browser ended | css-scaled capture: {scaled}; plain capture: {plain} | run the step again on an open page; when the browser ended, the run's browser log names why | unknown, exit 1, HTTP 500 | tests/vision/screenshot-failures.mjs::a_page_that_refuses_both_captures_is_named |
screenshot-cdp-failed | taking the screenshot of the page at {url} | the DevTools connection refused Page.captureScreenshot: the target was closed, or the connection dropped | Page.captureScreenshot threw: {problem} | reconnect to the page and ask again; `weles doctor` shows whether the browser answers | unknown, exit 1, HTTP 500 | tests/vision/screenshot-failures.mjs::a_capture_the_devtools_connection_refuses_is_named |
screenshot-cdp-empty | taking the screenshot of the page at {url} | Page.captureScreenshot answered without image data: the page has no surface to capture (a minimised or zero-sized window) | the answer holds: {members} | give the window a size (launch it headed with a viewport, or headless) and ask again | unknown, exit 1, HTTP 500 | tests/vision/screenshot-failures.mjs::a_capture_answered_without_data_is_named |
screenshot-page-unsupported | taking the screenshot of the page handed to the question | the object handed as the page offers neither screenshot() nor send(), so there is no way to capture it | the object offers: {members} | hand the question a Playwright page or a DevTools page connection (CDPConnection) of the session | config, exit 1, HTTP 503 | tests/vision/screenshot-failures.mjs::a_page_that_cannot_be_captured_is_named |
screenshot-not-png | reading the size of the screenshot of the page for the click target "{description}" | the screenshot the page gave is not a PNG datastream, so its width and height cannot be read from its header | {bytes} bytes, beginning {head} | capture the page as png (its screenshot() with type png), then ask again | unknown, exit 1, HTTP 500 | tests/vision/screenshot-failures.mjs::a_screenshot_that_is_no_png_is_named |
question-record-unwritable | recording the screenshot of the {tier} question in {directory} | the vision folder refused the screenshot ({errno}): it is not writable by the worker user, or its disk is full | {path}: {problem}; the model was not asked | give the worker's user write access to {directory} (WELES_VISION_DIR, else the run's vision folder under WELES_RECORDINGS_ROOT) and free space on its disk, then ask again | config, exit 1, HTTP 503 | tests/vision/question-failures.mjs::a_vision_folder_that_refuses_the_screenshot_is_named |
question-log-unwritten | recording the answer to the {tier} question in {directory} | the vision folder refused the question's record ({errno}): it is not writable by the worker user, or its disk is full; the question went on without it | {path}: {problem} | give the worker's user write access to {directory} (WELES_VISION_DIR, else the run's vision folder under WELES_RECORDINGS_ROOT) and free space on its disk, then ask again | config, exit 1, HTTP 503 | tests/vision/question-failures.mjs::a_vision_folder_that_refuses_the_record_is_named |
question-arguments-not-json | reading the {function} answer to the {tier} question "{question}" | the arguments the model gave {function} are not JSON, so the answer cannot be read | {bytes} characters; the parser said: {problem} | ask again; a model that keeps answering so needs an alternative chosen with `stado capabilities select inference --order ...`; the question's record in the run's vision folder keeps the raw answer | unknown, exit 1, HTTP 500 | tests/vision/question-failures.mjs::an_answer_whose_arguments_are_not_json_is_named |
question-shows-absent | reading the {function} answer to the {tier} question "{question}" | the answer carries no boolean shows, so it says neither yes nor no | arguments: {members}; shows is {kind} | ask again; a model that keeps answering so needs an alternative chosen with `stado capabilities select inference --order ...` | unknown, exit 1, HTTP 500 | tests/vision/question-failures.mjs::a_yes_or_no_answer_without_shows_is_named |
question-kind-undeclared | reading the {function} answer to the {tier} question "{question}" | the answer names {kind} as the kind of page, which is none of the kinds the question declares | kind {kind}; declared: {kinds} | ask again; a model that keeps answering so needs an alternative chosen with `stado capabilities select inference --order ...` | unknown, exit 1, HTTP 500 | tests/vision/question-failures.mjs::a_page_kind_that_is_not_declared_is_named |
question-controls-not-list | reading the {function} answer to the {tier} question "{question}" | the controls answer carries no list of controls, so none can be matched to the click target | arguments: {members}; controls is {kind} | ask again; a model that keeps answering so needs an alternative chosen with `stado capabilities select inference --order ...` | unknown, exit 1, HTTP 500 | tests/vision/question-failures.mjs::a_controls_answer_without_a_list_is_named |
question-point-not-numeric | reading the point to click for "{description}" | the {source} carries no numeric x and y, so there is no point to click | members: {members}; x is {x}, y is {y} | ask again; a model that keeps answering so needs an alternative chosen with `stado capabilities select inference --order ...` | unknown, exit 1, HTTP 500 | tests/vision/question-failures.mjs::a_point_without_numbers_is_named |
question-pick-not-json | reading which of {count} listed controls is "{description}" | the arguments the model gave {function} are not JSON, so the chosen control cannot be read | {bytes} characters; the parser said: {problem} | ask again; a model that keeps answering so needs an alternative chosen with `stado capabilities select inference --order ...` | unknown, exit 1, HTTP 500 | tests/vision/question-failures.mjs::a_control_choice_that_is_not_json_is_named |
question-label-not-offered | reading which of {count} listed controls is "{description}" | the model picked {label}, a label none of the listed controls carries, though the choice admits only theirs | picked {label}; offered {offered} | ask again; a model that keeps answering so needs an alternative chosen with `stado capabilities select inference --order ...` | unknown, exit 1, HTTP 500 | tests/vision/question-failures.mjs::a_control_choice_of_another_label_is_named |
question-refused-untranscribed | asking the {tier} question "{question}" | the model declined to answer about the screenshot, and declined to transcribe it too, so the question was asked of neither | about the screenshot: {reason}; asked to transcribe it: {transcription} | ask a question the page itself answers, or one a model of another alternative takes (`stado capabilities select inference --order ...`) | refused, exit 1, HTTP 403 | tests/vision/question-failures.mjs::a_question_refused_and_left_untranscribed_is_named |
question-refused-twice | asking the {tier} question "{question}" | the model declined to answer about the screenshot, and declined again when asked of its transcription | about the screenshot: {reason}; about the transcription: {again} | ask a question the page itself answers, or one a model of another alternative takes (`stado capabilities select inference --order ...`) | refused, exit 1, HTTP 403 | tests/vision/question-failures.mjs::a_question_refused_of_both_is_named |
question-click-target-refused | finding the click target "{description}" on a {width}x{height} screenshot | the model declined both ways of finding it: the point asked of the screenshot and the list of its controls | tier_0_bare: {bare}; tier_1_decompose: {list} | name the control by a selector or its visible text instead, or describe it as the page labels it | refused, exit 1, HTTP 403 | tests/vision/question-failures.mjs::a_click_target_refused_by_both_tiers_is_named |
Executor#
How an operator command fails to reach the managed Weles executor through Stado or WELES_WORKER_API_BASE. Every command that talks to the executor meets these.
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
stado-unavailable | running `stado {command}` | the Stado program cannot be run: WELES_STADO_BIN and STADO_BIN name none that runs, no stado is on PATH, and none is where its installer places it | {answer} | install Stado (its installer places it in $HOME/.stado/bin/stado, or in STADO_BIN_DIR when that is set), put it on PATH, or name the program with WELES_STADO_BIN | config, exit 1, HTTP 503 | tests/sign-in/executor.mjs::login_on_a_host_without_stado_names_where_it_looked |
stado-refused | running `stado {command}` | Stado ran and refused the request; its own words below name what it refused and why | stado exited {status}: {answer} | run the same stado command by hand, correct what it names (a target the registry does not hold, a credential it does not have), then run again | refused, exit 1, HTTP 403 | tests/sign-in/executor.mjs::login_whose_capabilities_stado_refuses_quotes_stado |
stado-answer-not-json | reading what `stado {command}` answered | Stado exited 0 but did not print the JSON document --json promises: an older Stado, or something writing to its standard output | {answer} | upgrade Stado to the version this Weles release names as minimum_stado_version, then run again | config, exit 1, HTTP 503 | tests/sign-in/executor.mjs::login_through_a_stado_that_prints_no_json_names_it |
stado-locator-form-invalid | reading the command line of src/_shared/linking/stado.cjs | the Stado locator a release script runs takes one form, `program`, which prints the Stado program every module of Weles runs | given: {given} | run node src/_shared/linking/stado.cjs program | refused, exit 2, HTTP 403 | tests/release/stado-locator.mjs::the_locator_refuses_a_form_it_does_not_take |
executor-route-unresolved | finding the Weles executor | WELES_WORKER_API_BASE is not set and Stado places no weles-admission service this shell can connect to | {answer} | set WELES_WORKER_API_BASE and WELES_WORKER_TOKEN to the executor's address and bearer, or run where `stado service directory connect weles-admission --consumer operator` answers a url | not_found, exit 1, HTTP 404 | tests/sign-in/executor.mjs::login_reading_a_run_where_stado_places_no_executor |
host-unnamed | naming this host's registry target | `stado registry self --name-only` printed no name | it printed nothing | enrol this host in the Stado registry, then ask again | config, exit 1, HTTP 503 | tests/skarbiec/failures.mjs::a_stado_that_names_no_host_is_refused |
variable-missing | reading {name} | {name} is not set, or set empty | {name}: not set | set {name}; for the executor, WELES_WORKER_TOKEN is the token field of the item playing the echo-weles-api role (stado credentials get --role echo-weles-api --field token) | config, exit 1, HTTP 503 | tests/sign-in/executor.mjs::login_with_an_executor_address_and_no_token_names_the_variable |
variable-invalid | reading {name} | {name} holds whitespace around it or a control character, so it cannot be sent in a header or a URL | {name} holds {length} characters, some of them whitespace or control characters | set {name} to the value alone, with no newline or space around it | config, exit 1, HTTP 503 | tests/sign-in/executor.mjs::login_with_a_token_holding_a_newline_names_the_variable |
endpoint-invalid | reading {name} | {name} is not of the form it must have: an address that does not parse or carries credentials, a query or a fragment, or an id that is not a UUID | {name}: {value}; {answer} | set {name} to the base address alone (such as https://<host>), or to the organization UUID for WISENT_ORGANIZATION_ID | config, exit 1, HTTP 503 | tests/sign-in/executor.mjs::login_with_an_executor_address_that_is_not_a_url_names_it |
endpoint-insecure | reading {name} | it names a plain-HTTP address on another machine, over which the bearer would travel readable | {name}: {value} | use https:// for a remote executor; plain http:// is accepted only for a loopback address | refused, exit 1, HTTP 403 | tests/sign-in/executor.mjs::login_with_a_remote_plain_http_executor_is_refused |
executor-unreachable | {operation} at {endpoint} | no HTTP answer came back: the host has no DNS answer, nothing listens on that port, or the connection was cut | {answer} | check the executor runs there (stado service status com.wisent.weles) and that the address is the one Stado places it at, then run again | infra_down, exit 69, HTTP 503, retryable | tests/sign-in/executor.mjs::login_reading_a_run_from_a_closed_port_names_the_address |
executor-unauthorized | {operation} at {endpoint} | the executor did not admit the bearer: it is another deployment's, or the item was rotated after it was read | HTTP {status}: {answer} | read the bearer again with `stado credentials get --role echo-weles-api --field token` (or correct WELES_WORKER_TOKEN) and run again | auth, exit 1, HTTP 401 | tests/sign-in/executor.mjs::login_reading_a_run_with_a_wrong_bearer_is_refused_by_the_executor |
executor-refused | {operation} at {endpoint} | the executor understood the request and refused it; its own words below say why | HTTP {status}: {answer} | correct what the executor names and run again | refused, exit 1, HTTP 403 | tests/sign-in/executor.mjs::login_reading_a_run_id_the_executor_refuses_quotes_it |
executor-not-found | {operation} at {endpoint} | the executor holds nothing at that address: the run id belongs to another executor, the run left no result yet, or its record was removed | HTTP {status}: {answer} | check the run id against `weles runs list`, and read it from the executor that started it | not_found, exit 1, HTTP 404 | tests/sign-in/executor.mjs::login_reading_a_run_the_executor_never_had_is_not_found |
executor-failed | {operation} at {endpoint} | the executor, or the forward in front of it, failed while answering | HTTP {status}: {answer} | read the executor's log (stado service logs com.wisent.weles) for the failure it names, then run again | infra_down, exit 69, HTTP 503, retryable | tests/sign-in/executor.mjs::login_reading_a_run_through_a_failing_forward_quotes_its_answer |
executor-answer-not-json | {operation} at {endpoint} | what answered is not the executor: a forward, proxy or other service answered in plain text or HTML because it could not reach the executor | {answer} | check the address names the executor and that the Stado forward to it is up (stado service directory connect weles-admission), then run again | infra_down, exit 69, HTTP 503, retryable | tests/sign-in/executor.mjs::login_reading_a_run_through_a_page_that_is_not_the_executor_names_it |
run-not-started | starting {action} on the executor | the executor answered without starting a detached run: it refused the action or its parameters | the executor answered: {answer} | read the executor's answer above; `weles runs list` shows what it runs now | refused, exit 1, HTTP 403 | tests/sign-in/failures.mjs::login_whose_run_the_executor_does_not_start_quotes_it |
run-result-unreadable | reading the result of run {run} | the run's run-result.json carries no status: it was written by another program, or is not a run result | {answer} | read the run's files with `weles runs show {run}` to see what it wrote | refused, exit 1, HTTP 403 | tests/sign-in/failures.mjs::login_reading_a_result_without_a_status_names_it |
run-unrelated | {operation}: reading the {action} run the executor answered | the executor answered a run that is not a {action} run with its id, state and login binding: the run id names a run of another action, or the executor runs an older Weles release that records no binding | answered: {answer} | check the run id is the one the start answered; when it is, compare `weles version` with the release `stado service status com.wisent.weles` names and move the executor onto it with `stado service update`; a missing binding is never accepted | config, exit 1, HTTP 503 | tests/security/failures.mjs::security_reading_a_run_of_another_action_names_it |
run-rebound | {operation}: binding the {action} run to its request | the executor answered a run bound to another request than the one asked: another login, run id, organization or account than requested, so its outcome would describe another account | asked {asked}; the executor answered {answered} | inspect the run with `weles runs show <run-id>` and the executor's release (`weles worker version`) before starting another; do not accept a run bound to another account | config, exit 1, HTTP 503 | tests/security/failures.mjs::security_start_answered_for_another_role_is_refused |
submit-action-invalid | starting a follow-up run | the action {action} is not an action name: lowercase letters, digits and _, beginning with a letter | action: {action} | name the action as the run that asks for it declares it (`weles errors show trajectories` lists the trajectories) | refused, exit 1, HTTP 403 | tests/runs/submitting/submit-failures.mjs::a_follow_up_of_no_action_name_is_refused |
submit-account-invalid | starting the follow-up run {action} on this host | the account item {item} is not a Skarbiec item id: a letter or digit, then letters, digits, ., _, : and - | accountItem: {item} | pass the exact id of the account item the run acts for, as its record names it | refused, exit 1, HTTP 403 | tests/runs/submitting/submit-failures.mjs::a_follow_up_for_an_item_of_no_id_is_refused |
submit-params-invalid | starting the follow-up run {action} on this host | params is {kind}, where a run takes one object of named parameters | params: {params} | pass the parameters as one object of named values, as the action reads them | refused, exit 1, HTTP 403 | tests/runs/submitting/submit-failures.mjs::a_follow_up_with_params_of_no_object_is_refused |
submit-api-unnamed | starting the follow-up run {action} on this host | neither WELES_WORKER_API_BASE nor WELES_API_PORT is set in this process, so it names no Weles API to start the run on | WELES_WORKER_API_BASE: not set; WELES_API_PORT: not set | run this inside a trajectory the Weles API started (it states WELES_API_PORT), or set WELES_WORKER_API_BASE to the API off this host | config, exit 1, HTTP 503 | tests/runs/submitting/submit-failures.mjs::a_follow_up_outside_a_weles_api_names_the_settings |
submit-token-unset | starting the follow-up run {action} on this host | none of WELES_API_TOKEN, WELES_CONSOLE_API_TOKEN or WELES_WORKER_TOKEN is set in this process, so the API would refuse the run | the API at {endpoint}; no bearer | run this inside a trajectory the Weles API started (it states WELES_API_TOKEN), or set WELES_WORKER_TOKEN from `stado credentials get --role echo-weles-api --field token` | config, exit 1, HTTP 503 | tests/runs/submitting/submit-failures.mjs::a_follow_up_without_a_bearer_names_the_settings |
runs#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
action-missing | reading the weles runs command line | weles runs needs an action, and none was named | the actions weles runs takes: {verbs} | run `weles runs <action>` with one of {verbs}; `weles runs --help` describes each | refused, exit 2, HTTP 403 | tests/runs/failures.mjs::runs_without_an_action_names_the_actions |
action-unknown | reading the weles runs command line | unknown runs action: {action} is not an action weles runs takes | action: {action}; weles runs takes {verbs} | name one of {verbs} | refused, exit 2, HTTP 403 | tests/runs/failures.mjs::runs_refuses_an_action_it_does_not_take |
options-unknown | reading the weles runs {action} command line | it holds a word or option runs {action} does not take | not taken: {given}; runs {action} takes {taken} | leave out {given}; `weles runs --help` lists what each action takes | refused, exit 2, HTTP 403 | tests/runs/failures.mjs::runs_refuses_an_option_its_action_does_not_take |
run-id-missing | reading the run runs {verb} acts on | runs {verb} requires <run-id>, and none was given | weles runs {verb} with no run id | give the run id `weles runs list` prints first on each line: weles runs {verb} <run-id> | refused, exit 2, HTTP 403 | tests/runs/failures.mjs::runs_show_without_a_run_id_is_refused |
run-id-invalid | reading the run runs {verb} acts on | a run id is a letter or digit followed by letters, digits, _ and -, since it names the run result file, and this is not one | run id: {run} | give the run id exactly as `weles runs list` or the run that started it printed it | refused, exit 2, HTTP 400 | tests/runs/failures.mjs::runs_refuses_a_run_id_that_cannot_name_a_run |
answer-missing | reading what the person did for runs answer | runs answer requires exactly one of --ready, --approved or --not-received; ending the wait is weles runs cancel <run-id> --detail <who and why> | answers given: {given} | give one of --ready (the phone is in hand), --approved (the prompt was approved) or --not-received (it never arrived) | refused, exit 2, HTTP 403 | tests/runs/failures.mjs::runs_answer_without_exactly_one_answer_is_refused |
detail-missing | reading --detail for runs {verb} | runs cancel requires --detail <who cancels it and why>, which the run record keeps, and runs answer takes --detail only with its text | --detail: {value} | give --detail "<one sentence>": who cancels the run and why, or what the person saw | refused, exit 2, HTTP 400 | tests/runs/failures.mjs::runs_cancel_without_detail_is_refused |
body-not-json | reading the request to {verb} run {run} | its body is not JSON, so the worker cannot read the answer or detail it carries | {method} {path}: {bytes} bytes; parser: {answer} | send a JSON object, such as {"detail": "<who and why>"}; `weles runs {verb}` sends one | refused, exit 2, HTTP 400 | tests/runs/failures.mjs::runs_cancel_with_a_body_that_is_not_json_is_refused |
answer-invalid | reading what the person did for run {run} | answer is not one the worker takes; ending the wait is POST /runs/{run}/cancel | answer: {value}; the worker takes {answers} | send answer as one of {answers}, as `weles runs answer` does | refused, exit 2, HTTP 400 | tests/runs/failures.mjs::runs_answer_the_worker_does_not_take_is_refused |
run-not-found | finding run {run} to {verb} | the worker holds no record of it and runs no child for it: it belongs to another worker, or it never started | run_not_found: no record and no live child for {run} on this worker | check the id against `weles runs list` and read it from the worker that started it | not_found, exit 1, HTTP 404 | tests/runs/failures.mjs::runs_show_of_a_run_the_worker_never_had_is_not_found |
run-finished | reaching run {run} to {verb} | the run has finished, so nothing is left to {verb} | run_not_running_here: the run finished at {completed}; there is nothing to {verb} | read what it left with `weles runs show {run}`, and start a new run if its work is still needed | refused, exit 1, HTTP 409 | tests/runs/failures.mjs::runs_cancel_of_a_finished_run_names_when_it_finished |
run-orphaned | reaching run {run} to {verb} | the server that ran it has stopped: its record still says {status}, and this server has no child for it | run_not_running_here: the run is recorded as {status} but this server has no child for it | start the run again if its work is still needed; `weles runs show {run}` prints the record it left | refused, exit 1, HTTP 409 | tests/runs/failures.mjs::runs_cancel_of_a_run_whose_server_stopped_names_its_status |
run-not-waiting | answering run {run} | the run waits for no answer from a person | run_not_waiting: it stands at {stage} | answer only a run `weles runs list` shows as WAITS FOR YOU; end this one with weles runs cancel {run} --detail <who and why> | refused, exit 1, HTTP 409 | tests/runs/failures.mjs::runs_answer_to_a_run_that_waits_on_nobody_names_its_stage |
run-record-unreadable | reading run {run}'s record on the worker | its result file {file} in the worker's detached-runs directory cannot be read as a JSON document: a write was cut off, its permissions changed, or another program wrote it | {answer} | on the worker's host, move ~/.stado/weles-detached-runs/{file} aside; the run's evidence stays under its recordings, and a later run of that id writes a new record | config, exit 1, HTTP 500 | tests/runs/failures.mjs::runs_show_of_a_record_that_is_not_json_names_the_file |
answer-unexpected | {operation} at {endpoint} | the worker answered JSON of another shape than the runs it lists, shows, answers and cancels: it runs another Weles release than this CLI | {answer} | compare `weles version` with `weles worker version` and install the same Weles release on both | config, exit 1, HTTP 503 | tests/runs/failures.mjs::runs_list_through_an_answer_without_a_run_list_names_it |
security#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
options-unknown | reading the weles security command line | it holds a word or option security does not take: security accepts --provider with --login-role, or --run; it cannot enable, disable or enrol 2FA | not taken: {given} | leave out {given}: weles security --provider google --login-role <skarbiec-role> [--json], or weles security --run <run-id> [--json] | refused, exit 2, HTTP 403 | tests/security/failures.mjs::security_refuses_an_option_it_does_not_take |
selector-invalid | choosing what weles security reads | provide exactly one of --login-role <skarbiec-role> or --run <run-id>, each with a value that is not blank | given: {given} | give --login-role with the Skarbiec role the Google login plays to start a check, or --run with the run id a start answered to read it back | refused, exit 2, HTTP 403 | tests/security/failures.mjs::security_without_exactly_one_selector_is_refused |
provider-unsupported | choosing the identity provider whose account is read | security needs --provider naming the identity provider whose account it reads; Weles reads {providers} | --provider: {provider} | give --provider {providers} | refused, exit 2, HTTP 403 | tests/security/failures.mjs::security_refuses_a_provider_it_cannot_read |
run-takes-no-start-options | reading the weles security --run command line | --run reads a started run back and takes no --provider | given with --run: {given} | leave out {given}: weles security --run <run-id> [--json] | refused, exit 2, HTTP 403 | tests/security/failures.mjs::security_reading_a_run_refuses_a_provider |
run-id-invalid | reading the weles security --run command line | --run {run} is no run id: a run id starts with a letter or digit and holds only letters, digits, _ and -, as the start answered it | --run {run}; nothing was asked of the executor | give --run with the run id `weles security --provider google --login-role <role>` answered, or one `weles runs list` prints | refused, exit 2, HTTP 403 | tests/security/failures.mjs::security_refuses_a_run_id_that_is_none |
observation-invalid | {operation}: reading the 2FA observation of run {run} | the run recorded a result that is not a weles.account-security.v1 observation of the login it was bound to: a field is missing or of another type, it names another login item, or ok disagrees with two_factor_enabled | result: {answer} | compare `weles version` with the release `stado service status com.wisent.weles` names and move the executor onto it with `stado service update`, then submit a new check; an invalid observation is never read as a verdict | config, exit 1, HTTP 503 | tests/security/failures.mjs::security_reading_an_invalid_observation_names_it |
password#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
options-unknown | reading the weles password command line | it holds a word or option password does not take: password accepts --provider with --login-role or --login-item, --organization and --account, or --run, and --json | not taken: {given} | leave out {given}: weles password --provider google --login-role <skarbiec-role>|--login-item <skarbiec-item> --organization <skrzynka-organization> --account <mailbox-email> [--json], or weles password --run <run-id> [--json] | refused, exit 2, HTTP 403 | tests/security/passwords/password-failures.mjs::password_refuses_an_option_it_does_not_take |
selector-invalid | choosing the login weles password signs in with | provide exactly one of --login-role <skarbiec-role>, --login-item <skarbiec-item> or --run <run-id>, each with a value that is not blank | given: {given} | give --login-role with the role the Google login plays in Skarbiec, --login-item with the exact login item, or --run with the run id a start answered | refused, exit 2, HTTP 403 | tests/security/passwords/password-failures.mjs::password_without_exactly_one_selector_is_refused |
organization-missing | reading --organization | --organization <id> names the Skrzynka organization the mailbox is declared in; none is assumed | --organization: {value} | give --organization with the Skrzynka organization the mailbox is declared in; `skrzynka --organization <id> mailbox list` shows the mailboxes an organization declares | refused, exit 2, HTTP 403 | tests/security/passwords/password-failures.mjs::password_without_an_organization_is_refused |
account-missing | reading --account | --account must name the Gmail account the mailbox belongs to; it is compared with the login before any browser opens, and none is inferred from the login | --account: {value} | give --account <mailbox-email>, the address of the mailbox | refused, exit 2, HTTP 403 | tests/security/passwords/password-failures.mjs::password_without_an_account_is_refused |
provider-unsupported | choosing the identity provider that issues the app password | password needs --provider naming the identity provider that issues it; Weles issues through {providers} | --provider: {provider} | give --provider {providers} | refused, exit 2, HTTP 403 | tests/security/passwords/password-failures.mjs::password_refuses_a_provider_it_cannot_issue_through |
run-takes-no-start-options | reading the weles password --run command line | --run reads a started run back and takes no --organization, --provider or --account | given with --run: {given} | leave out {given}: weles password --run <run-id> [--json] | refused, exit 2, HTTP 403 | tests/security/passwords/password-failures.mjs::password_reading_a_run_refuses_start_options |
run-id-invalid | reading the weles password --run command line | --run {run} is no run id: a run id starts with a letter or digit and holds only letters, digits, _ and -, as the start answered it | --run {run}; nothing was asked of the executor | give --run with the run id `weles password --provider google --login-role <role> ...` answered, or one `weles runs list` prints | refused, exit 2, HTTP 403 | tests/security/passwords/password-failures.mjs::password_refuses_a_run_id_that_is_none |
oauth-callback#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
options-unknown | reading the weles oauth-callback command line | it holds a word or option oauth-callback does not take: oauth-callback accepts --provider with --login-role or --login-item, --client-role or --client-item and --callback, or --run, and --json | not taken: {given} | leave out {given}: weles oauth-callback --provider google --login-role <skarbiec-role>|--login-item <skarbiec-item> --client-role <skarbiec-role>|--client-item <skarbiec-item> --callback <https-url> [--json], or weles oauth-callback --run <run-id> [--json] | refused, exit 2, HTTP 403 | tests/security/oauth/oauth-callback-failures.mjs::oauth_callback_refuses_an_option_it_does_not_take |
value-blank | reading --{option} | --{option} needs a value that is not blank | --{option}: {value} | give --{option} its value, or leave the option out | refused, exit 2, HTTP 403 | tests/security/oauth/oauth-callback-failures.mjs::oauth_callback_refuses_a_blank_value |
run-takes-no-start-options | reading the weles oauth-callback --run command line | --run reads a started run back and takes no other option but --json | given with --run: {given} | leave out {given}: weles oauth-callback --run <run-id> [--json] | refused, exit 2, HTTP 403 | tests/security/oauth/oauth-callback-failures.mjs::oauth_callback_reading_a_run_refuses_start_options |
run-id-invalid | reading the weles oauth-callback --run command line | --run {run} is no run id: a run id starts with a letter or digit and holds only letters, digits, _ and -, as the start answered it | --run {run}; nothing was asked of the executor | give --run with the run id a start answered, or one `weles runs list` prints | refused, exit 2, HTTP 403 | tests/security/oauth/oauth-callback-failures.mjs::oauth_callback_refuses_a_run_id_that_is_none |
provider-unsupported | choosing whose OAuth client weles oauth-callback edits | oauth-callback needs --provider naming whose OAuth client it edits; Weles edits {providers} | --provider: {provider} | give --provider {providers} | refused, exit 2, HTTP 403 | tests/security/oauth/oauth-callback-failures.mjs::oauth_callback_refuses_a_provider_it_does_not_edit |
login-selector-invalid | choosing the login weles oauth-callback signs in with | provide exactly one of --login-role <skarbiec-role> or --login-item <skarbiec-item> | given: {given} | give --login-role with the role the owner login plays in Skarbiec, or --login-item with the exact login item | refused, exit 2, HTTP 403 | tests/security/oauth/oauth-callback-failures.mjs::oauth_callback_without_exactly_one_login_is_refused |
client-selector-invalid | choosing the OAuth client weles oauth-callback edits | provide exactly one of --client-role <skarbiec-role> or --client-item <skarbiec-item> | given: {given} | give --client-role with the role the OAuth client item plays in Skarbiec, or --client-item with the exact item; the client secret is never asked for | refused, exit 2, HTTP 403 | tests/security/oauth/oauth-callback-failures.mjs::oauth_callback_without_exactly_one_client_is_refused |
callback-invalid | reading --callback | --callback must be the https URL, with a host, the provider must accept | --callback: {callback} | give --callback the sign-in server's https callback address, e.g. https://<host>/auth/v1/callback | refused, exit 2, HTTP 403 | tests/security/oauth/oauth-callback-failures.mjs::oauth_callback_refuses_a_callback_that_is_not_https |
Sign-in#
How weles login --provider apple fails, from the command line to the run's record.
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
options-unknown | reading the weles {command} command line | it holds a word or option {command} does not take: {command} either starts a run with {start} or reads one back with {read} | not taken: {given} | run `weles {command} --help` for the two forms and give one of them | refused, exit 2, HTTP 403 | tests/sign-in/failures.mjs::login_refuses_an_option_it_does_not_take |
provider-unsupported | choosing the provider weles {command} goes through | --provider is missing or names a provider Weles has no {command} for | --provider: {provider}; providers Weles takes: {providers} | give --provider {providers} | refused, exit 2, HTTP 403 | tests/sign-in/failures.mjs::login_refuses_a_provider_it_cannot_sign_in_to |
option-value-missing | reading --{option} of weles {command} | --{option} is required and was given no value | --{option}: {value} | give --{option} <{meaning}> | refused, exit 2, HTTP 403 | tests/sign-in/failures.mjs::login_refuses_a_missing_execution_host |
account-role-invalid | reading --account-role of weles {command} | a Skarbiec role is lowercase letters, digits and hyphens, starting with a letter or digit, at most 127 characters | --account-role: {value} | give the role the Apple account plays in Skarbiec (the item tagged stado:role:<role>), or leave it out for {standard} | refused, exit 2, HTTP 403 | tests/sign-in/failures.mjs::login_refuses_an_account_role_that_is_not_a_role |
confirmation-mismatch | authorizing one run of weles {command} | the operator's confirmation is not the exact phrase of this command, so no authorization is issued | --confirm: {value} | give --confirm "{phrase}" exactly, in capitals | refused, exit 2, HTTP 403 | tests/sign-in/failures.mjs::login_refuses_a_confirmation_that_is_not_the_phrase |
execution-host-invalid | reading --{option} of weles {command} | a Stado host name is letters, digits, ., _ and -, starting with a letter or digit, at most 253 characters | --{option}: {value} | give the registry target, as `stado registry host list` names it | refused, exit 2, HTTP 403 | tests/sign-in/failures.mjs::login_refuses_an_execution_host_that_is_not_a_host_name |
expiry-invalid | reading --expires-in-minutes of weles {command} | the lifetime of the one-use capabilities is a positive whole number of minutes | --expires-in-minutes: {value} | give --expires-in-minutes <n>, the minutes the run may take, such as 15 | refused, exit 2, HTTP 403 | tests/sign-in/failures.mjs::login_refuses_an_expiry_that_is_not_whole_minutes |
run-id-missing | reading an authorized Apple run back | --run names no run id | --run: no value | give --run <run-id>, the run the start answered | refused, exit 2, HTTP 403 | tests/sign-in/failures.mjs::login_refuses_run_without_an_id |
run-id-invalid | reading the weles {command} --run command line | --run {run} is no run id: a run id starts with a letter or digit and holds only letters, digits, _ and -, as the start answered it | `weles {command} --run {run}`; nothing was asked of the executor | run `weles {command} --run <run-id>` with the run id the start answered, or one `weles runs list` prints | refused, exit 2, HTTP 403 | tests/sign-in/failures.mjs::login_refuses_a_run_id_that_is_none |
install-incomplete | loading the Apple capability issuer {module} | the installed Weles lacks the module that asks Stado for the capabilities: the install is incomplete or was built without src/auth | {answer} | reinstall the Weles release through Stado (its payload carries src/auth), then run again | config, exit 1, HTTP 503 | tests/sign-in/failures.mjs::login_from_an_install_without_the_issuer_names_the_module |
capabilities-not-issued | issuing the Apple capabilities on {host} | Stado answered, but not with capabilities issued for that host: it issued them for another target, or reported a status other than issued | {answer} | run `stado identity issue --provider apple --target {host} ...` by hand to see what it answers, and correct the target or Stado's identity configuration | refused, exit 1, HTTP 403 | tests/sign-in/failures.mjs::login_whose_capabilities_name_another_host_is_refused |
challenge-not-confirmed | relaying the Apple two-factor challenge (stado {command}) | Stado answered without confirming the challenge as {expected} with its holder, user and destination | {answer} | run the same stado identity relay command by hand on the execution host, and correct the session or identity it names | refused, exit 1, HTTP 403 | tests/sign-in/failures.mjs::relay_whose_stado_answer_lacks_the_holder_is_refused |
relay-input-missing | relaying the Apple two-factor challenge | the Apple trajectory runs without {missing}: it was started without the params weles login, agreement and certificate give every run | identity: {identity}; authorization id: {authorization} | start the run with `weles login`, `weles agreement` or `weles certificate`, which issue the authorization and name the account role | config, exit 1, HTTP 503 | tests/sign-in/failures.mjs::relay_without_an_authorization_id_is_refused |
apple-no-trusted-device | waiting for the Apple two-factor code of {account} | Apple lists no trusted device for this Apple ID, so it notifies none, the account holder's Mac included: that Mac is not signed in to iCloud with this Apple ID, or Apple no longer trusts it; the code goes only to a trusted phone number | Apple answered noTrustedDevices {flag}, {devices} trusted devices and {phones} trusted phone numbers; nothing was relayed | sign in to iCloud with this Apple ID on the Mac Stado places the account holder on (System Settings, Apple Account), so Apple trusts it, then run again; `stado identity relay --provider apple --identity <role> --preflight` names that holder | refused, exit 1, HTTP 403 | tests/sign-in/failures.mjs::an_apple_id_with_no_trusted_device_is_named_before_the_relay |
apple-codes-held | waiting for the Apple two-factor code of {account} | Apple holds back new codes for this Apple ID ({flag} is true), so no device is notified until Apple lifts it | Apple's second-factor state: {flags} | wait until Apple lets codes be sent again, then run again once; every further attempt spends one of the few Apple allows | rate_limit, exit 69, HTTP 429, retryable | tests/sign-in/failures.mjs::an_apple_id_whose_codes_are_held_is_named_before_the_relay |
apple-device-silent | relaying the Apple two-factor code of {account} | Stado's relay from the account holder's session confirmed no code: that session is not a device Apple notifies (signed in to iCloud with another Apple ID, or its notifications for it are off), it never showed the code, or Apple sent none | where Apple sent it: {delivery}; the relay: {relay} | check that the Mac Stado places the account holder on is signed in to iCloud with this Apple ID and shows its sign-in notifications, then run again; `stado identity relay --provider apple --identity <role> --preflight` names that holder | refused, exit 1, HTTP 403 | tests/sign-in/failures.mjs::a_relay_that_confirms_no_code_names_where_apple_sent_it |
certificate#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
subject-invalid | reading --subject of weles certificate | --subject is the certificate request's distinguished name, and it does not start with /CN= | --subject: {value} | give --subject "/CN=<organization> Developer ID Application/O=<organization>/C=<country>" | refused, exit 2, HTTP 403 | tests/certificate/failures.mjs::certificate_refuses_a_subject_without_a_common_name |
path-not-absolute | reading --{option} of weles certificate | --{option} must be an absolute path on this machine | --{option}: {value} | give --{option} as a path starting at / | refused, exit 2, HTTP 403 | tests/certificate/failures.mjs::certificate_refuses_a_relative_key_path |
key-and-store-apart | reading weles certificate --run | --private-key and --store-host go together: both store the identity in the vault, neither leaves it in files | --private-key: {key}; --store-host: {host} | give both to keep the identity in the vault of the store host, or neither to write only the certificate | refused, exit 2, HTTP 403 | tests/certificate/failures.mjs::certificate_refuses_a_key_without_a_store_host |
file-exists | writing the {what} to {path} | a file already stands there, and weles certificate never replaces a {what}: a key that is replaced signs nothing its certificate names | {path} exists | give --{option} a path where nothing stands, or move the file there away yourself if it is no longer needed | refused, exit 1, HTTP 403 | tests/certificate/failures.mjs::certificate_refuses_to_replace_a_private_key |
private-key-missing | reading the private key {path} | no file stands at --private-key: it is not the path the start wrote the key to, or the key was moved | {path} does not exist | give --private-key the path the start answered as private_key, on the machine that ran the start | not_found, exit 1, HTTP 404 | tests/certificate/failures.mjs::certificate_refuses_a_private_key_that_is_not_there |
openssl-failed | running openssl {operation} | openssl is not on the PATH of weles, or it refused the input: a key path it cannot write, a key that does not match the certificate | openssl said: {answer} | install openssl on this machine (it ships with macOS), and give paths in a directory this user can write | config, exit 1, HTTP 503 | tests/certificate/failures.mjs::certificate_without_openssl_names_it |
certificate-count | reading the certificate run {run} returned | the run ended ok but its output holds not exactly one CERTIFICATE_BASE64 line: it is a run of another trajectory, or the trajectory stopped printing one | {count} certificate lines | read the run with `weles runs show {run}`, and give --run the run weles certificate started | not_found, exit 1, HTTP 404 | tests/certificate/failures.mjs::certificate_from_a_run_without_one_names_the_count |
certificate-invalid | reading the certificate run {run} returned | the bytes the run returned are not a DER X.509 certificate, so nothing was written | openssl said: {answer} | read the run with `weles runs show {run}`; start a new run with weles certificate when its output is damaged | refused, exit 1, HTTP 403 | tests/certificate/failures.mjs::certificate_that_is_not_x509_is_refused |
receipt-invalid | reading the Apple two-factor receipt run {run} returned | the run returned more than one receipt, or one that is not JSON, or one that lacks source capability, provider_accepted true, a holder, a user and a destination | {answer} | read the run with `weles runs show {run}`; a run without a complete receipt was not authorized by its holder, so start a new one | refused, exit 1, HTTP 403 | tests/certificate/failures.mjs::certificate_with_a_receipt_that_is_not_json_is_refused |
certificate-unwritable | writing the certificate to {path} | the file cannot be created: its directory does not exist, or this user may not write there | the system said: {answer} | give --certificate-out a path in a directory this user can write | config, exit 1, HTTP 503 | tests/certificate/failures.mjs::certificate_into_a_missing_directory_is_named |
common-name-missing | naming the signing identity of the certificate run {run} returned | the certificate names no common name, which is the identity codesign selects, so it cannot be kept as a signing identity | subject: {subject} | start a new run with a --subject that begins with /CN=; the certificate was written, the vault was not changed | refused, exit 1, HTTP 403 | tests/certificate/failures.mjs::certificate_without_a_common_name_is_not_stored |
worker#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
action-missing | reading the weles worker command line | weles worker needs an action, and none was named | the actions weles worker takes: {verbs} | run `weles worker <action>` with one of {verbs}; `weles --help` describes each | refused, exit 2, HTTP 403 | tests/worker/failures.mjs::worker_without_an_action_names_the_actions |
action-unknown | reading the weles worker command line | unknown worker action: {action} is not an action weles worker takes | action: {action}; weles worker takes {verbs} | name one of {verbs} | refused, exit 2, HTTP 403 | tests/worker/failures.mjs::worker_refuses_an_action_it_does_not_take |
options-unknown | reading the weles worker {action} command line | it holds a word or option worker {action} does not take; every worker action takes only --json | not taken: {given} | leave out {given}: weles worker {action} [--json] | refused, exit 2, HTTP 403 | tests/worker/failures.mjs::worker_refuses_a_word_or_option_its_action_does_not_take |
control-in-progress | worker {action} of the task dispatcher | another worker control request is changing the dispatcher now, and the worker takes one at a time, since a recovery rewrites one durable queue | worker_control_in_progress: a control request was still running when {action} arrived | wait until the other request answers (`weles worker status` shows the dispatcher it left), then run weles worker {action} again | rate_limit, exit 69, HTTP 409, retryable | tests/worker/failures.mjs::worker_control_while_another_runs_is_refused |
dispatcher-busy | worker {action}: recovering the task dispatcher | a task is running or being started, or the server is shutting down, and the dispatcher recovers its durable queue only while nothing runs | worker_busy: dispatcher {dispatcher} | wait until `weles worker status` shows active 0 and draining false, or end the run with `weles runs cancel <run-id> --detail <who and why>`, then run weles worker {action} again | rate_limit, exit 69, HTTP 409, retryable | tests/worker/failures.mjs::worker_restart_while_a_task_runs_is_refused |
recovery-failed | worker {action}: recovering the durable task queue | the worker could not read back the task state it persisted under ~/.stado/weles-detached-runs/public-tasks: an entry that is no task, a task or reservation file that is not JSON, or a directory it cannot read; admission stays paused until a recovery succeeds | worker_recovery_failed: {detail} | on the worker's host, move aside the entry the failure names under ~/.stado/weles-detached-runs/public-tasks, then run weles worker restart | config, exit 1, HTTP 502 | tests/worker/failures.mjs::worker_restart_over_task_state_that_is_no_task_names_the_entry |
dispatcher-not-running | worker {action} of the task dispatcher | the dispatcher does not run after it: starting a queued task failed (its failure names the task and why), or the server began shutting down | worker_not_running_after_control_action: dispatcher {dispatcher} | on the worker's host, move aside the task file the failure names under ~/.stado/weles-detached-runs/public-tasks, then run weles worker restart | config, exit 1, HTTP 502 | tests/worker/failures.mjs::worker_start_over_a_queued_task_that_cannot_be_read_names_it |
answer-unexpected | {operation} at {endpoint} | the endpoint answered JSON of another shape than a resident Weles worker reports: it runs another Weles release than this CLI, or it is not Weles | {answer} | compare `weles version` with the release `stado service status com.wisent.weles` names, and move the worker onto the same release with `stado service update` | config, exit 1, HTTP 503 | tests/worker/failures.mjs::worker_status_through_an_answer_of_another_shape_names_it |
process-replaced | {operation} at {endpoint} | the worker answered its control with another process than the one it reported before: the endpoint restarts the service itself, as the retired native worker did, instead of controlling the dispatcher in the process that answers | process {before} before, {after} after | move the worker onto the current Weles release with `stado service update`, whose /worker routes control the resident dispatcher, then read `weles worker status` | config, exit 1, HTTP 503 | tests/worker/failures.mjs::worker_control_that_replaces_the_process_is_refused |
launch-address-unset | reading the address the Weles service declaration assigns | {name} is not set: Stado writes WELES_API_HOST and WELES_API_PORT into the unit, and this process was started outside it | {name}: not set | start the API through its unit (`stado service ensure weles --host <this host> --reason <why>`), or set {name} to the address the declaration assigns | config, exit 1, HTTP 503 | tests/worker/launching/launch-failures.mjs::a_launch_without_its_address_names_the_variable |
launch-port-duplicate | starting the Weles API on port {port} | another Weles API already serves that port; a second process would only stand by | weles api port {port} is already served by {holder}, which answers /healthz as {source} | the existing API was not stopped; retire the duplicate service declaration with `stado service retire` | refused, exit 1, HTTP 403 | tests/worker/launching/launch-failures.mjs::a_launch_onto_a_port_a_weles_serves_is_refused |
launch-port-foreign | starting the Weles API on port {port} | a process that is not a Weles API holds the port, and standing by would leave the service reported active while every caller fails | weles api port {port} is held by {holder}, which is not a Weles API: {detail} | stop or re-place the holder, then start the unit with `stado service ensure weles --host <this host> --reason <why>` | infra_down, exit 69, HTTP 503, retryable | tests/worker/launching/launch-failures.mjs::a_launch_onto_a_port_another_program_holds_is_refused |
launch-workload-unset | reading the workload Weles acquires its secrets as | SKARBIEC_WORKLOAD_ID is not set: the service unit names the workload Skarbiec's registry knows Weles by | SKARBIEC_WORKLOAD_ID: not set | start the API through its unit (`stado service ensure weles --host <this host> --reason <why>`), whose declaration sets it | config, exit 1, HTTP 503 | tests/worker/launching/launch-failures.mjs::a_launch_without_its_workload_names_it |
launch-release-incomplete | reading {what} of this release | the release payload lacks it or holds it in another shape: it was built or unpacked incompletely | {answer} | install the release again with `stado product install weles --surface service` | config, exit 1, HTTP 503 | tests/worker/launching/launch-failures.mjs::a_release_without_its_capability_routes_is_named |
launch-step-refused | the startup step {step} | the program it runs could not start, or exited refusing; its own words name the dependency | {answer} | correct what the step's own words name, then start the unit again with `stado service ensure weles --host <this host> --reason <why>` | infra_down, exit 69, HTTP 503, retryable | tests/worker/launching/launch-failures.mjs::a_startup_step_that_refuses_is_quoted |
launch-forward-unpublished | reading the address Stado published for {service} | the forward marker is missing, unreadable or empty: the service directory has not published {service} on this host | {marker}: {answer} | let `stado service directory publish` write it, then start the unit again | config, exit 1, HTTP 503 | tests/worker/launching/launch-failures.mjs::a_launch_without_the_object_api_forward_names_it |
launch-answer-empty | the startup step {step} | it succeeded and printed nothing where it promises {what} | an empty answer | run the step by hand (`node src/_shared/vaulting/skarbiec-runtime.mjs <question>` for Skarbiec) and correct what Stado declares | config, exit 1, HTTP 503 | tests/worker/launching/launch-failures.mjs::a_startup_answer_that_is_empty_is_named |
launch-refusal-unservable | serving the refused startup on {host}:{port} | the address the API would listen on cannot be bound while the startup is refused: another process took the port after the launcher checked it, or the host no longer holds that address | {answer} | find what holds {host}:{port} (`lsof -nP -iTCP:{port} -sTCP:LISTEN`), stop or re-place it, then let the unit start again | infra_down, exit 69, HTTP 503, retryable | tests/worker/launching/launch-failures.mjs::a_refused_startup_whose_address_is_taken_is_named |
api-bearer-rejected | authenticating {method} {path} | the request carries no Authorization bearer or x-api-key equal to this API's token: none was sent, it is another deployment's, or the item was rotated after it was read | a request without the API token | send the token of the item playing echo-weles-api as the bearer (`stado credentials get --role echo-weles-api --field token`) | auth, exit 1, HTTP 401 | tests/api/failures.mjs::a_token_route_without_the_api_token_is_refused |
api-token-unset | authenticating {method} {path} | this API was started without WELES_API_TOKEN, so no bearer can be admitted: it was run outside its unit, whose launcher acquires the token | WELES_API_TOKEN: not set | start the API through its unit (`stado service ensure weles --host <this host> --reason <why>`), whose launcher acquires WELES_API_TOKEN | config, exit 1, HTTP 500 | tests/api/failures.mjs::an_api_started_without_its_token_names_it |
api-route-unknown | routing {method} {path} | no route of this Weles API answers that method at that address | {method} {path} | send the request to a route the API answers; https://weles.wisent.com/docs/errors lists them | not_found, exit 1, HTTP 404 | tests/api/failures.mjs::an_address_no_route_answers_is_named |
api-route-failed | answering {method} {path} | the route raised an error it declares no cause for, so nothing analysed why it failed: a defect of this Weles release | {answer} | run `weles errors show worker` for the causes the route declares, and report the observed error with the route and `weles worker version`, so the cause is declared where it is raised | unknown, exit 1, HTTP 500 | tests/api/failures.mjs::a_route_failing_with_an_undeclared_error_is_named |
api-body-not-json | reading the body of {method} {path} | the body is not one JSON document, so the route cannot read what it is asked | {bytes} bytes; parser: {answer} | send the request as one JSON object (Content-Type: application/json), or no body | refused, exit 1, HTTP 400 | tests/api/failures.mjs::a_run_whose_body_is_not_json_is_refused |
api-answer-unserializable | answering {method} {path} | the answer could not be serialized, so it cannot be redacted: it holds a value JSON cannot carry (a BigInt, or an object that holds itself), and an answer is never sent unredacted; a defect of this release | serializer: {answer} | report it with `weles worker version` and the route; the request had no answer, and nothing it changed was undone | unknown, exit 1, HTTP 500 | tests/api/failures.mjs::an_answer_json_cannot_carry_is_named |
api-field-missing | reading {field} of {method} {path} | {field} is required, and the body does not carry it | a body without {field} | send {field}: {expected} | refused, exit 1, HTTP 400 | tests/api/records-failures.mjs::a_record_request_without_its_field_is_refused |
api-field-invalid | reading {field} of {method} {path} | {field} is not {expected} | {field}: {value} | send {field} as {expected} | refused, exit 1, HTTP 400 | tests/api/pages-failures.mjs::a_page_request_with_a_field_of_another_shape_is_refused |
api-body-unread | reading the body of {method} {path} | the connection ended with an error after {bytes} bytes of the body, so the route never had the whole request | {bytes} bytes read; connection: {errno}: {answer} | send the request again; a proxy between the caller and the worker that cuts bodies shows in its own log | infra_down, exit 69, HTTP 400, retryable | tests/api/failures.mjs::a_body_cut_off_by_the_caller_is_named |
diagnostics-run-id-invalid | reading the run whose evidence GET /diagnostics names | a run id is a letter or digit followed by letters, digits, ., _ and -, since it names the run directory, and this is not one | run id: {run} | give the run id exactly as `weles runs list` or the run that started it printed it | refused, exit 1, HTTP 400 | tests/api/failures.mjs::evidence_of_a_run_id_that_cannot_name_a_run_is_refused |
diagnostics-wait-invalid | reading which file of run {run} to answer, and whether to wait for it | wait=terminal waits for the run outcome only, so it requires path=run-result.json; no other wait exists | wait: {wait}; path: {path} | ask for path=run-result.json with wait=terminal, or leave wait out | refused, exit 1, HTTP 400 | tests/api/failures.mjs::a_wait_for_another_file_is_refused |
diagnostics-not-found | reading the evidence manifest of run {run} | this worker holds no evidence for that run: it ran on another worker, it has not written any yet, or its evidence was removed | no evidence directory for run {run} | check the run id against `weles runs list`, and ask the worker that ran it | not_found, exit 1, HTTP 404 | tests/api/failures.mjs::evidence_of_a_run_the_worker_never_had_is_not_found |
diagnostics-file-not-found | reading {path} of run {run} | the run's evidence holds no such file: it is not written yet, it names a path outside the run's evidence, or no path was given | path: {path} | name a file the run's manifest lists (GET /diagnostics/{run}), or wait for run-result.json with wait=terminal | not_found, exit 1, HTTP 404 | tests/api/failures.mjs::a_file_the_evidence_does_not_hold_is_not_found |
evidence-roots-unreadable | finding the recordings of run {run} | no readable recording root holds the run, and {count} root(s) where its recordings may live could not be read by the worker user, so the run cannot be called absent | unreadable: {roots} | give the worker's user read and search access to each root named (`ls -ld` on it shows its owner and mode), or move a root that no longer belongs to Weles aside, then ask again | config, exit 1, HTTP 500 | tests/api/evidence-failures.mjs::evidence_under_a_root_the_worker_cannot_read_names_the_root |
evidence-file-unexaminable | examining {path} of run {run} | the file exists under the run's recordings but the worker user cannot examine it ({code}), so it is neither served nor reported absent | {file}: {answer} | give the worker's user read and search access to {file} and the directories above it (`ls -ld` shows each one's owner and mode), then ask again | config, exit 1, HTTP 500 | tests/api/evidence-failures.mjs::evidence_file_the_worker_cannot_examine_names_it |
evidence-result-unexaminable | examining the persisted outcome of run {run} | the results directory or the run's result file cannot be examined by the worker user ({code}), so the outcome is neither served nor reported absent | {file}: {answer} | give the worker's user read and search access to {directory} (`ls -ld {directory}` shows its owner and mode), then ask again | config, exit 1, HTTP 500 | tests/api/evidence-failures.mjs::an_outcome_the_worker_cannot_examine_names_the_file |
evidence-result-vanished | waiting for the outcome of run {run} | the run's result file existed when the wait began and was removed before the run ended: something other than the worker deleted or moved it | no {file} any more; the wait had read {reads} version(s) of it | find what removes files under {directory} (a cleanup job, another worker sharing the home); the run itself is still on `weles runs show {run}` | config, exit 1, HTTP 500 | tests/api/evidence-failures.mjs::an_outcome_removed_during_the_wait_is_named |
evidence-result-not-json | reading the persisted outcome of run {run} | the run's result file is not one JSON document: the worker writes it whole and replaces it in one rename, so it was truncated or written by something else | {file} ({bytes} bytes) begins {head}; parser: {answer} | move {file} aside and read the run with `weles runs show {run}`; the run's recordings under GET /diagnostics/{run} still hold what it did | config, exit 1, HTTP 500 | tests/api/evidence-failures.mjs::an_outcome_that_is_not_json_is_named |
evidence-result-invalid | reading the persisted outcome of run {run} | the run's result is neither running (status running, ok null) nor ended (a status other than running and ok true or false), so whether the run ended cannot be read from it | {file}: status {status}, ok {ok} | compare `weles worker version` with the release that wrote {file}; move the file aside if another program wrote it, and read the run with `weles runs show {run}` | config, exit 1, HTTP 500 | tests/api/evidence-failures.mjs::an_outcome_neither_running_nor_ended_is_named |
evidence-results-directory-gone | waiting for the outcome of run {run} | the directory the worker persists run outcomes in was moved or removed while the wait watched it, so the run's outcome can no longer arrive there | {directory} renamed or removed; the run's file was {file} | restore {directory} (the worker recreates it with its next run) and stop whatever moves it; ask again once `weles runs show {run}` shows the run ended | config, exit 1, HTTP 500 | tests/api/evidence-failures.mjs::an_outcome_whose_directory_is_removed_during_the_wait_is_named |
evidence-results-unwatchable | waiting for the outcome of run {run} | the operating system stopped reporting changes to the results directory ({code}): its file watches are exhausted or the directory sits on a file system without change events | watching {directory}: {answer} | raise the watch limit of the worker's host (fs.inotify.max_user_watches on Linux) or keep {directory} on a local disk, then ask again; without wait=terminal the outcome reads at once | infra_down, exit 69, HTTP 503, retryable | tests/api/evidence-failures.mjs::an_outcome_whose_directory_cannot_be_watched_is_named |
evidence-recording-root-linked | reading the named documents run {run} recorded | the run's recording root is a symbolic link or not a directory, and the worker never follows a link out of a run's tree | {root}: {kind} | replace {root} with the run's own directory (the worker writes it as one); the documents read as unreadable until then | config, exit 1, HTTP 503 | tests/api/evidence-failures.mjs::documents_of_a_linked_recording_root_are_named_unreadable |
evidence-recording-root-outside | reading the named documents run {run} recorded | the run's recording root resolves outside the recordings directory, so reading it would serve files no run of this worker wrote | {root} resolves to {real}, outside {recordings} | move the run's recordings under {recordings} as a directory of their own; the documents read as unreadable until then | config, exit 1, HTTP 503 | tests/api/evidence-failures.mjs::documents_of_a_recording_root_outside_the_recordings_are_named_unreadable |
captcha-answer-unawaited | keeping the captcha answer a provider sent for run {run} | no run of this worker waits for that token: the run ended, another callback already delivered it, or no run ever drew it | no {token}.waiting marker for run {run} | nothing to do for a repeated callback; a run still waiting for its answer has a .waiting marker in its directory under ~/.stado/weles-captcha-answers, so check the provider's callback URL names that run and token | not_found, exit 1, HTTP 404 | tests/api/failures.mjs::a_captcha_answer_no_run_waits_for_is_refused |
captcha-answer-unwritable | keeping the captcha answer a provider sent for run {run} | the run's answers directory refused the marker check or the write: its permissions changed, or the disk is full or read-only | {answer} | give the worker's user write access to ~/.stado/weles-captcha-answers and free space on its disk; the provider retries its callback | infra_down, exit 69, HTTP 500, retryable | tests/api/failures.mjs::a_captcha_answer_the_run_directory_refuses_is_named |
serve-identity-unreadable | reading the Weles release identity {path} | a release states its identity in release/source-identity.json (release/stado-build.sh writes it into the worker payload) or, for a package installed from a git archive, in the commit release/source-commit holds beside package.json; this tree has neither readable: it was checked out, or the files were removed | {answer} | install Weles as a release (`stado product install weles --surface service --host <this host>`, or `stado product install weles --surface cli` for the CLI package) and start it through its unit; `ls -l {path}` shows what is there | config, exit 1, HTTP 503 | tests/worker/launching/serve-failures.mjs::an_api_without_its_release_identity_names_it |
serve-release-unstated | reading which release this process is | WELES_WORKER_RELEASE_VERSION is unset, or WELES_WORKER_RELEASE_SHA256 is not a lowercase SHA-256: release/stado-launcher.sh exports both from the payload it unpacked, so this process was not started as a release | WELES_WORKER_RELEASE_VERSION: {version}; WELES_WORKER_RELEASE_SHA256: {digest} | start the API through its unit (`stado product install weles --surface service --host <this host>` installs it), whose launcher exports both | config, exit 1, HTTP 503 | tests/worker/launching/serve-failures.mjs::an_api_started_without_its_release_names_it |
serve-identity-mismatch | proving the release identity {path} is the deployed release | the identity is not a weles.source-identity.v1 document of weles-worker at the version the launcher exported with a 40-digit commit: the runtime tree holds another release than the one unpacked | identity: {identity}; exported version: {version} | reinstall the service (`stado product install weles --surface service --host <this host>`) so its runtime tree and launcher name one release | config, exit 1, HTTP 503 | tests/worker/launching/serve-failures.mjs::an_api_whose_identity_names_another_release_is_refused |
serve-setting-unset | reading {name} | {name} is not set: the service declaration states {meaning}, and this process was started outside it | {name}: not set | start the API through its unit (`stado product install weles --surface service --host <this host>`), or set {name} to {meaning} | config, exit 1, HTTP 503 | tests/worker/launching/serve-failures.mjs::an_api_without_its_address_names_the_variable |
serve-setting-invalid | reading {name} | {name} is not {expected} | {name}: {value} | set {name} to {expected}, as the service declaration states it, or unset it where it is optional | config, exit 1, HTTP 503 | tests/worker/launching/serve-failures.mjs::an_api_with_a_port_that_is_no_port_names_it |
serve-origins-invalid | reading WELES_PUBLIC_API_ALLOWED_ORIGINS | it names no origin, mixes the wildcard * with origins, or holds an entry that is not an exact HTTPS origin (scheme and host, no path or trailing slash) | WELES_PUBLIC_API_ALLOWED_ORIGINS: {value}; {answer} | set it to * alone, or to a comma-separated list of exact https://<host> origins | config, exit 1, HTTP 503 | tests/worker/launching/serve-failures.mjs::an_api_whose_allowed_origins_are_no_origins_names_them |
serve-organization-invalid | reading WELES_PUBLIC_API_ORGANIZATION_ID | it is not a UUID, so no task could ever name the organization it serves | WELES_PUBLIC_API_ORGANIZATION_ID: {value} | set it to the organization UUID the service declaration assigns | config, exit 1, HTTP 503 | tests/worker/launching/serve-failures.mjs::an_api_whose_organization_is_no_uuid_names_it |
serve-receipt-key-invalid | reading the receipt signing key WELES_RECEIPT_PRIVATE_KEY | it is not a PEM private key, or not an Ed25519 one, which receipts are signed with | {answer} | set WELES_RECEIPT_PRIVATE_KEY to the Ed25519 private key the launcher acquires from Skarbiec; start the API through its unit | config, exit 1, HTTP 503 | tests/worker/launching/serve-failures.mjs::an_api_whose_receipt_key_is_no_key_names_it |
serve-receipt-keys-invalid | reading the receipt public keys WELES_RECEIPT_PUBLIC_KEYS_JSON | it is not a JSON object of key id to PEM public key, it is empty, or it carries no key under WELES_RECEIPT_KEY_ID ({key_id}) | {answer} | set it to the published receipt key set, which carries the public key of {key_id} | config, exit 1, HTTP 503 | tests/worker/launching/serve-failures.mjs::an_api_whose_receipt_key_set_lacks_its_key_names_it |
serve-receipt-key-mismatch | proving the receipt key {key_id} against its published public key | the public key WELES_RECEIPT_PUBLIC_KEYS_JSON names for {key_id} is not the one of WELES_RECEIPT_PRIVATE_KEY, so no receipt this API signs could be verified | the private key derives another public key than the set publishes for {key_id} | publish the public key of the private key in use under {key_id}, or acquire the private key the set names | config, exit 1, HTTP 503 | tests/worker/launching/serve-failures.mjs::an_api_whose_receipt_key_is_not_the_published_one_is_refused |
serve-policy-invalid | reading the browser-evidence policy of this release | dist/agent/browser-evidence-policy/withheld-ledger.js exports no policy with a version: the release was built from a tree whose policy module moved or broke | policy: {value} | reinstall the service from a release built whole (`stado product install weles --surface service --host <this host>`) | config, exit 1, HTTP 503 | tests/worker/launching/serve-failures.mjs::an_api_without_its_evidence_policy_names_it |
serve-client-window-invalid | reading WELES_CLIENT_SUPPORTED_GENERATIONS | it is not an integer, so /api/v1/version could not report the client generations this worker serves | WELES_CLIENT_SUPPORTED_GENERATIONS: {value} | set it to the integer count of client generations the deployment serves, or unset it | config, exit 1, HTTP 503 | tests/worker/launching/serve-failures.mjs::an_api_whose_client_generations_are_no_integer_names_it |
pages-busy | opening a page for {path} | as many pages are open as the host reports processors ({limit}); nothing is queued behind a browser the caller cannot see | {open} of {limit} pages are open | send the request again once an open page has answered | rate_limit, exit 69, HTTP 429, retryable | tests/api/pages-failures.mjs::a_page_beyond_the_open_limit_is_refused |
page-target-not-url | admitting the page {url} | url does not parse as a URL | {answer} | send url as an absolute https://<host>/<path> address | refused, exit 1, HTTP 422 | tests/api/pages-failures.mjs::a_page_target_that_is_no_url_is_refused |
page-target-not-https | admitting the page {url} | only credential-free HTTPS addresses on the default port are served, and this one is not | scheme {scheme}, credentials {credentials}, port {port} | send the public https:// address of the page, with no user, password or port | refused, exit 1, HTTP 422 | tests/api/pages-failures.mjs::a_page_target_over_plain_http_is_refused |
page-target-not-public | admitting the page {url} | its host does not resolve, or resolves to loopback, link-local, private or cloud-metadata space of this worker's network | {answer} | send a page whose host resolves to public addresses only | refused, exit 1, HTTP 422 | tests/api/pages-failures.mjs::a_page_target_on_a_private_address_is_refused |
page-no-response | loading {url} | the navigation ended without an HTTP response: the page answered from its cache or was replaced before it answered | no navigation response for {url} | send the address the page settles on, or a load_state the page reaches | infra_down, exit 69, HTTP 502, retryable | tests/api/pages-failures.mjs::a_page_that_answers_no_response_is_named |
page-http-error | loading {url} | the page answered an HTTP status that is not success | HTTP {status} | send an address the page serves; the status names why it did not | infra_down, exit 69, HTTP 502, retryable | tests/api/pages-failures.mjs::a_page_that_answers_an_http_error_is_named |
page-selector-unmatched | filling {selector} on {url} | no element of the loaded page matches the selector | no element matches {selector} | name a selector the page holds once it reaches the load_state asked for | refused, exit 1, HTTP 502 | tests/api/pages-failures.mjs::an_export_whose_field_the_page_lacks_is_named |
page-button-absent | pressing the export button on {url} | no button of the loaded page has exactly that accessible name | no button reads "{click_text}" | send click_text as the exact accessible name of the button that exports | refused, exit 1, HTTP 502 | tests/api/pages-failures.mjs::an_export_whose_button_the_page_lacks_is_named |
page-download-failed | taking the file {url} downloads | the page started a download and the browser reported it failed | {answer} | send the request again; a download that fails each time is the page's own failure | infra_down, exit 69, HTTP 502, retryable | tests/api/pages-failures.mjs::an_export_whose_download_fails_is_named |
page-browser-failed | rendering {url} for {path} | the browser itself failed while it opened or read the page: it could not start, crashed, or timed out reaching the load state | {answer} | read the worker's log line event:page_route for the browser's words; `weles doctor` checks the browser runtime | infra_down, exit 69, HTTP 502, retryable | tests/api/pages-failures.mjs::a_page_the_browser_cannot_render_is_named |
records-account-not-found | reading the Weles account record {account} | the vault holds no live item tagged weles:record:account with that id: it was never written, or it was trashed | no Weles account record {account} | list the account records (POST /records/accounts/list) and name one of their ids | not_found, exit 1, HTTP 404 | tests/api/records-failures.mjs::a_record_of_an_account_the_vault_lacks_is_not_found |
http-status-unknown | reading the HTTP status "{phrase}" the routes answer with | Node's HTTP status table has no status "{phrase}": the worker runs on a Node {node} whose table names it otherwise, so the server does not start | node {node}: {count} statuses, none "{phrase}" | run the worker on the Node the release names (`weles worker version` and package.json engines), then `weles worker restart` | unknown, exit 1, HTTP 500 | tests/api/routes-failures.mjs::a_node_without_a_status_phrase_is_named |
captcha-run-unnamed | naming the captcha answers directory of run {run} | run id {run} cannot name a captcha answers directory: it is empty, holds a slash or begins with a dot, and every caller passes a run id it admitted, so this is a defect of the release | run id {run} | report it with `weles worker version` and the run that named it; no answer was written | unknown, exit 1, HTTP 500 | tests/api/routes-failures.mjs::a_run_id_that_names_no_directory_is_named |
settings#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
setting-unset | reading {name} | {name} is not set, or holds only blanks, and nothing stands in for it: it is stated, never assumed | {name}: {shown} | set {name} in the environment this process starts with (the release launcher states the settings of the processes it starts), then start it again; `weles doctor` reads the running API against its release | config, exit 1, HTTP 503 | tests/settings/failures.mjs::a_setting_that_is_not_set_is_named |
secret-short | reading the secret {name} | {name} holds {bytes} bytes, fewer than the {minimum} a secret needs: Weles signs and compares with HMAC-SHA256, and RFC 2104 section 3 discourages a key shorter than its digest | {name}: {bytes} bytes | set {name} to a secret of at least {minimum} random bytes (`openssl rand -hex 32` prints one), store it where the launcher reads it, then start again | config, exit 1, HTTP 503 | tests/settings/failures.mjs::a_secret_shorter_than_a_digest_is_refused |
port-invalid | reading the TCP port {name} | {name} is not a TCP port: a port is a whole number from 1 to 65535 (RFC 9293 section 3.1; 0 names no port to listen on) | {name}: {value} | set {name} to a whole number from 1 to 65535 that no other service on this host listens on | config, exit 1, HTTP 503 | tests/settings/failures.mjs::a_port_outside_a_port_field_is_refused |
count-invalid | reading {name}, a number of {unit} | {name} is not a whole number of {unit} above zero | {name}: {value} | set {name} to a whole number of {unit} above zero | config, exit 1, HTTP 503 | tests/settings/failures.mjs::a_count_that_is_no_whole_number_is_refused |
origin-unset | reading {name}, the origin of the service it names | {name} is not set, or holds only blanks, so this process has no address to reach that service at | {name}: {shown} | set {name} to the service origin (https://host[:port], or http://localhost:port on this machine), as the route Stado gives for it names, then start again | config, exit 1, HTTP 503 | tests/settings/failures.mjs::an_origin_that_is_not_set_is_named |
origin-not-url | reading {name}, the origin of the service it names | {name} is not a URL, so it names no origin | {name}: {value} | set {name} to an origin such as https://host or http://localhost:8080, scheme included | config, exit 1, HTTP 503 | tests/settings/failures.mjs::an_origin_that_is_no_url_is_named |
origin-credentials | reading {name}, the origin of the service it names | {name} carries a user name or password in the URL; the bearer is a setting of its own and never travels in an address | {name}: {value} | set {name} to {origin} and keep the credential in the bearer setting the service is reached with | config, exit 1, HTTP 503 | tests/settings/failures.mjs::an_origin_carrying_credentials_is_refused |
origin-path | reading {name}, the origin of the service it names | {name} has the path {path}; it names an origin only, and each request adds its own path | {name}: {value} | set {name} to {origin} | config, exit 1, HTTP 503 | tests/settings/failures.mjs::an_origin_with_a_path_is_refused |
origin-query | reading {name}, the origin of the service it names | {name} has the query {query}; it names an origin only, and each request adds its own | {name}: {value} | set {name} to {origin} | config, exit 1, HTTP 503 | tests/settings/failures.mjs::an_origin_with_a_query_is_refused |
origin-fragment | reading {name}, the origin of the service it names | {name} has the fragment {fragment}, which no request carries; it names an origin only | {name}: {value} | set {name} to {origin} | config, exit 1, HTTP 503 | tests/settings/failures.mjs::an_origin_with_a_fragment_is_refused |
origin-insecure | reading {name}, the origin of the service it names | {name} uses {protocol} to {host}, which is not this machine: a service elsewhere is reached over HTTPS, and plain HTTP only on the loopback (localhost or an address of an internal interface) | {name}: {value} | set {name} to the service's https:// origin, or reach it through a loopback forward (`stado service directory connect SERVICE --consumer weles`) | config, exit 1, HTTP 503 | tests/settings/failures.mjs::a_remote_plain_http_origin_is_refused |
errors#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
action-invalid | reading the weles errors command line | errors takes: list [--json], show <operation-or-area> [--json], or check [--root <checkout>] [--json] | given: {given} | run weles errors with one of list [--json], show <operation-or-area> [--json], or check [--root <checkout>] [--json] | refused, exit 2, HTTP 403 | tests/errors/failures.mjs::errors_refuses_a_form_it_does_not_take |
options-unknown | reading the weles errors {action} command line | errors {action} takes only {taken} | got {given} | leave out {given}: weles errors {action} {taken} | refused, exit 2, HTTP 403 | tests/errors/failures.mjs::errors_refuses_an_option_its_action_does_not_take |
operation-unknown | finding the catalogue of {id} | no operation or area {id} declares a failure catalogue: it is neither an operation id nor the part of one before its dot | the operations that do: {operations} | name one of those operations, or its area (`weles errors show executor` for executor.request and executor.run) | refused, exit 2, HTTP 403 | tests/errors/failures.mjs::errors_show_of_an_operation_nobody_declares_names_those_that_do |
root-missing | reading errors check --root | errors check --root needs the checkout directory, and none was given | --root given without a value | give --root <checkout>, the Weles checkout to check, or leave --root out to check the current directory | refused, exit 2, HTTP 403 | tests/errors/failures.mjs::errors_check_with_an_empty_root_is_refused |
root-not-a-checkout | reading the checkout errors check holds to the standard | errors check reads a Weles checkout, and {root} holds no {directory} directory | {root} without {directory} | run weles errors check in the checkout, or name the checkout with --root <checkout> | refused, exit 2, HTTP 403 | tests/errors/failures.mjs::errors_check_of_a_directory_that_is_no_checkout_names_it |
standard-not-met | holding {root} to the error standard | the error standard is not met: a declaration is incomplete, a cause names a test that neither exists nor is listed in a part of tests/untested-causes/, the list of untested causes is itself broken, or the source says a failure happened without saying which | {count} problem(s) in {root}: {problems} | correct each problem where it names, write the missing test or list it with why in the last part of tests/untested-causes/ (part-<two letters>.json, a new part once it would pass 300 lines), then run weles errors check --root {root} again | refused, exit 1, HTTP 403 | tests/errors/failures.mjs::errors_check_of_a_checkout_that_breaks_the_standard_lists_each_problem |
cli#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
command-unknown | reading the weles command line | unknown command: {word} is not a command weles takes | the commands weles takes: {commands} | run `weles --help` for every command and its usage lines | refused, exit 2, HTTP 403 | tests/cli/failures.mjs::weles_refuses_a_command_it_does_not_take |
verb-missing | reading the weles {command} command line | weles {command} needs an action: {verbs} | no action given | run `weles {command} <action>`; `weles {command} --help` prints the usage lines | refused, exit 2, HTTP 403 | tests/cli/failures.mjs::onboarding_without_an_action_names_the_actions |
verb-unknown | reading the weles {command} command line | weles {command} takes {verbs}, not {verb} | action: {verb} | run `weles {command}` with one of {verbs} | refused, exit 2, HTTP 403 | tests/cli/failures.mjs::release_refuses_an_action_it_does_not_take |
arguments-invalid | reading the arguments of weles {command} | weles {command} takes {takes} | given: {given} | run `weles {command} {takes}` | refused, exit 2, HTTP 403 | tests/cli/failures.mjs::import_without_its_file_names_what_it_takes |
option-required | reading --{option} of weles {command} | --{option} is required and was given no value | --{option}: {value} | give --{option} <{meaning}> | refused, exit 2, HTTP 403 | tests/cli/failures.mjs::release_enforce_without_its_decision_names_it |
provider-unsupported | choosing the provider weles {command} goes through | --provider is missing or names a provider Weles has no {command} for | --provider: {provider}; providers Weles takes: {providers} | give --provider {providers} | refused, exit 2, HTTP 403 | tests/cli/failures.mjs::design_through_a_provider_weles_has_none_for_is_refused |
manifest-unreadable | reading the Weles package manifest {path} | the install carries no package.json beside its dist, or one that is not JSON: the install is incomplete or was changed after it was installed | {answer} | install the Weles release again through Stado (`stado product install weles`), which places dist and package.json together | config, exit 1, HTTP 503 | tests/cli/failures.mjs::version_from_an_install_without_its_manifest_names_the_path |
version-missing | reading the version the Weles package manifest {path} names | the manifest names no version: it is not the manifest a Weles release ships | version: {version} | install the Weles release again through Stado (`stado product install weles`), whose manifest names its version | config, exit 1, HTTP 503 | tests/cli/failures.mjs::version_from_a_manifest_without_a_version_names_it |
node-unsupported | loading the Weles error catalogue | Node {found} cannot require() the ECMAScript module @wisent/errors; Weles needs Node {needed} (package.json engines.node) | node {found} at {path}: {answer} | run Weles with a Node of {needed} first on PATH; `node --version` shows the one PATH finds | config, exit 1, HTTP 503 | tests/cli/failures.mjs::weles_under_a_node_without_require_esm_names_both_versions |
recordings#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
root-unset | reading WELES_RECORDINGS_ROOT | WELES_RECORDINGS_ROOT is not set: the directory that holds run recordings is declared, never assumed | WELES_RECORDINGS_ROOT: {value} | set WELES_RECORDINGS_ROOT to the directory runs record into (the managed worker's own setting), then run again | config, exit 1, HTTP 503 | tests/recordings/failures.mjs::scrub_without_a_recordings_root_names_the_variable |
root-unreadable | listing the recordings under {root} | WELES_RECORDINGS_ROOT names a directory that does not exist, is not a directory, or cannot be read | {answer} | set WELES_RECORDINGS_ROOT to the directory runs record into, or give this user read access to it, then run again | not_found, exit 1, HTTP 404 | tests/recordings/failures.mjs::scrub_of_a_recordings_root_that_does_not_exist_names_it |
write-denied | {step} {path} for {what} of run {run} | the system refused this user access to {nearest} ({errno}), so nothing can be created under it | {nearest} is owned by uid {owner} with mode {mode}; this process runs as {user}; {left} | give {user} write access to {nearest} (`ls -ld {nearest}` shows its owner and mode), or set WELES_RECORDINGS_ROOT to a directory it can write, then run the operation again | config, exit 1, HTTP 503 | tests/recordings/write-failures.mjs::a_recording_in_a_directory_this_user_cannot_write_is_refused |
write-disk-full | {step} {path} for {what} of run {run} | the file system holding {nearest} has no space or quota left for this user ({errno}) | {free} bytes free for this user on the file system of {nearest}; {left} | free space on the disk holding {nearest} (`df -h {nearest}` shows it), then run the operation again | infra_down, exit 69, HTTP 503, retryable | tests/recordings/write-failures.mjs::a_recording_on_a_full_disk_is_refused |
write-read-only | {step} {path} for {what} of run {run} | the file system holding {nearest} is mounted read-only (EROFS), so no file can be written there | {nearest} exists on a read-only file system; {left} | set WELES_RECORDINGS_ROOT to a directory on a writable file system (`mount` shows how {nearest} is mounted), then run the operation again | config, exit 1, HTTP 503 | tests/recordings/write-failures.mjs::a_recording_on_a_read_only_file_system_is_refused |
write-path-blocked | {step} {path} for {what} of run {run} | a part of the path that must be a directory is a file ({errno}): {blocker} | {blocker} exists and is not a directory; {left} | move {blocker} aside or set WELES_RECORDINGS_ROOT to a directory, then run the operation again | config, exit 1, HTTP 503 | tests/recordings/write-failures.mjs::a_recording_under_a_file_is_refused |
write-target-directory | {step} {path} for {what} of run {run} | a directory already stands where the file is written (EISDIR), and a file does not replace a directory | {path} is a directory holding {entries} entries; {left} | move the directory {path} aside (nothing in Weles makes a directory of that name), then run the operation again | config, exit 1, HTTP 503 | tests/recordings/write-failures.mjs::a_recording_where_a_directory_stands_is_refused |
write-name-too-long | {step} {path} for {what} of run {run} | the path is longer than the file system takes (ENAMETOOLONG): its longest part {longest} characters, the whole {length} characters | the longest part starts {start}; {left} | shorten WELES_RECORDINGS_ROOT, ACTION or WELES_LABEL (the parts the path is built from), then run the operation again | config, exit 1, HTTP 503 | tests/recordings/write-failures.mjs::a_recording_whose_path_is_too_long_is_refused |
write-device-failed | {step} {path} for {what} of run {run} | the disk or file system under {nearest} failed the operation ({errno}), a fault of the device and not of the path | the system said: {problem}; {left} | check the disk holding {nearest} (`diskutil verifyVolume /` on macOS, `dmesg` on Linux), then run the operation again | infra_down, exit 69, HTTP 503, retryable | tests/recordings/write-failures.mjs::a_recording_the_device_fails_is_refused |
write-reason-unnamed | {step} {path} for {what} of run {run} | the system refused the operation for a reason ({errno}) this release names no cause for | the system said: {problem}; {nearest} is owned by uid {owner} with mode {mode}; {left} | look the reason {errno} up in `man 2 open` and `man 2 rename` for what the system wants of {nearest}, then run the operation again | unknown, exit 1, HTTP 500 | tests/recordings/write-failures.mjs::a_recording_refused_for_an_unnamed_reason_is_named |
action-invalid | reading the weles recordings command line | weles recordings takes scrub, and {given} is not it | given: {given} | run weles recordings scrub [--json] | refused, exit 2, HTTP 403 | tests/recordings/failures.mjs::recordings_refuses_an_action_it_does_not_take |
options-unknown | reading the weles recordings scrub command line | recordings scrub takes no word after it and only --json | not taken: {given} | leave out {given}: weles recordings scrub [--json] | refused, exit 2, HTTP 403 | tests/recordings/failures.mjs::recordings_scrub_refuses_what_it_does_not_take |
scrub-incomplete | scrubbing the recording envelopes under {root} | some session_meta.json files are not JSON, hold an env_all that is not an environment object, or could not be written back; every other envelope was scrubbed | {count} envelope(s): {failures} | correct or move aside each file named (its JSON or its permissions), then run weles recordings scrub again; envelopes already scrubbed stay as they are | config, exit 1, HTTP 503 | tests/recordings/failures.mjs::scrub_over_an_envelope_that_is_not_one_names_it |
pointer#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
trace-missing | reading the pointer trace | no pointer trace at {path}: this Weles was released without one | {path} does not exist | harvest it from operator-driven runs with weles pointer harvest <run>.inst.json in the Weles checkout, commit it and release; WELES_POINTER_TRACE names another file for a test journey | config, exit 1, HTTP 503 | tests/pointer/failures.mjs::show_without_a_trace_names_its_path |
trace-invalid | reading the pointer trace {path} | {path} is not a pointer trace: it is not JSON, or not the shape weles pointer harvest writes (sources, segments of points, wheel steps) | {answer} | harvest it from operator-driven runs with weles pointer harvest <run>.inst.json in the Weles checkout, commit it and release | config, exit 1, HTTP 503 | tests/pointer/failures.mjs::show_of_a_trace_that_is_not_one_names_it |
action-invalid | reading the weles pointer command line | weles pointer takes harvest or show, and {given} is not one | given: {given} | run weles pointer harvest <run>.inst.json... [--json], or weles pointer show [--json] | refused, exit 2, HTTP 403 | tests/pointer/failures.mjs::pointer_refuses_an_action_it_does_not_take |
options-unknown | reading the weles pointer {action} command line | pointer {action} takes {takes} | not taken: {given} | leave out {given}: weles pointer {action} {takes} | refused, exit 2, HTTP 403 | tests/pointer/failures.mjs::pointer_refuses_what_its_action_does_not_take |
dumps-missing | reading the dumps weles pointer harvest reads | pointer harvest needs at least one <run>.inst.json, and none was named | no dump named | name the instrumentation dumps of runs a person drove: weles pointer harvest <run>.inst.json... | refused, exit 2, HTTP 403 | tests/pointer/failures.mjs::harvest_without_a_dump_is_refused |
dump-unreadable | reading the instrumentation dump {path} | the file does not exist, cannot be read, or is not JSON, or an input it logged holds values that are not JSON | {answer} | name the <run>.inst.json an operator-driven run's instrumentation wrote, whole | not_found, exit 1, HTTP 404 | tests/pointer/failures.mjs::harvest_of_a_dump_that_is_not_one_names_it |
dump-no-input | reading the page input {path} logged | {path} holds no access log: the run recorded no page input, since its input recorder never ran | accesses is {accesses} | name the dump of a run recorded with instrumentation on, whose access log holds the page input | refused, exit 1, HTTP 403 | tests/pointer/failures.mjs::harvest_of_a_dump_without_an_access_log_names_it |
no-travel | harvesting pointer travel from {sources} | they hold no pointer travel between two presses: no run among them had a person move the pointer from one press to another | {sources}: 0 segments | name dumps of runs a person drove, pressing at least twice | refused, exit 1, HTTP 403 | tests/pointer/failures.mjs::harvest_of_dumps_without_travel_names_them |
no-wheel | harvesting wheel steps from {sources} | they hold no vertical wheel step: nobody scrolled in those runs | {sources}: {segments} segments, 0 wheel steps | add the dump of a run in which the person scrolled | refused, exit 1, HTTP 403 | tests/pointer/failures.mjs::harvest_of_dumps_without_scrolling_names_them |
trace-unwritable | writing the harvested pointer trace to {path} | the file cannot be written: its directory does not exist or this user cannot write there, as in an installed release | {answer} | run weles pointer harvest in the Weles checkout, whose src/human/pointer is writable, or name a writable file with WELES_POINTER_TRACE | config, exit 1, HTTP 503 | tests/pointer/failures.mjs::harvest_into_a_place_that_cannot_be_written_names_it |
parts#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
part-unreadable | reading {what} from {path} | the directory or one of its parts cannot be read by this user: it is not readable, or it vanished while being read | {answer} | make {path} readable by this user (`ls -l {path}` shows its owner and mode), or rewrite the part from the source the document is made from, or restore the directory from the release | config, exit 1, HTTP 503 | tests/parts/failures.mjs::a_part_that_cannot_be_read_is_named |
parts-empty | reading {what} from the directory {path} | the directory holds no part: none of its files ends in .json | {path} holds {entries} | put the document's parts (NNN.json) in {path}, or rewrite the part from the source the document is made from, or restore the directory from the release | config, exit 1, HTTP 503 | tests/parts/failures.mjs::a_directory_without_parts_is_named |
part-not-json | reading {what} from {part} | the part is not JSON: it was cut short, edited by hand into invalid syntax, or is another kind of file | {part} ({bytes} bytes): {answer} | correct the syntax at the place the observed message names, or rewrite the part from the source the document is made from, or restore the directory from the release | config, exit 1, HTTP 503 | tests/parts/failures.mjs::a_part_that_is_not_json_is_named |
part-not-object | reading {what} from {part} | the part is JSON but not an object, so it names no member of the document | {part} holds {found}, where a JSON object is expected | make {part} an object whose members are the document's ({part} holds one slice of them), or rewrite the part from the source the document is made from, or restore the directory from the release | config, exit 1, HTTP 503 | tests/parts/failures.mjs::a_part_that_is_not_an_object_is_named |
member-repeated | merging the parts of {what} in {path} | two parts give the same value: {member} is stated by both, where only an array continues across parts and an object gains keys no other part gives | {member} in {first} and again in {second} | keep {member} in one of {first} and {second} | config, exit 1, HTTP 503 | tests/parts/failures.mjs::a_member_two_parts_state_is_named |
member-mixed | merging the parts of {what} in {path} | member {member} is a different kind of value in two parts, so they cannot be merged | {member} is {firstKind} in {first} and {secondKind} in {second} | make {member} the same kind of value (an array, or an object) in every part that holds it | config, exit 1, HTTP 503 | tests/parts/failures.mjs::a_member_of_two_kinds_is_named |
keeper#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
session-invalid | finding keeper session {session} | a keeper session names one directory under ~/.weles/keeper, and this one is empty, . or .., or holds a path separator | session: {session} | name the session with one word of letters, digits, - or _, the --session the keeper was started with | refused, exit 2, HTTP 403 | tests/keeper/socket-failures.mjs::a_session_that_is_a_path_is_refused |
keeper-not-running | asking keeper {session} {action} | no keeper serves that session: it was never started under this HOME, or it stopped and removed its socket | no socket at {socket}: {answer} | start it with `weles keeper start --session {session}`; `weles keeper status --session {session}` says whether it runs | not_found, exit 1, HTTP 404 | tests/keeper/socket-failures.mjs::a_command_to_a_keeper_that_never_started_is_refused |
socket-stale | asking keeper {session} {action} | a socket stands at that path but nothing listens on it: the keeper that made it ended without removing it (it was killed, or its host restarted) | {socket} refuses connections: {answer} | `weles keeper stop --session {session}` removes the dead socket; `weles keeper start --session {session}` replaces it with a running keeper | not_found, exit 1, HTTP 404 | tests/keeper/socket-failures.mjs::a_socket_nothing_listens_on_is_named_stale |
socket-path-too-long | reaching keeper {session} at {socket} | the socket path is longer than the operating system takes for a unix socket (sun_path in sys/un.h), so no keeper can listen or be reached there | the path is {bytes} bytes; the system said: {answer} | use a shorter --session, or run weles under a HOME whose path is shorter, so ~/.weles/keeper/<session>/socket fits | config, exit 1, HTTP 503 | tests/keeper/socket-failures.mjs::a_socket_path_longer_than_the_system_takes_is_named |
socket-forbidden | reaching keeper {session} at {socket} | this user may not connect to the socket: the keeper runs as another user, or the directory denies access | the system said: {answer} | run the command as the user that started the keeper, or give that user access to {socket} | config, exit 1, HTTP 503 | tests/keeper/socket-failures.mjs::a_socket_this_user_may_not_open_is_named |
socket-connect-failed | reaching keeper {session} at {socket} | reading, binding or connecting to the socket broke for a reason the system names below, other than a missing, dead, forbidden or too long socket | the system said: {answer} | read the system error; once `weles keeper status --session {session}` answers, ask again | infra_down, exit 69, HTTP 503, retryable | tests/keeper/socket-failures.mjs::a_socket_connection_the_system_breaks_is_named |
keeper-closed-unanswered | asking keeper {session} {action} | the keeper accepted the connection and closed it without answering: it was stopping, or its process ended while the command ran | the connection to {socket} closed with no answer line | read `weles keeper status --session {session}`; start the keeper again if it ended | infra_down, exit 69, HTTP 503, retryable | tests/keeper/socket-failures.mjs::a_keeper_that_closes_without_answering_is_named |
keeper-answer-unreadable | reading the answer of keeper {session} to {action} | the line it answered is not JSON: what listens on the socket is not a Weles keeper, or a keeper of another release | the answer line: {line}; the parser said: {answer} | stop what listens on {socket} and start the keeper with `weles keeper start --session {session}` from this release | config, exit 1, HTTP 503 | tests/keeper/socket-failures.mjs::a_keeper_answer_that_is_not_json_is_named |
keeper-refused | asking keeper {session} {action} | the keeper refused and named no cause this weles declares: it is a keeper of another release, or it ended with a failure nobody has declared yet | the keeper answered: {answer} | read `weles keeper status --session {session}`; start the keeper again with this release, `weles keeper start --session {session}` | refused, exit 1, HTTP 403 | tests/keeper/socket-failures.mjs::a_keeper_refusal_without_a_declared_cause_is_quoted |
verb-unknown | reading the weles keeper command line | weles keeper takes start, status, stop, not {verb} | verb: {verb} | run `weles keeper start|status|stop --session <id>`; `weles keeper --help` prints the usage lines | refused, exit 2, HTTP 403 | tests/keeper/control/control-failures.mjs::keeper_refuses_a_verb_it_does_not_take |
session-missing | reading the weles keeper command line | keeper {verb} needs --session <id>, the session it starts or asks | --session: {session} | give --session <id>, the same id each command of one keeper uses | refused, exit 2, HTTP 403 | tests/keeper/control/control-failures.mjs::keeper_without_a_session_is_refused |
option-unknown | reading the options of weles keeper {verb} | keeper {verb} takes only {takes}; keeper status and stop do not start a browser, so --url and --headless belong to start | options given: {given} | run `weles keeper {verb}` with only {takes} | refused, exit 2, HTTP 403 | tests/keeper/control/control-failures.mjs::keeper_status_with_a_start_option_is_refused |
directory-foreign | reading the directory of keeper {session} | the session directory is not a directory a keeper made: it is a symbolic link or a file, so the socket in it may belong to anything | {directory} is {kind} | remove {directory} (it holds no keeper), then start the keeper with `weles keeper start --session {session}` | config, exit 1, HTTP 503 | tests/keeper/control/control-failures.mjs::a_session_directory_that_is_a_link_is_refused |
socket-foreign | reading the socket of keeper {session} | what stands at the socket path is not a socket, so no keeper made it and nothing can answer there | {socket} is {kind} | remove {socket}, then start the keeper with `weles keeper start --session {session}` | config, exit 1, HTTP 503 | tests/keeper/control/control-failures.mjs::a_file_at_the_socket_path_is_refused |
directory-unwritable | making the directory of keeper {session} | the keeper cannot create {directory}: a file stands where a directory of its path should be, or this user may not write there | the system said: {answer} | make ~/.weles/keeper a directory this user can write (remove a file standing in its path), then start the keeper again | config, exit 1, HTTP 503 | tests/keeper/control/control-failures.mjs::a_keeper_directory_that_cannot_be_made_is_named |
socket-occupied | starting keeper {session} | another process listens on the socket of that session: another keeper serves it, and one session has one keeper | {socket} accepts connections | use the running keeper, or stop it first with `weles keeper stop --session {session}`, or start this one under another --session | refused, exit 1, HTTP 403 | tests/keeper/control/control-failures.mjs::starting_a_session_another_keeper_serves_is_refused |
start-url-failed | opening --url {url} in keeper {session} | the browser started but could not load the first page: the address is not a URL, its host has no DNS answer, nothing listens there, or the connection broke | the engine said: {answer} | check the address answers (curl -v {url}), then start the keeper again; the browser it started was closed | refused, exit 1, HTTP 403 | tests/keeper/control/control-failures.mjs::a_keeper_whose_first_page_cannot_load_closes_its_browser |
page-crashed | serving keeper {session} | the page of the keeper crashed (its renderer process ended), so the keeper closed its browser and ended | the page reported a crash at {url} | start the keeper again with `weles keeper start --session {session}`; the recording of the run names the last action before the crash | infra_down, exit 69, HTTP 503, retryable | tests/keeper/control/control-failures.mjs::a_keeper_whose_page_crashes_ends_naming_the_crash |
answer-foreign | asking keeper {session} {action} | the keeper listening on that socket serves another session: it was started for another id and its socket was moved or linked here | the keeper at {socket} answered for session {answered} | stop the keeper of session {answered} (`weles keeper stop --session {answered}`) and start {session} with its own socket | config, exit 1, HTTP 503 | tests/keeper/control/control-failures.mjs::a_keeper_answering_for_another_session_is_refused |
route-unknown | answering {method} {path} | the keeper routes are GET /keepers, GET /keepers/<session> and POST /keepers/<session>/stop | {method} {path} | send one of those three requests | refused, exit 2, HTTP 405 | tests/keeper/route-failures.mjs::a_keeper_route_the_worker_does_not_serve_is_refused |
action-session-missing | choosing the keeper keeper/action asks | keeper action needs SESSION, the --session of the running keeper, in its environment | SESSION: {session} | run keeper/action with SESSION=<id> and the command on stdin | refused, exit 2, HTTP 403 | tests/keeper/control/command-failures.mjs::keeper_action_without_a_session_is_refused |
command-not-object | reading a keeper command | a keeper command must be a JSON object such as {"action":"url"}, and this one is not JSON or not an object | {answer} | send one JSON object per line with its "action" and the fields that action needs | refused, exit 2, HTTP 403 | tests/keeper/control/command-failures.mjs::keeper_action_with_a_command_that_is_not_an_object_is_refused |
action-unknown | running a command in keeper {session} | keeper {session} has no action {action} | the actions it takes: {actions} | send one of those actions | refused, exit 2, HTTP 403 | tests/keeper/control/command-failures.mjs::a_keeper_command_naming_no_action_it_takes_is_refused |
argument-missing | running {action} in keeper {session} | {action} needs a non-empty "{key}" | {key}: {value} | send "{key}" as a non-empty string with the {action} command | refused, exit 2, HTTP 403 | tests/keeper/control/command-failures.mjs::a_keeper_command_without_its_text_is_refused |
coordinate-missing | running {action} in keeper {session} | {action} needs a numeric "{key}", a finite number of CSS pixels | {key}: {value} | send "{key}" as a number with the {action} command | refused, exit 2, HTTP 403 | tests/keeper/control/command-failures.mjs::a_keeper_command_without_its_coordinate_is_refused |
fill-text-missing | running fill in keeper {session} | fill needs a string "text"; an empty string clears the field | text: {value} | send "text" as a string, "" to clear the field | refused, exit 2, HTTP 403 | tests/keeper/control/command-failures.mjs::a_fill_without_its_text_is_refused |
described-control-not-found | clicking the control described as {description} in keeper {session} | the vision model found no control matching "{description}" on the settled page | the page {url} shows no such control | read the page (the screenshot action saves it) and describe the control as it is labelled there | not_found, exit 1, HTTP 404 | tests/keeper/control/command-failures.mjs::a_described_control_the_page_does_not_show_is_named |
keeper-stopping | running {action} in keeper {session} | keeper {session} is stopping; action not started, because a stop closes its browser | state: stopping | start the keeper again with `weles keeper start --session {session}` and send the command to it | refused, exit 1, HTTP 403 | tests/keeper/control/command-failures.mjs::a_command_to_a_stopping_keeper_is_refused |
action-refused | running {action} in keeper {session} on {url} | the page or the browser refused the action, in the engine words below: the element is not there, the selector does not parse, the expression threw, or the page is gone | the engine said: {answer} | read the engine words and send the command again with the selector, key or expression they name corrected | refused, exit 1, HTTP 403 | tests/keeper/control/command-failures.mjs::a_keeper_action_the_page_refuses_quotes_the_engine |
onboarding#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
file-unreadable | reading the {what} {path} | the file could not be read ({errno}): it does not exist, is a directory, or this user may not read it | {answer} | give --{option} a readable JSON file: the receipt a real workflow wrote, or the map of trusted receipt keys | not_found, exit 1, HTTP 404 | tests/onboarding/failures.mjs::verify_with_a_receipt_that_does_not_exist_names_it |
file-not-json | reading the {what} {path} | the file holds {bytes} characters that are not JSON | the parser said: {answer} | give --{option} the JSON file itself: the receipt.json a workflow wrote, or the key map the worker publishes | refused, exit 1, HTTP 403 | tests/onboarding/failures.mjs::verify_with_a_receipt_that_is_not_json_is_refused |
receipt-keys-not-object | reading the receipt key map {path} | the key map holds {found}, where it is one JSON object of key ids to PEM public keys | nothing was verified | give --keys a file such as {"<key-id>": "-----BEGIN PUBLIC KEY-----..."}, as the worker publishes its receipt keys | refused, exit 1, HTTP 403 | tests/onboarding/failures.mjs::verify_with_a_key_list_is_refused |
receipt-keys-empty | reading the receipt key map {path} | the key map is an empty object, so no receipt can verify against it | nothing was verified | add the worker's published receipt keys to {path} | refused, exit 1, HTTP 403 | tests/onboarding/failures.mjs::verify_with_an_empty_key_map_is_refused |
receipt-key-not-pem | reading the receipt key map {path} | the key map's entry {key} holds {found}, not the text of a PEM public key | the map holds {count} entries; nothing was verified | put the worker's PEM public key text under {key} in {path} | refused, exit 1, HTTP 403 | tests/onboarding/failures.mjs::verify_with_a_key_that_is_no_text_is_refused |
boundary-first | importing existing data into onboarding | complete the authorization-boundary step before importing existing data: an import binds drafts to a host the operator has not yet seen explained | the journey stands at {screen} | run `weles onboarding next` until the journey reaches existing-data, then import | refused, exit 1, HTTP 403 | tests/onboarding/failures.mjs::import_before_the_boundary_step_is_refused |
step-action-unknown | running the onboarding action {action} | the journey takes no action {action} | it takes {actions}; nothing was changed | call runWelesOnboarding with one of {actions} (`weles onboarding --help`) | refused, exit 2, HTTP 403 | tests/onboarding/stages/steps-failures.mjs::an_action_the_journey_does_not_take_is_refused |
step-next-at-receipt | advancing the journey from receipt-verification | the receipt-verification step advances only on a signed receipt of a completed workflow, never on next | the journey stands at receipt-verification; nothing was changed | `weles onboarding verify --receipt <receipt.json> --keys <receipt-keys.json>` | refused, exit 1, HTTP 403 | tests/onboarding/stages/steps-failures.mjs::next_at_the_receipt_step_is_refused |
step-unadvanced | advancing the journey from {screen} by {action} | the definition the attempt runs names no transition from {screen} for this evidence: {evidence} | the definition came from {source}; nothing was changed | `weles onboarding status --json` shows the screen and its actions; `weles onboarding reset` starts again on the current definition | unknown, exit 1, HTTP 500 | tests/onboarding/stages/steps-failures.mjs::a_screen_without_a_transition_for_its_evidence_is_named |
step-import-early | recording an import in the journey | complete the authorization-boundary step before recording an import: the journey stands at {screen}, not existing-data | nothing was recorded | run `weles onboarding next` until the journey reaches existing-data, then `weles onboarding import` | refused, exit 1, HTTP 403 | tests/onboarding/stages/steps-failures.mjs::an_import_recorded_before_the_boundary_is_refused |
step-import-unpersisted | recording an import in the journey | onboarding import requires a persisted Weles import result: the report {problem} | the report: {report}; nothing was recorded | `weles onboarding import <trajectory-export.json> --host <host>` imports and records in one step | refused, exit 1, HTTP 403 | tests/onboarding/stages/steps-failures.mjs::an_import_report_without_a_draft_is_refused |
step-verify-early | verifying a receipt in the journey | complete the authorization-boundary and host-execution steps before verifying a receipt: the journey stands at {screen} | the receipt was not read | run `weles onboarding next` until the journey reaches receipt-verification, then verify | refused, exit 1, HTTP 403 | tests/onboarding/stages/steps-failures.mjs::a_verify_before_host_execution_is_refused |
step-verify-inputs | verifying a receipt in the journey | receipt verification requires a receipt and trusted public-key map: {missing} | nothing was verified | pass both (`weles onboarding verify --receipt <receipt.json> --keys <receipt-keys.json>`) | refused, exit 2, HTTP 403 | tests/onboarding/stages/steps-failures.mjs::a_verify_without_keys_is_refused |
step-receipt-outcome-other | verifying the receipt of task {task} | verified receipt outcome is not a completed Weles workflow: it is {outcome} | the receipt is signed by key {key}; the journey did not move | run an authorized workflow to completion and verify its receipt | refused, exit 1, HTTP 403 | tests/onboarding/stages/steps-failures.mjs::a_receipt_of_a_failed_workflow_is_refused |
step-first-success-unmet | completing the journey with the receipt of task {task} | verified receipt did not satisfy the Weles first-success contract: the journey stands at {screen} and its definition completes it on no such evidence | the definition came from {source}; the journey did not move | `weles onboarding status --json` shows the screen; `weles onboarding reset` starts again on the current definition | unknown, exit 1, HTTP 500 | tests/onboarding/stages/steps-failures.mjs::a_receipt_the_definition_does_not_complete_on_is_named |
step-progress-absent | starting the journey in {directory} | Weles onboarding did not create progress: start() returned without progress, a defect of the release | the definition came from {source} | install a whole Weles release (`stado product install weles --surface cli`); report it to Weles's maintainers if it persists | unknown, exit 1, HTTP 500 | tests/onboarding/stages/steps-failures.mjs::a_start_without_progress_is_named |
step-content-missing | showing the journey screen {screen} | Weles has no product content for journey screen {screen}: the definition the attempt runs names a screen this release does not render | the definition came from {source}; this release renders {screens} | install a whole Weles release (`stado product install weles --surface cli`); report it to Weles's maintainers if it persists | unknown, exit 1, HTTP 500 | tests/onboarding/stages/steps-failures.mjs::a_screen_this_release_does_not_render_is_named |
step-bundled-invalid | loading the first-use journey this release bundles | bundled Weles first-use journey identity is invalid: its {field} is {found}, where this release expects {expected} | no onboarding step can run | install a whole Weles release (`stado product install weles --surface cli`); report it to Weles's maintainers if it persists | unknown, exit 1, HTTP 500 | tests/onboarding/stages/steps-failures.mjs::a_bundled_journey_of_another_version_is_named |
state-file-unread | reading the onboarding file {path} | the file exists and could not be read ({errno}): this user may not read it, or it is a directory | {problem}; nothing was changed | `ls -l {path}` shows its owner and mode; `ls -l {directory}` lists the state directory; move the damaged file aside (the journey starts again from the newest good version) or name another --state-dir | config, exit 1, HTTP 503 | tests/onboarding/storage-failures.mjs::a_state_file_that_cannot_be_read_is_named |
state-file-not-json | reading the onboarding file {path} | the file holds {bytes} characters that are not JSON: it was cut short or written by another program | the parser said: {problem}; nothing was changed | `ls -l {directory}` lists the state directory; move the damaged file aside (the journey starts again from the newest good version) or name another --state-dir | config, exit 1, HTTP 503 | tests/onboarding/storage-failures.mjs::a_state_file_that_is_not_json_is_refused |
journal-shape | reading the onboarding file {path} | the journal is not a progress map and an events list: it holds {found} | version {version} of the journal; nothing was changed | `ls -l {directory}` lists the state directory; move the damaged file aside (the journey starts again from the newest good version) or name another --state-dir | config, exit 1, HTTP 503 | tests/onboarding/storage-failures.mjs::a_journal_of_another_shape_is_refused |
journal-progress-malformed | reading the onboarding file {path} | the journal's progress {key} is not a journey progress record (attempt, status, screens and evidence) | version {version} of the journal holds {count} progress records; nothing was changed | `ls -l {directory}` lists the state directory; move the damaged file aside (the journey starts again from the newest good version) or name another --state-dir | config, exit 1, HTTP 503 | tests/onboarding/storage-failures.mjs::a_journal_with_malformed_progress_is_refused |
journal-event-malformed | reading the onboarding file {path} | queued event {index} of the journal is not a journey event (id, name, attempt and time) | version {version} of the journal queues {count} events; nothing was changed | `ls -l {directory}` lists the state directory; move the damaged file aside (the journey starts again from the newest good version) or name another --state-dir | config, exit 1, HTTP 503 | tests/onboarding/storage-failures.mjs::a_journal_with_a_malformed_event_is_refused |
state-directory-unread | listing the onboarding state directory {directory} | the directory exists and could not be listed ({errno}): it is a file, or this user may not read it | {problem}; nothing was changed | `ls -ld {directory}` shows what it is; name a directory of this user's with --state-dir | config, exit 1, HTTP 503 | tests/onboarding/storage-failures.mjs::a_state_directory_that_cannot_be_listed_is_named |
state-write-failed | {step} in the onboarding state directory {directory} | the system refused it ({errno}): the directory is not writable by this user, or the disk is full | {problem}; {left} | `ls -ld {directory}` shows its owner and mode, `df -h {directory}` its free space; correct it, then run the step again | config, exit 1, HTTP 503 | tests/onboarding/storage-failures.mjs::a_state_directory_that_cannot_be_written_is_named |
plane-offline | asking the onboarding control plane for {operation} | this run has no control plane: {reason} | the journey runs on the stored or bundled definition and its events stay queued | set WELES_STADO_INTEGRATION_TOKEN to Weles's integration bearer, and let Stado name the onboarding-events alternative (`stado capabilities resolve onboarding-events --consumer weles --json`) | config, exit 1, HTTP 503 | tests/onboarding/stages/plane-failures.mjs::an_offline_journey_says_why |
plane-origin-invalid | connecting the journey to the control plane Stado named | the address {address} is not an HTTPS origin: it has {part}, where the control plane is reached at an origin and the journey adds the path | the journey runs on the stored or bundled definition and its events stay queued | `stado capabilities resolve onboarding-events --consumer weles --json` shows the address Stado names; correct capabilities.onboarding-events.url in Stado's configuration | config, exit 1, HTTP 503 | tests/onboarding/stages/plane-failures.mjs::a_control_plane_that_is_not_an_https_origin_is_refused |
plane-token-empty | connecting the journey to the control plane at {origin} | the bearer handed to the transport is empty | the journey runs on the stored or bundled definition and its events stay queued | set WELES_STADO_INTEGRATION_TOKEN to Weles's integration bearer | config, exit 1, HTTP 503 | tests/onboarding/stages/plane-failures.mjs::a_transport_without_a_bearer_is_refused |
plane-token-malformed | reading the onboarding bearer from WELES_STADO_INTEGRATION_TOKEN | the variable holds {problem}, which no bearer holds | it holds {length} characters; the value is not shown | set WELES_STADO_INTEGRATION_TOKEN to the bearer alone, without spaces, newlines or control characters | config, exit 1, HTTP 503 | tests/onboarding/stages/plane-failures.mjs::a_bearer_with_a_newline_is_refused |
plane-product-invalid | posting {operation} for {product} to the onboarding control plane at {origin} | the product id {product} is not an identifier (a letter, then up to 127 letters, digits, dots, underscores or dashes), so no operation path can be made of it | the journey runs on the stored or bundled definition and its events stay queued | call the journey with the product id its bundle declares (`weles` for Weles's own) | config, exit 1, HTTP 503 | tests/onboarding/stages/plane-failures.mjs::a_product_id_that_is_no_identifier_is_refused |
plane-unreachable | posting {operation} for {product} to the onboarding control plane at {origin} | the control plane did not answer ({errno}): its host is down, DNS does not resolve it, or the network is out | {problem}; the journey runs on the stored or bundled definition and its events stay queued | `curl -sSI {origin}` shows whether it answers from this host; the next run sends the queued events | infra_down, exit 69, HTTP 503, retryable | tests/onboarding/stages/plane-failures.mjs::a_control_plane_that_does_not_answer_is_named |
plane-answer-not-json | posting {operation} for {product} to the onboarding control plane at {origin} | the control plane answered HTTP {status} with a body that is not the JSON envelope it answers with: a proxy or another service answered at that address | content-type {type}, {bytes} characters: {head}; the journey runs on the stored or bundled definition and its events stay queued | `stado capabilities resolve onboarding-events --consumer weles --json` shows the address Stado names; correct it in Stado's configuration | infra_down, exit 69, HTTP 503, retryable | tests/onboarding/stages/plane-failures.mjs::a_control_plane_answer_that_is_not_json_is_named |
plane-refused | posting {operation} for {product} to the onboarding control plane at {origin} | the control plane refused the operation with HTTP {status} and error {error} | the envelope said ok: {ok}; the journey runs on the stored or bundled definition and its events stay queued | HTTP 401 or 403 means the bearer in WELES_STADO_INTEGRATION_TOKEN is not Weles's; another error names what the control plane refused (`weles onboarding status --json` shows the journey) | refused, exit 1, HTTP 403 | tests/onboarding/stages/plane-failures.mjs::a_control_plane_that_refuses_the_bearer_is_named |
plane-result-absent | posting {operation} for {product} to the onboarding control plane at {origin} | the control plane answered HTTP {status} and ok without a result, which every operation answers with | the envelope holds {members}; the journey runs on the stored or bundled definition and its events stay queued | report the control plane's answer to Wisent Integrations' maintainers; the journey keeps running offline | unknown, exit 1, HTTP 500 | tests/onboarding/stages/plane-failures.mjs::a_control_plane_answer_without_a_result_is_named |
plane-bundle-foreign | reading the journey {journey} version {version} from the control plane | the control plane answered a bundle that is not the journey this release renders: {reason} | the journey runs on the stored or bundled definition and its events stay queued | publish journey {journey} version {version} for product weles on the control plane, or install the Weles release made for the journey it serves (`stado product install weles --surface cli`) | refused, exit 1, HTTP 403 | tests/onboarding/stages/plane-failures.mjs::a_control_plane_bundle_of_another_version_is_refused |
bundle-envelope-invalid | checking journey bundle {journey} of {product} | its envelope is not a bundle's: its {field} is {found}, where a bundle carries a UUID version id, a SHA-256 content digest and its canonical definition text | nothing of the bundle was believed | install the Weles release whose bundled journey is whole (`stado product install weles --surface cli`); a control plane or stored bundle that fails this is fallen back from | unknown, exit 1, HTTP 500 | tests/onboarding/stages/runtime-failures.mjs::a_bundle_without_its_digest_is_refused |
bundle-identity-other | checking journey bundle {journey} of {product} | its definition declares {field} {found}, where this journey is {expected} | nothing of the bundle was believed | install the Weles release whose bundled journey is whole (`stado product install weles --surface cli`); a control plane or stored bundle that fails this is fallen back from | unknown, exit 1, HTTP 500 | tests/onboarding/stages/runtime-failures.mjs::a_bundle_of_another_product_is_refused |
bundle-experiment-invalid | checking journey bundle {journey} of {product} | its experiment contract is not one: {reason} | the contract holds {members} | install the Weles release whose bundled journey is whole (`stado product install weles --surface cli`); a control plane or stored bundle that fails this is fallen back from | unknown, exit 1, HTTP 500 | tests/onboarding/stages/runtime-failures.mjs::a_bundle_whose_control_variant_is_not_eligible_is_refused |
bundle-canonical-differs | checking journey bundle {journey} of {product} | its canonical definition text is not the canonical form of its definition: one of the two was changed after the bundle was made | the text holds {stated} characters, the definition's canonical form {computed} | install the Weles release whose bundled journey is whole (`stado product install weles --surface cli`); a control plane or stored bundle that fails this is fallen back from | unknown, exit 1, HTTP 500 | tests/onboarding/stages/runtime-failures.mjs::a_bundle_whose_definition_was_changed_is_refused |
bundle-screens-invalid | checking journey bundle {journey} of {product} | its definition holds {found}, where a journey holds at least one screen | nothing of the bundle was believed | install the Weles release whose bundled journey is whole (`stado product install weles --surface cli`); a control plane or stored bundle that fails this is fallen back from | unknown, exit 1, HTTP 500 | tests/onboarding/stages/runtime-failures.mjs::a_bundle_without_screens_is_refused |
bundle-screen-invalid | checking journey bundle {journey} of {product} | screen {index} ({screen}) is not one: {reason} | the definition holds {count} screens | install the Weles release whose bundled journey is whole (`stado product install weles --surface cli`); a control plane or stored bundle that fails this is fallen back from | unknown, exit 1, HTTP 500 | tests/onboarding/stages/runtime-failures.mjs::a_bundle_repeating_a_screen_is_refused |
bundle-screen-missing | checking journey bundle {journey} of {product} | the {role} {screen} names no screen of the definition | its screens are {screens} | install the Weles release whose bundled journey is whole (`stado product install weles --surface cli`); a control plane or stored bundle that fails this is fallen back from | unknown, exit 1, HTTP 500 | tests/onboarding/stages/runtime-failures.mjs::a_bundle_whose_entry_screen_is_missing_is_refused |
bundle-digest-differs | checking journey bundle {journey} of {product} | the SHA-256 of its canonical text is {computed}, not the content digest {stated} it states: the bundle was changed after it was signed off | nothing of the bundle was believed | install the Weles release whose bundled journey is whole (`stado product install weles --surface cli`); a control plane or stored bundle that fails this is fallen back from | unknown, exit 1, HTTP 500 | tests/onboarding/stages/runtime-failures.mjs::a_bundle_whose_digest_differs_is_refused |
journey-screen-unknown | choosing the next screen after {screen} | the progress stands at {screen}, which the definition the attempt runs does not hold: the definition changed under a started attempt | its screens are {screens} | `weles onboarding reset --state-dir <dir>` starts the journey again on the current definition | unknown, exit 1, HTTP 500 | tests/onboarding/stages/runtime-failures.mjs::progress_on_a_screen_the_definition_lacks_is_refused |
client-identity-invalid | making the journey client of {product}/{journey} | its {field} {found} is not {shape} | no attempt was started | make the client with an identifier product and journey id and the SHA-256 hex of the subject | refused, exit 1, HTTP 403 | tests/onboarding/stages/runtime-failures.mjs::a_client_for_a_subject_that_is_no_digest_is_refused |
client-unstarted | {step} of journey {journey} | the client was asked to {step} before start() loaded a bundle and progress: a defect of the caller | no progress was changed and no event queued | report the call to Weles's maintainers; `weles onboarding status` starts the journey | unknown, exit 1, HTTP 500 | tests/onboarding/stages/runtime-failures.mjs::a_step_before_start_is_refused |
imports#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
import-host-invalid | reading --host of the import | it is not a host name (letters, digits, dots and hyphens, at most 253 characters), so no managed worker can be named by it | --host: {host} | name the managed Weles worker exactly as the registry does: --host <managed-worker-hostname> | refused, exit 2, HTTP 403 | tests/onboarding/import-failures.mjs::an_import_for_a_host_that_is_no_host_name_is_refused |
import-file-unreadable | reading the trajectory export {path} | no file can be read there: it does not exist, or this user may not read it | {answer} | name the export file a Weles export wrote, by a path this user can read | not_found, exit 1, HTTP 404 | tests/onboarding/import-failures.mjs::an_import_of_a_file_that_does_not_exist_names_it |
import-file-not-regular | reading the trajectory export {path} | it is a directory or another kind of file, not the one export file an import reads | {path} is not a regular file | name the export file itself, not the directory holding it | refused, exit 1, HTTP 403 | tests/onboarding/import-failures.mjs::an_import_of_a_directory_is_refused |
import-file-not-json | reading the trajectory export {path} | it is not one JSON document: it was cut short, edited by hand, or is not an export | the parser said: {answer} | export the trajectories again and import the file the export wrote | refused, exit 1, HTTP 403 | tests/onboarding/import-failures.mjs::an_import_of_a_file_that_is_not_json_is_refused |
import-export-not-object | reading the trajectory export | a trajectory export is one JSON object, and this is not one | it is a JSON {kind} | import the object a Weles export writes, not a part of it | refused, exit 1, HTTP 403 | tests/onboarding/import-failures.mjs::an_import_of_json_that_is_not_an_object_is_refused |
import-unreachable | importing the trajectory export at {endpoint} | no HTTP answer came back: the host has no DNS answer, nothing listens on that port, or the connection was cut | {answer} | check that WELES_API_BASE names the Weles web API and that it answers there, then run again | infra_down, exit 69, HTTP 503, retryable | tests/onboarding/import-failures.mjs::an_import_to_an_address_nothing_answers_on_names_it |
import-unauthorized | importing the trajectory export at {endpoint} | the Weles web API did not admit WELES_TOKEN for this organization: the token is another organization's or was revoked | HTTP {status}: {answer} | set WELES_TOKEN to a token of the organization WISENT_ORGANIZATION_ID names, then run again | auth, exit 1, HTTP 401 | tests/onboarding/import-failures.mjs::an_import_with_a_token_the_api_refuses_is_named |
import-refused | importing the trajectory export at {endpoint} | the Weles web API understood the request and refused it; its own words below say why (an export too large, a host the organization does not hold) | HTTP {status}: {answer} | correct what the API names and import again | refused, exit 1, HTTP 403 | tests/onboarding/import-failures.mjs::an_import_the_api_refuses_quotes_it |
import-failed | importing the trajectory export at {endpoint} | the Weles web API, or the proxy in front of it, failed while answering | HTTP {status}: {answer} | run the import again; when it fails again, the API names its failure in the words above | infra_down, exit 69, HTTP 503, retryable | tests/onboarding/import-failures.mjs::an_import_the_api_fails_on_quotes_it |
import-answer-invalid | importing the trajectory export at {endpoint} | what answered is not the Weles web API's import report: a proxy or page answered instead, or the report does not count its items, organization and host as the request named them | {answer} | check that WELES_API_BASE names the Weles web API, not a proxy or the worker, then run again | infra_down, exit 69, HTTP 503, retryable | tests/onboarding/import-failures.mjs::an_import_answered_by_something_else_is_named |
skarbiec#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
skarbiec-binary-unusable | finding the {label} | it is not an absolute path to a regular executable file: the variable names another program, or the file Stado names is gone or not executable | {answer} | unset SKARBIEC_BIN so Stado names the program, or install Skarbiec through Stado (`stado release active binary skarbiec` names it) | config, exit 1, HTTP 503 | tests/skarbiec/failures.mjs::a_skarbiec_program_that_is_not_there_is_named |
skarbiec-not-attested | finding the Skarbiec program Stado attests for this host | Stado names neither a signed Skarbiec release active on this host nor the host-declared Skarbiec in ~/.stado/bin | stado release active binary skarbiec answered {answer} | install Skarbiec on this host through Stado, so `stado release active binary skarbiec --json` names it | config, exit 1, HTTP 503 | tests/skarbiec/failures.mjs::a_stado_that_attests_no_skarbiec_is_quoted |
skarbiec-digest-mismatch | checking the Skarbiec program {path} | its bytes are not the ones Stado's host report names: the file was replaced or changed after Stado declared it | digest {found}, Stado names {declared} | reinstall Skarbiec through Stado so the file and its declaration agree again | refused, exit 1, HTTP 403 | tests/skarbiec/failures.mjs::a_skarbiec_program_whose_bytes_changed_is_refused |
skarbiec-units-not-one | reading the Skarbiec unit environment on {host} | Stado declares not exactly one Skarbiec unit on that host, and the capability socket is read from exactly one | {answer} | declare one Skarbiec unit on {host} through Stado (`stado service env show skarbiec --host {host}` shows them) | config, exit 1, HTTP 503 | tests/skarbiec/failures.mjs::a_host_with_no_skarbiec_unit_is_named |
capability-socket-undeclared | reading the Skarbiec unit environment on {host} | the one Skarbiec unit declares no absolute SKARBIEC_CAP_SOCKET for the Skarbiec unit on that host | SKARBIEC_CAP_SOCKET: {socket} | set SKARBIEC_CAP_SOCKET in the Skarbiec unit environment through Stado (`stado service env set skarbiec`) | config, exit 1, HTTP 503 | tests/skarbiec/failures.mjs::a_unit_without_a_capability_socket_is_named |
resolver-action-unknown | reading what skarbiec-runtime.mjs is asked | it answers active-binary, endpoint or capability-socket | asked: {action} | run `node src/_shared/vaulting/skarbiec-runtime.mjs active-binary|endpoint|capability-socket` | refused, exit 2, HTTP 403 | tests/skarbiec/failures.mjs::the_resolver_refuses_a_question_it_does_not_answer |
vault-value-invalid | reading {what} from the vault | the vault holds it in a shape Weles does not read: {shape}; it was written by hand or by another program | {answer} | rewrite it in that shape with the Weles command or route that writes it, or `skarbiec set` it by hand | config, exit 1, HTTP 503 | tests/skarbiec/failures.mjs::a_setting_that_holds_no_json_names_it |
role-invalid | finding the item that plays role {role} | a role is lowercase letters, digits and hyphens, starting with a letter or digit | role: {role} | name the role as `stado credentials item put --role` takes it | config, exit 1, HTTP 503 | tests/runs/submitting/request-failures.mjs::a_role_that_is_not_a_role_is_refused |
role-unheld | finding the item that plays role {role} | no live Skarbiec item carries {tag} | no item of {items} carries it | store the secret for role {role} with `stado credentials item put --host <vault owner> --role {role}` | not_found, exit 1, HTTP 404 | tests/runs/submitting/request-failures.mjs::a_role_no_item_plays_is_named |
role-ambiguous | finding the item that plays role {role} | several live Skarbiec items carry {tag}, and exactly one item may play a role: reading either would be a guess | items carrying it: {holders} | keep the one that should play {role} and retag the others (`stado credentials item retag`) | refused, exit 1, HTTP 403 | tests/runs/submitting/request-failures.mjs::a_role_several_items_play_is_refused |
second-factor-not-held | starting an automated sign-in of login {item} | Skarbiec judges its second factor {state}: its account is recorded to ask for {factor} after the password, and {holder}, the item keeping it, holds nothing an automated sign-in answers that with (its seed is {seed}), so the sign-in would stop there or wait on a person's phone; no browser was started and nothing was sent | skarbiec login factor list {item}: state {state}, factor {factor}, holder {holder}, seed {seed} | {fix}; `skarbiec login factor list {item}` says what the vault holds | config, exit 1, HTTP 503 | tests/skarbiec/factors/second-factor.mjs::a_sign_in_whose_recorded_factor_the_vault_does_not_hold_is_refused_before_the_browser |
second-factor-undeclared | starting an automated sign-in of login {item} | nothing records what its account asks after the password (context.second_factor on {holder}, the item keeping its second factor), so Skarbiec judges it undeclared and Google could answer the password with a prompt on the owner's phone; no browser was started and nothing was sent | skarbiec login factor list {item}: state undeclared, holder {holder}, seed {seed} | {fix}; `skarbiec login factor list {item}` says what the vault holds | config, exit 1, HTTP 503 | tests/skarbiec/factors/second-factor.mjs::a_sign_in_with_no_record_and_no_working_seed_is_refused_before_the_browser |
acquire-usage | reading the acquisition helper's arguments | the helper takes exactly <scope-file> <consumer> <item> <field>, and it was given {count} argument(s) | arguments: {given} | run node src/worker/deploy/acquire/skarbiec-acquire.mjs <scope-file> <consumer> <item> <field> | refused, exit 2, HTTP 403 | tests/skarbiec/acquisition-failures.mjs::an_acquisition_without_its_four_arguments_is_refused |
acquire-coordinate-invalid | asking for {consumer} on {item}#{field} | the {part} {value} is not an exact name (letters, digits, . _ and -){role} | {part}: {value} | name the consumer and field exactly, and the item as an exact name or role:<exact name> | refused, exit 2, HTTP 403 | tests/skarbiec/acquisition-failures.mjs::an_acquisition_of_a_coordinate_that_is_no_name_is_refused |
acquire-scope-catalog-absent | checking that the scope catalog {scope_file} declares {consumer} on {item}#{field} | no file is at {scope_file} | {answer} | set SKARBIEC_WELES_ACQUISITION_SCOPES_FILE to the catalog the release ships, or unset it so the release's own is read | config, exit 1, HTTP 503 | tests/skarbiec/acquisition-failures.mjs::an_acquisition_whose_scope_catalog_is_missing_is_refused |
acquire-scope-catalog-unreadable | checking that the scope catalog {scope_file} declares {consumer} on {item}#{field} | the catalog cannot be read ({error_code}): it is a directory, or this user may not read it | {answer} | make {scope_file} a file this user can read (`ls -l {scope_file}` shows its owner and mode) | config, exit 1, HTTP 503 | tests/skarbiec/acquisition-failures.mjs::an_acquisition_whose_scope_catalog_is_a_directory_is_refused |
acquire-scope-row-invalid | checking that the scope catalog {scope_file} declares {consumer} on {item}#{field} | line {line} is not one consumer|item|field of exact names | line {line}: {row} | correct the catalog so each line is one consumer|item|field (item an exact name or role:<exact name>), once; the release ships src/worker/deploy/acquire/skarbiec-acquisition-scopes.conf | config, exit 1, HTTP 503 | tests/skarbiec/acquisition-failures.mjs::an_acquisition_whose_scope_catalog_holds_a_malformed_row_is_refused |
acquire-scope-catalog-empty | checking that the scope catalog {scope_file} declares {consumer} on {item}#{field} | the catalog declares no scope at all ({lines} line(s), every one blank or a comment) | {lines} line(s), no scope | correct the catalog so each line is one consumer|item|field (item an exact name or role:<exact name>), once; the release ships src/worker/deploy/acquire/skarbiec-acquisition-scopes.conf | config, exit 1, HTTP 503 | tests/skarbiec/acquisition-failures.mjs::an_acquisition_whose_scope_catalog_is_empty_is_refused |
acquire-scope-repeated | checking that the scope catalog {scope_file} declares {consumer} on {item}#{field} | line {line} repeats the scope line {first} declares, so the catalog is not one list of distinct grants | line {line}: {row} | correct the catalog so each line is one consumer|item|field (item an exact name or role:<exact name>), once; the release ships src/worker/deploy/acquire/skarbiec-acquisition-scopes.conf | config, exit 1, HTTP 503 | tests/skarbiec/acquisition-failures.mjs::an_acquisition_whose_scope_catalog_repeats_a_scope_is_refused |
acquire-scope-undeclared | checking that the scope catalog {scope_file} declares {consumer} on {item}#{field} | none of its {declared} declared scopes is {consumer}|{item}|{field}, so the workload never asks Skarbiec for it | {declared} declared scopes in {scope_file} | add the line {consumer}|{item}|{field} to the catalog the release ships, with the Skarbiec grant it stands for, or ask for a declared scope | config, exit 1, HTTP 503 | tests/skarbiec/acquisition-failures.mjs::an_undeclared_scope_is_refused_before_skarbiec_is_asked |
acquire-workload-id-invalid | naming the workload that asks for {consumer} on {item}#{field} | SKARBIEC_WORKLOAD_ID is {why}, where it is nonempty text without surrounding space or control characters | SKARBIEC_WORKLOAD_ID: {shown} | set SKARBIEC_WORKLOAD_ID to the name Skarbiec's workload registry knows this service by, as its service unit declares it | config, exit 1, HTTP 503 | tests/skarbiec/acquisition-failures.mjs::an_acquisition_without_a_workload_id_is_refused |
acquire-signing-key-path-invalid | proving the workload {workload} for {consumer} on {item}#{field} | SKARBIEC_WORKLOAD_SIGNING_KEY_FILE is {shown}, which is not an absolute path | SKARBIEC_WORKLOAD_SIGNING_KEY_FILE: {shown} | point SKARBIEC_WORKLOAD_SIGNING_KEY_FILE at the workload key Skarbiec's workload registry holds the public half of (a regular file of this user, mode 600), as the service unit declares it | config, exit 1, HTTP 503 | tests/skarbiec/acquisition-failures.mjs::an_acquisition_with_a_relative_signing_key_path_is_refused |
acquire-signing-key-absent | proving the workload {workload} for {consumer} on {item}#{field} | no file is at {key_file}, so the workload cannot sign its proof | {answer} | point SKARBIEC_WORKLOAD_SIGNING_KEY_FILE at the workload key Skarbiec's workload registry holds the public half of (a regular file of this user, mode 600), as the service unit declares it | config, exit 1, HTTP 503 | tests/skarbiec/acquisition-failures.mjs::an_acquisition_whose_signing_key_is_missing_is_refused |
acquire-signing-key-unreadable | proving the workload {workload} for {consumer} on {item}#{field} | the signing key {key_file} cannot be read ({error_code}) | {answer} | point SKARBIEC_WORKLOAD_SIGNING_KEY_FILE at the workload key Skarbiec's workload registry holds the public half of (a regular file of this user, mode 600), as the service unit declares it | config, exit 1, HTTP 503 | tests/skarbiec/acquisition-failures.mjs::an_acquisition_whose_signing_key_cannot_be_read_is_refused |
acquire-signing-key-unsafe | proving the workload {workload} for {consumer} on {item}#{field} | the signing key {key_file} {why}: it is a regular file this user (uid {uid}) owns, closed to group and others | type {type}; owner uid {owner}; mode {mode} | point SKARBIEC_WORKLOAD_SIGNING_KEY_FILE at the workload key Skarbiec's workload registry holds the public half of (a regular file of this user, mode 600), as the service unit declares it; `chmod 600 {key_file}` closes it to others | config, exit 1, HTTP 503 | tests/skarbiec/acquisition-failures.mjs::an_acquisition_whose_signing_key_others_can_read_is_refused |
acquire-signing-key-unusable | proving the workload {workload} for {consumer} on {item}#{field} | the file {key_file} ({bytes} bytes) holds no private key the workload can sign with (a PEM Ed25519 key) | {answer} | point SKARBIEC_WORKLOAD_SIGNING_KEY_FILE at the workload key Skarbiec's workload registry holds the public half of (a regular file of this user, mode 600), as the service unit declares it | config, exit 1, HTTP 503 | tests/skarbiec/acquisition-failures.mjs::an_acquisition_whose_signing_key_is_no_key_is_refused |
acquire-authority-unreachable | Skarbiec {stage} at {origin} for {consumer} on {item}#{field} | Skarbiec did not answer POST {path}: nothing listens at {origin}, the forward to it is down, or the network refused the connection | {answer} | `stado capabilities resolve secrets --consumer weles --json` shows the route Stado names; ask again once it answers | infra_down, exit 69, HTTP 503, retryable | tests/skarbiec/acquisition-failures.mjs::an_acquisition_from_a_skarbiec_that_does_not_answer_names_it |
acquire-field-absent | Skarbiec {stage} at {origin} for {consumer} on {item}#{field} | Skarbiec answered HTTP {status}: the item carries no field {field} | POST {path}: HTTP {status}: {detail}; asserted workload {workload} | store {field} on the item (or the role's item), or ask for a field it carries | not_found, exit 1, HTTP 404 | tests/skarbiec/acquisition-failures.mjs::an_acquisition_of_a_field_the_item_lacks_names_it |
acquire-workload-unauthorized | Skarbiec {stage} at {origin} for {consumer} on {item}#{field} | Skarbiec answered HTTP {status}: no grant covers {consumer} for the workload {workload}, or its proof was not accepted | POST {path}: HTTP {status}: {detail}; asserted workload {workload} | grant the workload acquire:{item}#{field} for {consumer} in Skarbiec, or check the host's clock and workload key | auth, exit 1, HTTP 401 | tests/skarbiec/acquisition-failures.mjs::an_acquisition_no_grant_covers_names_it |
acquire-grant-ended | Skarbiec {stage} at {origin} for {consumer} on {item}#{field} | Skarbiec answered HTTP {status}: the acquire grant covering {consumer} for the workload {workload} has ended (Skarbiec's error grant_ended) | POST {path}: HTTP {status}: {detail}; asserted workload {workload} | register the acquisition catalog with Skarbiec again as its answer says; restarting the service does not renew a grant | auth, exit 1, HTTP 401 | tests/skarbiec/acquisition-failures.mjs::an_acquisition_whose_grant_ended_names_it |
acquire-refused | Skarbiec {stage} at {origin} for {consumer} on {item}#{field} | Skarbiec answered HTTP {status}: Skarbiec refused the {stage} for a reason its answer names | POST {path}: HTTP {status}: {detail}; asserted workload {workload} | correct what Skarbiec names; `stado service logs skarbiec` shows its side | refused, exit 1, HTTP 403 | tests/skarbiec/acquisition-failures.mjs::an_acquisition_skarbiec_refuses_quotes_it |
acquire-answer-unread | Skarbiec {stage} at {origin} for {consumer} on {item}#{field} | Skarbiec answered HTTP {status} to POST {path} and the connection broke before its body was read | {answer} | ask again; if it repeats, `stado service logs skarbiec` shows why | infra_down, exit 69, HTTP 503, retryable | tests/skarbiec/acquisition-failures.mjs::an_acquisition_answer_cut_off_is_named |
acquire-answer-not-json | Skarbiec {stage} at {origin} for {consumer} on {item}#{field} | Skarbiec answered HTTP {status} to POST {path} with a body that is not JSON: the route reaches something other than Skarbiec | {bytes} bytes; parser: {answer} | `stado capabilities resolve secrets --consumer weles --json` shows the route Stado names; it must reach Skarbiec | config, exit 1, HTTP 503 | tests/skarbiec/acquisition-failures.mjs::an_acquisition_answer_that_is_not_json_is_named |
acquire-bearer-invalid | Skarbiec {stage} at {origin} for {consumer} on {item}#{field} | the bearer Skarbiec issued is not bound to exactly {consumer}, {item} and {field} with a token and an expiry: {why} | fields {fields} | compare Skarbiec's release with the acquisition protocol this helper speaks (POST /v1/acquisitions answers consumer, expires_at, field, item, token, until_revoked) | config, exit 1, HTTP 503 | tests/skarbiec/acquisition-failures.mjs::an_acquisition_bearer_bound_elsewhere_is_named |
acquire-value-invalid | Skarbiec {stage} at {origin} for {consumer} on {item}#{field} | the one-time read did not answer exactly the bound field as nonempty text: {why} | shape {shape}; the value is not shown | compare Skarbiec's release with the acquisition protocol this helper speaks (POST /v1/acquisitions/read answers consumer, field, item, value and an optional provider) | config, exit 1, HTTP 503 | tests/skarbiec/acquisition-failures.mjs::an_acquisition_read_of_another_shape_is_named |
write-usage | reading the write helper's arguments | the helper takes <consumer> <item|role:ROLE> <field> <absolute-token-file> <operation> <operation-id>, and {why} | arguments: {given} | run node src/worker/deploy/acquire/skarbiec-write.mjs <consumer> <item|role:ROLE> <field> <absolute-token-file> <operation> <operation-id> with the payload on standard input | refused, exit 2, HTTP 403 | tests/skarbiec/write-failures.mjs::a_write_without_its_six_arguments_is_refused |
write-token-unread | reading the writer token for {consumer} writing {item}#{field} ({mode}) | the token file {token_file} cannot be read ({error_code}) | {answer} | state the writer token file Stado provisioned for {consumer} (a regular file of this user, mode 600) | config, exit 1, HTTP 503 | tests/skarbiec/write-failures.mjs::a_write_whose_token_file_is_missing_is_refused |
write-token-invalid | reading the writer token for {consumer} writing {item}#{field} ({mode}) | the token file {token_file} holds {why}, which is no bearer | {bytes} bytes; the value is not shown | write the token Skarbiec issued to {consumer} into {token_file}, without a trailing newline | config, exit 1, HTTP 503 | tests/skarbiec/write-failures.mjs::a_write_whose_token_holds_whitespace_is_refused |
write-payload-not-json | reading the payload of {consumer} writing {item}#{field} ({mode}) | the {bytes} bytes on standard input are not JSON | parser: {answer}; the payload is not shown | pass one skarbiec.item.v2 item on standard input whose fields carry {field}, as writeWelesAcquiredSecret builds it | refused, exit 1, HTTP 403 | tests/skarbiec/write-failures.mjs::a_write_of_a_payload_that_is_not_json_is_refused |
write-payload-shape | reading the payload of {consumer} writing {item}#{field} ({mode}) | the payload is not one skarbiec.item.v2 item carrying {field}: {why} | members {members}; the values are not shown | pass one skarbiec.item.v2 item on standard input whose fields carry {field}, as writeWelesAcquiredSecret builds it | refused, exit 1, HTTP 403 | tests/skarbiec/write-failures.mjs::a_write_of_a_payload_without_its_field_is_refused |
write-capture-origin-invalid | reading the payload of {consumer} writing {item}#{field} ({mode}) | the payload's capture_origin {found} is not one absolute https origin, which Skarbiec requires the write to echo | capture_origin: {found} | state capture_origin as https://<host> exactly as the acquisition declared it, or leave it out | refused, exit 1, HTTP 403 | tests/skarbiec/write-failures.mjs::a_write_with_a_capture_origin_that_is_no_origin_is_refused |
write-unreachable | PUT {origin}/v1/items for {consumer} writing {item}#{field} ({mode}) | Skarbiec did not answer: nothing listens at {origin}, the forward to it is down, or the network refused the connection | {answer} | `stado capabilities resolve secrets --consumer weles --json` shows the route Stado names; write again once it answers | infra_down, exit 69, HTTP 503, retryable | tests/skarbiec/write-failures.mjs::a_write_to_a_skarbiec_that_does_not_answer_names_it |
write-bearer-refused | PUT {origin}/v1/items for {consumer} writing {item}#{field} ({mode}) | Skarbiec answered HTTP {status}: it does not accept the writer token of {consumer} | HTTP {status}: {detail} | state the token Skarbiec issued to {consumer} in {token_file} | auth, exit 1, HTTP 401 | tests/skarbiec/write-failures.mjs::a_write_skarbiec_refuses_the_bearer_for_is_named |
write-skarbiec-failing | PUT {origin}/v1/items for {consumer} writing {item}#{field} ({mode}) | Skarbiec answered HTTP {status}: Skarbiec failed on its side, so nothing was written | HTTP {status}: {detail} | write again later; `stado service logs skarbiec` shows why it fails | infra_down, exit 69, HTTP 503, retryable | tests/skarbiec/write-failures.mjs::a_write_skarbiec_fails_on_is_named |
write-refused | PUT {origin}/v1/items for {consumer} writing {item}#{field} ({mode}) | Skarbiec answered HTTP {status}: Skarbiec refused the write for a reason its answer names | HTTP {status}: {detail} | correct what Skarbiec names (the grant, the mode, the origin); `stado service logs skarbiec` shows its side | refused, exit 1, HTTP 403 | tests/skarbiec/write-failures.mjs::a_write_skarbiec_refuses_quotes_it |
write-answer-unread | PUT {origin}/v1/items for {consumer} writing {item}#{field} ({mode}) | Skarbiec answered HTTP {status} and the connection broke before its body was read, so the write is not acknowledged | {answer} | write again with the same operation id; Skarbiec keeps one item per operation | infra_down, exit 69, HTTP 503, retryable | tests/skarbiec/write-failures.mjs::a_write_answer_cut_off_is_named |
write-answer-not-json | PUT {origin}/v1/items for {consumer} writing {item}#{field} ({mode}) | Skarbiec answered HTTP {status} with a body that is not JSON: the route reaches something other than Skarbiec | {bytes} bytes beginning {body}; parser: {answer} | `stado capabilities resolve secrets --consumer weles --json` shows the route Stado names; it must reach Skarbiec | config, exit 1, HTTP 503 | tests/skarbiec/write-failures.mjs::a_write_answer_that_is_not_json_is_named |
write-unacknowledged | PUT {origin}/v1/items for {consumer} writing {item}#{field} ({mode}) | Skarbiec answered HTTP {status} without acknowledging {item}: {why} | answer: {body} | compare Skarbiec's release with the write this helper makes (PUT /v1/items answers ok: true and the item's id) | config, exit 1, HTTP 503 | tests/skarbiec/write-failures.mjs::a_write_skarbiec_does_not_acknowledge_is_named |
broker#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
broker-socket-missing | asking the capability broker on {socket} of this machine ({machine}) for its status | nothing exists at that path: the Skarbiec unit of this machine is not running, or it listens on a socket other than the one Stado declares for it | {socket}: {answer} | start the Skarbiec unit of {machine} through Stado, or correct SKARBIEC_CAP_SOCKET in its declaration; `stado service status skarbiec` shows whether the Skarbiec unit of {machine} runs; `skarbiec capability status --socket {socket}` asks the broker as Weles did | infra_down, exit 69, HTTP 503, retryable | tests/secrets/broker-status-failures.mjs::a_socket_nothing_created_is_named |
broker-socket-stale | asking the capability broker on {socket} of this machine ({machine}) for its status | the socket file exists but no process listens on it: the broker that created it ended without removing it | {socket} ({state}): {answer} | restart the Skarbiec unit of {machine} through Stado so its broker listens again; `stado service status skarbiec` shows whether the Skarbiec unit of {machine} runs; `skarbiec capability status --socket {socket}` asks the broker as Weles did | infra_down, exit 69, HTTP 503, retryable | tests/secrets/broker-status-failures.mjs::a_socket_nobody_listens_on_is_named |
broker-socket-forbidden | asking the capability broker on {socket} of this machine ({machine}) for its status | the operating system refused this process (user {user}) the socket: its owner or mode admits another user only | {socket} ({state}): {answer} | run the Weles API as the user the Skarbiec unit admits, or let the Skarbiec unit grant that user the socket, through their Stado declarations | config, exit 1, HTTP 503 | tests/secrets/broker-status-failures.mjs::a_socket_this_user_may_not_open_is_named |
broker-socket-unreachable | asking the capability broker on {socket} of this machine ({machine}) for its status | the connection failed for a reason other than a missing, dead or forbidden socket (the system error {errno}) | {socket} ({state}): {answer} | correct what the system error names, then let the unit start again; `stado service status skarbiec` shows whether the Skarbiec unit of {machine} runs; `skarbiec capability status --socket {socket}` asks the broker as Weles did | infra_down, exit 69, HTTP 503, retryable | tests/secrets/broker-status-failures.mjs::a_socket_connection_the_system_breaks_is_named |
broker-answer-incomplete | asking the capability broker on {socket} of this machine ({machine}) for its status | the broker closed the connection before it ended its status line | {length} characters without a line end: {answer} | retry; a broker that keeps doing it is failing, `stado service status skarbiec` shows whether the Skarbiec unit of {machine} runs; `skarbiec capability status --socket {socket}` asks the broker as Weles did | infra_down, exit 69, HTTP 503, retryable | tests/secrets/broker-status-failures.mjs::a_broker_that_hangs_up_mid_answer_is_named |
broker-answer-invalid | asking the capability broker on {socket} of this machine ({machine}) for its status | its status line is not the {version} listening status with a process id that Skarbiec answers: another program holds the socket, or the broker speaks another version | {answer} | find what listens on {socket} (`lsof {socket}`) and let only this machine's Skarbiec broker hold it; `stado service status skarbiec` shows whether the Skarbiec unit of {machine} runs; `skarbiec capability status --socket {socket}` asks the broker as Weles did | config, exit 1, HTTP 503 | tests/secrets/broker-status-failures.mjs::a_broker_answer_that_is_no_status_is_named |
integrations#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
integrations-bearer-unset | reaching the {family} family for {domain}/{action} | WELES_STADO_INTEGRATION_TOKEN, Weles's own bearer for the action boundary, is {value} | WELES_STADO_INTEGRATION_TOKEN: {value} | set WELES_STADO_INTEGRATION_TOKEN in the Weles service's environment through Stado (`stado service env set weles`), then run it again | config, exit 1, HTTP 503 | tests/integrations/failures.mjs::a_run_without_its_bearer_is_refused_before_stado_is_asked |
integrations-unanswered | resolving the {family} family for weles, for {domain}/{action} | Stado names no alternative that answers it: the family is unknown to this Stado, or none of its alternatives answers | {answer} | repair what Stado names (`stado capabilities select {family} --order ...` puts one that answers in the order); `stado capabilities resolve {family} --consumer weles` shows the alternative Stado names | infra_down, exit 69, HTTP 503, retryable | tests/integrations/failures.mjs::a_family_stado_does_not_resolve_is_quoted |
integrations-alternative-invalid | resolving the {family} family for weles, for {domain}/{action} | the alternative Stado named is no wisent-integrations route Weles can call: an HTTPS origin, or HTTP to this machine's loopback, without credentials, path, query or fragment | {answer} | correct the address (`stado config set capabilities.{family}.url <https origin>`); `stado capabilities resolve {family} --consumer weles` shows the alternative Stado names | config, exit 1, HTTP 503 | tests/integrations/failures.mjs::an_alternative_that_is_no_route_is_named |
integrations-unreachable | the action {domain}/{action} at {endpoint} | no HTTP answer came back, also after Stado proved the route again: the boundary is down or the address does not lead to it | {answer} | check that Wisent Integrations answers at {route}; `stado capabilities resolve {family} --consumer weles` shows the alternative Stado names | infra_down, exit 69, HTTP 503, retryable | tests/integrations/failures.mjs::a_boundary_that_does_not_answer_is_named |
integrations-unauthorized | the action {domain}/{action} at {endpoint} | the boundary refused Weles's bearer (HTTP {status}): WELES_STADO_INTEGRATION_TOKEN is not one it admits, or it may not act for {domain} as {identity} | HTTP {status}: {answer} | mint Weles's integration bearer again and set it as WELES_STADO_INTEGRATION_TOKEN (`stado service env set weles`), or let Wisent Integrations admit it for {domain} | auth, exit 1, HTTP 401 | tests/integrations/failures.mjs::a_bearer_the_boundary_refuses_is_named |
integrations-action-refused | the action {domain}/{action} at {endpoint} | the boundary or the provider behind it refused the action as asked (HTTP {status}): its own error names why | HTTP {status}: {answer} | correct what the provider's error names (the id, the channel, the domain, the identity's scopes) and run it again | refused, exit 1, HTTP 403 | tests/integrations/failures.mjs::an_action_the_provider_refuses_is_quoted |
integrations-rate-limited | the action {domain}/{action} at {endpoint} | the boundary or the provider behind it is limiting this identity (HTTP {status}) | HTTP {status}: {answer} | wait the time the answer names, then run it again | rate_limit, exit 69, HTTP 429, retryable | tests/integrations/failures.mjs::an_action_the_provider_limits_is_named |
integrations-boundary-failing | the action {domain}/{action} at {endpoint} | the boundary failed on its side (HTTP {status}) | HTTP {status}: {answer} | retry; when it persists, read the Wisent Integrations log for {domain}/{action} | infra_down, exit 69, HTTP 503, retryable | tests/integrations/failures.mjs::a_boundary_that_fails_is_named |
integrations-answer-invalid | the action {domain}/{action} at {endpoint} | the answer is not the `{ok: true, result}` envelope the boundary answers with, or its result lacks what the caller reads: {problem} | HTTP {status}: {answer} | check that {route} is Wisent Integrations and not another service; `stado capabilities resolve {family} --consumer weles` shows the alternative Stado names | infra_down, exit 69, HTTP 503, retryable | tests/integrations/failures.mjs::an_answer_that_is_no_envelope_is_named |
records#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
record-id-invalid | naming a Weles record | a record id is an item id: a letter or digit, then letters, digits, `_`, `.`, `:` or `-` | id: {id} | name the record by the id its list answered | refused, exit 1, HTTP 403 | tests/records/failures.mjs::a_record_id_that_is_no_item_id_is_refused |
record-invalid | writing Weles record {id} | {what} must be {shape}; nothing was written | {answer} | hand the record over in that shape | refused, exit 1, HTTP 403 | tests/records/failures.mjs::an_account_without_metadata_is_refused |
argument-missing | reading the arguments of {program} {command} | {command} takes {name}, and none was given; nothing was written | arguments: {given} | run `node {program} {usage}` | refused, exit 2, HTTP 403 | tests/records/failures.mjs::a_subscription_command_without_its_id_is_refused |
command-unknown | reading the command {program} is asked | it answers {usage} | asked: {command} | run `node {program} {usage}` | refused, exit 2, HTTP 403 | tests/records/failures.mjs::a_records_program_asked_what_it_does_not_answer_is_refused |
json-argument-invalid | reading {name} of {program} {command} | it is not JSON; nothing was written | {answer} | pass {name} as one JSON object argument, quoted for the shell | refused, exit 2, HTTP 403 | tests/records/failures.mjs::a_subscription_row_that_is_not_json_is_refused |
subscription-not-object | reading the subscription {what} | it must be a JSON object; nothing was written | {what}: {answer} | pass the {what} as one JSON object | refused, exit 1, HTTP 403 | tests/records/failures.mjs::a_subscription_row_that_is_not_an_object_is_refused |
observation-incomplete | recording the subscription of {service}/{account} | it has no {missing}: what was observed, and when, is the collector's to state; nothing was written | missing: {missing} | state service_name, provider, account_identifier, status, last_verified_at and a metadata object | refused, exit 1, HTTP 403 | tests/records/failures.mjs::a_subscription_without_its_observation_is_refused |
observed-at-invalid | recording the subscription of {service}/{account} | subscription last_verified_at must be a date-time the collector observed; nothing was written | last_verified_at: {value} | state last_verified_at as an ISO 8601 date-time | refused, exit 1, HTTP 403 | tests/records/failures.mjs::a_subscription_observed_at_no_time_is_refused |
observation-split | updating subscription record {id} | subscription status and last_verified_at must be supplied together for a new observation; nothing was written | the patch names {named} | send status and last_verified_at in one patch | refused, exit 1, HTTP 403 | tests/records/failures.mjs::a_status_without_its_observation_time_is_refused |
subscription-not-found | reading service subscription record {id} | no live vault item tagged weles:record:service-subscription has that id: it was deleted, or it is another record | service subscription record {id} not found among {count} records | take the id from `node src/lib/subscribing/service_subscriptions.mjs list` | not_found, exit 1, HTTP 404 | tests/records/failures.mjs::a_subscription_that_does_not_exist_is_not_found |
subscription-unreadable | reading service subscription record {id} | the item is tagged weles:record:service-subscription and its context carries no subscription object: it was written by hand or by another program | context: {context} | write it again with `node src/lib/subscribing/service_subscriptions.mjs upsert`, or remove its tag | config, exit 1, HTTP 503 | tests/records/failures.mjs::a_subscription_item_without_its_subscription_is_named |
credential-id-invalid | naming the service credential | a credential id is an item id: a letter or digit, then letters, digits, `_`, `.`, `:` or `-`; nothing was written | id: {id} | name the credential by an item id | refused, exit 1, HTTP 403 | tests/records/failures.mjs::a_credential_id_that_is_no_item_id_is_refused |
login-method-missing | writing service credential {id} | it states no login_method; say how the account signs in; nothing was written | login_method: {value} | state login_method (password, google_sso, ...) | refused, exit 1, HTTP 403 | tests/records/failures.mjs::a_credential_without_its_login_method_is_refused |
kimi-address-missing | configuring Kimi credential {id} for Google sign-in | ensure-kimi-google-sso needs the Google account address: pass it, or name a source credential that carries login_email | source credential: {source} | run `node src/lib/service_credentials.mjs ensure-kimi-google-sso <id> <source> <email>` | refused, exit 1, HTTP 403 | tests/records/failures.mjs::a_kimi_credential_without_an_address_is_refused |
credential-unreadable | reading service credential {id} | the item carries no fields or no context object: it was written by hand or by another program | {answer} | write it again with `node src/lib/service_credentials.mjs patch {id} <json>` | config, exit 1, HTTP 503 | tests/records/failures.mjs::a_credential_item_without_its_context_is_named |
doctor#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
recording-runtime-unresolved | resolving the Playwright recording runtime | Playwright's browser manifest cannot be read, lists no browsers, declares no ffmpeg revision, or resolves no bundled ffmpeg executable | {answer} | reinstall the Weles release through Stado, whose payload carries Playwright and its browser manifest | config, exit 1, HTTP 503 | tests/doctor/failures.mjs::doctor_without_a_playwright_manifest_names_it |
recording-encoder-broken | probing {command} | the ffmpeg encoder Playwright records video with is missing, not a regular file, not executable, or fails to run, so every recorded browser task on this host fails | {answer} | repair the packaged recording runtime: install it with `npx playwright install ffmpeg` where PLAYWRIGHT_BROWSERS_PATH points, or reinstall the Weles release through Stado | config, exit 1, HTTP 503 | tests/doctor/failures.mjs::doctor_without_the_recording_encoder_names_it |
service-install-unreadable | reading the installed Weles service {root} | the directory exists and cannot be listed: its permissions or the disk refuse it | {answer} | give this user read access to {root}, or reinstall the service with `stado product install weles --surface service` | config, exit 1, HTTP 503 | tests/doctor/failures.mjs::doctor_over_an_install_it_cannot_list_names_it |
service-platform-absent | choosing the platform of the installed Weles service {root} | STADO_PLATFORM names {platform}, and {payload} is absent: no release for that platform is installed | the installed platforms are {platforms} | set STADO_PLATFORM to the platform this host runs, or install its release with `stado product install weles --surface service` | config, exit 1, HTTP 503 | tests/doctor/failures.mjs::doctor_with_a_platform_nothing_is_installed_for_names_it |
service-platform-ambiguous | choosing the platform of the installed Weles service {root} | several platforms hold a payload, and nobody set STADO_PLATFORM to choose one | several Weles service payloads are installed: {payloads} | set STADO_PLATFORM to the platform this host runs | config, exit 1, HTTP 503 | tests/doctor/failures.mjs::doctor_over_two_installed_platforms_without_a_choice_names_them |
service-payload-unreadable | hashing the installed payload {payload} | the payload exists and cannot be read: its permissions refuse it, or it is not a regular file | {answer} | reinstall the service with `stado product install weles --surface service` | config, exit 1, HTTP 503 | tests/doctor/failures.mjs::doctor_over_a_payload_it_cannot_read_names_it |
service-address-invalid | reading the Weles API address {variable} names | it is not an http(s) URL, or WELES_API_PORT is not a TCP port from 1 to 65535 | {variable}: {value} | set WELES_WORKER_API_BASE to the API URL, or WELES_API_PORT to the port its service declaration assigns | config, exit 1, HTTP 503 | tests/doctor/failures.mjs::doctor_with_an_api_address_that_is_not_a_url_names_it |
service-unreachable | the Weles API at {address} could not be read | nothing accepts a connection there: the Weles service of this host is stopped, or the address names another port | {answer} | start the service from what is installed with `stado service ensure weles --host <this host> --reason <why>`, or correct WELES_WORKER_API_BASE or WELES_API_PORT | infra_down, exit 69, HTTP 503, retryable | tests/doctor/failures.mjs::doctor_whose_api_address_nothing_answers_names_it |
service-health-refused | the Weles API at {address} could not be read | its /healthz answers an HTTP error, not the service identity | {address}/healthz answered HTTP {status} | read why with `stado service logs weles`, then start it from what is installed with `stado service ensure weles --host <this host> --reason <why>` | infra_down, exit 69, HTTP 503, retryable | tests/doctor/failures.mjs::doctor_whose_api_answers_an_http_error_names_it |
service-not-weles | the Weles API at {address} could not be read | what answers there is not the Weles API: its /healthz is not JSON naming source weles_api | {address}/healthz names {source}, not weles_api | correct WELES_WORKER_API_BASE or WELES_API_PORT to the port the Weles service declaration assigns | config, exit 1, HTTP 503 | tests/doctor/failures.mjs::doctor_whose_api_is_another_program_names_it |
service-not-installed | comparing the Weles API at {address} with the installed release | no weles-worker release is installed under {root}, so Stado did not install what answers on this port | the Weles API at {address} serves {served} | install the release with `stado product install weles --surface service` | config, exit 1, HTTP 503 | tests/doctor/failures.mjs::doctor_whose_api_has_no_installed_release_names_it |
service-release-mismatch | comparing the Weles API at {address} with the installed release | the running process is not the installed release: it did not restart onto it, or another release holds the port | the Weles API at {address} serves {served}, while the installed payload {payload} is sha256 {sha256} | start it from what is installed with `stado service ensure weles --host <this host> --reason <why>` | config, exit 1, HTTP 503 | tests/doctor/failures.mjs::doctor_whose_api_serves_another_release_names_both |
capture#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
param-not-object | reading the {action} parameters | {action} requires {name} as an object. | {name}: {value} | send {name} as a JSON object | refused, exit 2, HTTP 403 | tests/capture/params-failures.mjs::a_viewport_that_is_not_an_object_is_refused |
param-not-slug | reading the {action} parameters | {action} requires {name} as a lowercase slug of letters, digits, dot, dash or underscore. | {name}: {value} | send {name} as at most 81 such characters, starting with a letter or digit | refused, exit 2, HTTP 403 | tests/capture/params-failures.mjs::a_batch_that_is_not_a_slug_is_refused |
source-url-invalid | reading the {action} parameters | {action} requires {name} as an http(s) URL. | {name}: {value} | send {name} as an absolute http:// or https:// address | refused, exit 2, HTTP 403 | tests/capture/params-failures.mjs::a_source_url_that_is_not_http_is_refused |
param-not-integer | reading the {action} parameters | {action} rejects {name} {value}: {name} must be an integer between {min} and {max}. | {name}: {value} | send {name} as a whole number from {min} to {max} | refused, exit 2, HTTP 403 | tests/capture/params-failures.mjs::a_viewport_width_of_zero_is_refused |
scale-not-positive | reading the {action} parameters | {action} rejects viewport.device_scale_factor {value}: device_scale_factor must be a positive number. | viewport.device_scale_factor: {value} | send device_scale_factor as a number above zero, or leave it out for 1 | refused, exit 2, HTTP 403 | tests/capture/params-failures.mjs::a_device_scale_factor_of_zero_is_refused |
artifact-prefix-invalid | reading the {action} parameters | {action} rejects artifact_prefix {value}: {reason} | artifact_prefix: {value} | send artifact_prefix as {root}<segment>/.../ of letters, digits, dot, dash or underscore, ending with a slash | refused, exit 2, HTTP 403 | tests/capture/params-failures.mjs::an_artifact_prefix_outside_the_root_is_refused |
steps-not-array | reading the {action} parameters | {action} requires steps as an array. | steps: {value} | send steps as a list of { "op": ..., "value": ... }, or leave it out | refused, exit 2, HTTP 403 | tests/capture/params-failures.mjs::steps_that_are_not_a_list_are_refused |
step-op-unknown | reading the {action} parameters | {action} rejects step op {op}: op must be one of {ops}. | op: {op} | send each step with an op of {ops} | refused, exit 2, HTTP 403 | tests/capture/params-failures.mjs::a_step_of_an_unknown_op_is_refused |
step-value-missing | reading the {action} parameters | {action} rejects step op {op} without a value: {name} requires a value. | value: {value} | send the step with the selector, key or address its op acts on as value | refused, exit 2, HTTP 403 | tests/capture/params-failures.mjs::a_click_step_without_a_selector_is_refused |
axis-unknown | reading the {action} parameters | {action} rejects axis {value}: axis must be one of {axes}. | axis: {value} | send axis as one of {axes} | refused, exit 2, HTTP 403 | tests/capture/params-failures.mjs::an_unknown_axis_is_refused |
record-seconds-invalid | reading the {action} parameters | {action} rejects record_seconds {value}: record_seconds must be a number of seconds, zero or more. | record_seconds: {value} | send record_seconds as 0 for stills, or the seconds of video to record | refused, exit 2, HTTP 403 | tests/capture/params-failures.mjs::negative_record_seconds_are_refused |
full-page-invalid | reading the {action} parameters | {action} rejects full_page {value}: full_page must be true or false. | full_page: {value} | send full_page as true or false, or leave it out | refused, exit 2, HTTP 403 | tests/capture/params-failures.mjs::a_full_page_that_is_not_a_boolean_is_refused |
plan-missing | reading the plan the {action} trajectory was handed | {name} is required: the trajectory runs only from the plan dispatch wrote | {name}: {value} | start the action through the worker (POST /run), which writes {name}; a hand run sets it to the JSON parameters | refused, exit 2, HTTP 403 | tests/capture/params-failures.mjs::a_capture_run_without_its_plan_is_refused |
release#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
document-unreadable | reading the {document} at {path} | the file is not there or this user cannot read it: a path naming another directory, or a checkout that lacks it | {answer} | name the file that holds the {document}, or run in the checkout that carries it | not_found, exit 1, HTTP 404 | tests/release/documents/document-failures.mjs::a_document_that_is_not_there_is_named |
document-not-json | reading the {document} at {path} | the file is there but does not hold one JSON document | {answer} | write the {document} as the JSON the release pipeline produced, or name the file that holds it | config, exit 1, HTTP 503 | tests/release/documents/document-failures.mjs::a_document_that_is_not_json_is_named |
document-unwritable | writing the {document} to {path} | the directory is not there or this user cannot write to it, so the document stays as it was | {answer} | name a path in a directory this user can write, then run again | config, exit 1, HTTP 503 | tests/release/documents/document-failures.mjs::a_document_that_cannot_be_written_is_named |
surface-reader-unavailable | loading the TypeScript compiler the surface is read with | the surface is read from a checkout through its development dependencies, and this install carries no TypeScript compiler: an installed weles, or a checkout before npm ci | {answer} | run weles release surface --root <checkout> from a Weles checkout after npm ci | config, exit 1, HTTP 503 | tests/release/surface-failures.mjs::surface_without_the_typescript_compiler_names_it |
surface-tsconfig-missing | finding the TypeScript program of {root} | the directory holds no tsconfig.json, so it is no Weles checkout and its program cannot be read | no file {path} | name a Weles checkout with --root, or run in one | config, exit 1, HTTP 503 | tests/release/surface-failures.mjs::surface_of_a_directory_without_tsconfig_names_it |
surface-tsconfig-invalid | reading the TypeScript program of {root} | TypeScript refuses its tsconfig.json in the words below | {path}: {answer} | correct the tsconfig.json TypeScript names, then run again | config, exit 1, HTTP 503 | tests/release/surface-failures.mjs::surface_of_a_checkout_whose_tsconfig_is_broken_names_it |
surface-source-unparsed | reading {what} out of the program of {root} | the program TypeScript built does not hold the file the surface is read from: it is not there, or tsconfig.json leaves it out | not parsed: {path} | run in a complete Weles checkout, or include the file in tsconfig.json | config, exit 1, HTTP 503 | tests/release/surface-failures.mjs::surface_of_a_checkout_without_its_library_entry_names_it |
surface-index-unexported | reading the exports of src/index.ts in {root} | src/index.ts is not a module: it exports nothing, so the library publishes nothing a reader can list | {path} has no module symbol | export the library from src/index.ts, then run again | config, exit 1, HTTP 503 | tests/release/surface-failures.mjs::surface_of_a_library_entry_that_exports_nothing_names_it |
surface-bin-missing | reading the commands package.json publishes in {root} | package.json has no bin map, so no command of the package is known | bin: {value} | restore the bin map of package.json, then run again | config, exit 1, HTTP 503 | tests/release/surface-failures.mjs::surface_of_a_package_without_commands_names_it |
surface-command-not-literal | reading the CliCommand union of {path} | a member of CliCommand is not a string literal, so the command it stands for has no name the surface can list | member: {value} | write every member of CliCommand as a string literal | config, exit 1, HTTP 503 | tests/release/surface-failures.mjs::surface_of_a_cli_whose_command_is_no_literal_names_it |
surface-nothing-resolved | reading the {what} of {root} | the scan found none, and a build that publishes no {what} is a scan that broke: the declaration it reads moved or was renamed | {path} | restore the declaration the reader names (CliCommand in src/cli.ts, ToolDefinition[] lists in src/mcp, "METHOD /path" literals in the worker API server), or teach src/release/surface/published.mjs where it moved | config, exit 1, HTTP 503 | tests/release/surface-failures.mjs::surface_of_a_cli_without_commands_names_it |
surface-tool-not-literal | reading the MCP tools of {path} | a tool of a ToolDefinition[] list is not an object literal with a string literal name, so the tool has no name the surface can list | {value} | declare every MCP tool as an object literal with name: "<tool>" | config, exit 1, HTTP 503 | tests/release/surface-failures.mjs::surface_of_an_mcp_tool_without_a_literal_name_names_it |
surface-schemas-none | reading the API schemas of release/policy/compatibility-policy.json in {root} | the compatibility policy lists no API schema in api.supportedSchemas, so the release would claim none | api.supportedSchemas: {value} | list the API schemas this release serves in api.supportedSchemas | config, exit 1, HTTP 503 | tests/release/surface-failures.mjs::surface_of_a_policy_without_schemas_names_it |
surface-version-undeclared | reading the released version of release/version-change.json in {root} | the version declaration states no current version, so the surface cannot say which release it is | current: {value} | adopt what Stado publishes (scripts/release/version/adopt-published.mjs), which writes current | config, exit 1, HTTP 503 | tests/release/surface-failures.mjs::surface_of_a_checkout_without_its_released_version_names_it |
declaration-schema-unsupported | reading the version declaration | its schema is not weles.version-change.v1, the one the gate reads | schema: {value} | write release/version-change.json as weles.version-change.v1 (scripts/release/version/declare-candidate.mjs writes one) | config, exit 1, HTTP 503 | tests/release/deploying/enforce-failures.mjs::enforce_of_a_declaration_of_another_schema_names_it |
declaration-field-invalid | reading {field} of the version declaration | {field} must be {shape} | {field}: {value} | state {field} as {shape} in release/version-change.json | config, exit 1, HTTP 503 | tests/release/deploying/enforce-failures.mjs::enforce_of_a_declaration_without_its_reason_names_it |
baseline-disagrees | holding the released baseline against the version declaration | the baseline records another release than the declaration names as current, so one of them was not adopted from what Stado publishes | baseline {baseline}, declaration current {current} | adopt what Stado publishes (scripts/release/version/adopt-published.mjs), which writes both | refused, exit 1, HTTP 403 | tests/release/deploying/enforce-failures.mjs::enforce_of_a_baseline_another_release_names_it |
decision-disagrees | holding AutoVersion's decision against the version declaration | AutoVersion judged from another current release than the declaration names | AutoVersion current {decided}, declaration current {current} | run AutoVersion against the baseline the declaration names, or adopt the published release first | refused, exit 1, HTTP 403 | tests/release/deploying/enforce-failures.mjs::enforce_of_a_decision_from_another_release_names_it |
package-disagrees | holding package.json against the version declaration | the package version is not the candidate the declaration names | package {version}, declaration candidate {candidate} | set the candidate in both (npm version <candidate> after declare-candidate.mjs), then run again | refused, exit 1, HTTP 403 | tests/release/deploying/enforce-failures.mjs::enforce_of_a_package_of_another_version_names_it |
decision-field-invalid | reading {field} of AutoVersion's decision | {field} must be {shape} | {field}: {value} | run AutoVersion again and pass the decision it writes | config, exit 1, HTTP 503 | tests/release/deploying/enforce-failures.mjs::enforce_of_a_decision_of_an_unknown_change_names_it |
breaking-not-escalated | holding a declared breaking change against AutoVersion | the declaration says the change breaks callers and AutoVersion found no published surface removed | declared breaking, AutoVersion change {change} | remove the surface the change breaks, or declare it not breaking with its reason | refused, exit 1, HTTP 403 | tests/release/deploying/enforce-failures.mjs::enforce_of_a_breaking_change_autoversion_did_not_find_names_it |
version-kept | holding the package version against a changed surface | the published surface changed and the package keeps the released version, so callers could not tell the releases apart | package remains {current}; AutoVersion requires {required} | declare {required} as candidate and set it in package.json | refused, exit 1, HTTP 403 | tests/release/deploying/enforce-failures.mjs::enforce_of_a_changed_surface_under_the_same_version_names_it |
version-not-required | holding the declared candidate against AutoVersion | the candidate is not the version the surface change requires: too small a step hides a change, too large one claims a break that is not there | declared {candidate}, AutoVersion requires {required} | declare {required} as candidate and set it in package.json | refused, exit 1, HTTP 403 | tests/release/deploying/enforce-failures.mjs::enforce_of_a_candidate_autoversion_does_not_require_names_it |
manifest-schema-unsupported | reading the candidate deployment manifest {path} | its schema is not weles.deployment.v2, the contract a release is judged by | schema: {value} | write the manifest as weles.deployment.v2 | config, exit 1, HTTP 503 | tests/release/validate-failures.mjs::validate_of_a_manifest_of_another_schema_names_it |
manifest-properties-mismatch | reading {field} of the candidate deployment manifest {path} | {field} must hold exactly the properties the contract names, no fewer and no others | missing [{missing}], unexpected [{unexpected}] | write {field} with exactly the properties weles.deployment.v2 names | config, exit 1, HTTP 503 | tests/release/validate-failures.mjs::validate_of_a_manifest_with_a_property_too_many_names_it |
manifest-field-invalid | reading {field} of the candidate deployment manifest {path} | {field} must be {shape} | {field}: {value} | write {field} as {shape} | config, exit 1, HTTP 503 | tests/release/validate-failures.mjs::validate_of_a_manifest_whose_revision_is_short_names_it |
manifest-revision-mismatch | holding the candidate manifest against the revision released | the manifest was made for another source revision than the release is cut from | manifest sourceRevision {manifest}, release target {target} | make the manifest from the revision the release is for, or release the revision it names | refused, exit 1, HTTP 403 | tests/release/validate-failures.mjs::validate_of_a_manifest_for_another_revision_names_it |
candidate-tag-mismatch | holding the candidate tag against the manifest | a candidate tag names the manifest deployment and an abbreviation of the released revision, and this one does not | tag {tag}; expected {prefix}<abbreviation of {revision}> | cut the tag as {prefix}<abbreviation of {revision}> | refused, exit 1, HTTP 403 | tests/release/validate-failures.mjs::validate_with_a_tag_of_another_deployment_names_it |
adopt-released-invalid | reading the released version to adopt | it is not a SemVer version, so no baseline can be ordered against it | released: {value} | give --released the version the release was published as, such as 0.8.0 | refused, exit 2, HTTP 403 | tests/release/deploying/adopt-failures.mjs::adopting_a_release_that_is_no_version_is_refused |
adopt-surface-empty | reading the published surface {path} | the document lists no surface, and a release that published nothing is a surface that was not read | surface: {value} | give --published-surface the released-surface.json the published build's runtime payload carries | config, exit 1, HTTP 503 | tests/release/deploying/adopt-failures.mjs::adopting_a_surface_that_lists_nothing_is_refused |
adopt-baseline-unversioned | reading the version the recorded baseline {path} names | the recorded baseline states no SemVer version, so nothing can say whether this release moves back from it | version: {value} | restore the baseline from the repository, or pass --correcting <its version> to replace a claim that was never published | config, exit 1, HTTP 503 | tests/release/deploying/adopt-failures.mjs::adopting_over_a_baseline_without_a_version_is_refused |
adopt-moves-backwards | recording {released} as published over {recorded} | the baseline would move backwards, which lets a surface that was removed pass as newly added | recorded {recorded}, released {released} | adopt the newer release, or pass --correcting {recorded} to state that {recorded} was never published | refused, exit 1, HTTP 403 | tests/release/deploying/adopt-failures.mjs::adopting_an_older_release_over_a_newer_baseline_is_refused |
adopt-platform-missing | reading the command line of adopt-published.mjs | --platform is required: it reads the published archive of one platform, and none was named | given: {given} | run node scripts/release/version/adopt-published.mjs --platform <platform> [--root <checkout>] | refused, exit 2, HTTP 403 | tests/release/deploying/adopt-failures.mjs::adopting_the_published_release_without_a_platform_is_refused |
adopt-product-unnamed | reading the product and runtime payload .wisent-release.json names in {root} | the release manifest names no product or no runtime.binary, so there is no release to ask Stado for and no payload to read the surface from | product: {product}, runtime.binary: {payload} | run in a Weles checkout, or name one with --root | config, exit 1, HTTP 503 | tests/release/deploying/adopt-failures.mjs::adopting_from_a_manifest_naming_no_payload_is_refused |
adopt-targets-unstated | reading `stado release status {product} --json` | Stado answered a document without the targets list its release status carries: a Stado older than this Weles | targets: {value} | upgrade Stado to the minimum_stado_version this Weles release names, then run again | config, exit 1, HTTP 503 | tests/release/deploying/adopt-failures.mjs::adopting_from_a_status_without_targets_names_it |
adopt-nothing-published | finding the {platform} release Stado serves of {product} | no target Stado rolls {product} out to desires a {platform} release | targets: {targets} | publish a {platform} release first (stado release submit), or name a platform it was published for | not_found, exit 1, HTTP 404 | tests/release/deploying/adopt-failures.mjs::adopting_a_platform_nothing_was_published_for_names_it |
adopt-rollout-unsettled | finding the one release Stado serves of {product} | the targets desire different releases, so a rollout is still moving and no single release is the published one | {named} | wait until stado release status {product} names one desired release on every target, then run again | refused, exit 1, HTTP 403 | tests/release/deploying/adopt-failures.mjs::adopting_during_a_rollout_names_each_release |
adopt-archive-unreadable | taking {member} out of {archive} | tar could not extract it: the archive Stado fetched does not carry the runtime payload or its released-surface.json | tar exited {status}: {answer} | fetch the release again with stado release fetch; a release whose payload lacks released-surface.json was built before the surface was carried, and is adopted with weles release adopt from its source | config, exit 1, HTTP 503 | tests/release/deploying/adopt-failures.mjs::adopting_an_archive_without_its_surface_names_it |
deployment-identity-partial | reading the release identity this worker was deployed with | the release identity is stated in part: {set} of {total} settings hold a value, and once any setting only a deployment states holds one, an identity is stated whole | unset: {unset} | start the worker through its release launcher, which states every WELES_* release setting together, or unset the deployment's own settings (version, revision, digests, deployment id, ring, claims switch, API schemas) to run without one | config, exit 1, HTTP 500 | tests/release/deploying/deployment-failures.mjs::a_partly_stated_release_identity_names_the_unset_settings |
deployment-ring-unknown | reading the release identity this worker was deployed with | WELES_DEPLOYMENT_RING is {ring}, which is not a release ring | rings: {rings} | set WELES_DEPLOYMENT_RING to one of {rings}; start the worker through its release launcher, which states every WELES_* release setting together | config, exit 1, HTTP 500 | tests/release/deploying/deployment-failures.mjs::a_deployment_ring_of_no_release_names_the_rings |
deployment-claims-invalid | reading the release identity this worker was deployed with | WELES_CLAIMS_ENABLED is {value}, which is not a claims switch | switches: {switches} | set WELES_CLAIMS_ENABLED to one of {switches}; start the worker through its release launcher, which states every WELES_* release setting together | config, exit 1, HTTP 500 | tests/release/deploying/deployment-failures.mjs::a_claims_switch_that_is_none_names_the_switches |
deployment-ring-claims | reading the release identity this worker was deployed with | release ring {ring} {claims} queued work, but WELES_CLAIMS_ENABLED is {value} | ring {ring}; WELES_CLAIMS_ENABLED {value} | set WELES_CLAIMS_ENABLED to {expected} for ring {ring}; start the worker through its release launcher, which states every WELES_* release setting together | config, exit 1, HTTP 500 | tests/release/deploying/deployment-failures.mjs::a_claims_switch_against_its_ring_names_both |
deployment-instance-unnamed | naming this worker instance | neither WELES_INSTANCE_ID nor INSTANCE_ID is set, and this machine reports no host name, so the id would name no machine | host name: empty; process {pid} | set WELES_INSTANCE_ID in the worker's environment, or give the machine a host name (`hostname` prints it) | config, exit 1, HTTP 500 | tests/release/deploying/deployment-failures.mjs::an_instance_on_a_nameless_host_is_refused |
objects#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
token-repeated | reading WELES_STADO_OBJECT_API_TOKEN, the bearer of the Stado object API | it holds the same value as {sibling}: each service Weles reaches has a bearer of its own, so one leaked token opens one service only | WELES_STADO_OBJECT_API_TOKEN equals {sibling} | state the object API bearer from its own item (`stado credentials get --role weles-object-api --field token`) and {sibling} from its own, then start again | config, exit 1, HTTP 503 | tests/artifacts/object-failures.mjs::an_object_token_repeating_another_bearer_is_refused |
namespace-unknown | naming the private object {key} | Weles writes objects only into the namespaces it declares, and {namespace} is not one | namespace: {namespace}; declared: {namespaces} | write into one of {namespaces}; a new namespace is declared in src/worker/objects/stado-object.ts first | refused, exit 1, HTTP 403 | tests/artifacts/object-failures.mjs::an_object_in_an_undeclared_namespace_is_refused |
key-empty | naming a private object in {namespace} | the object key is empty, so it names no object | key: "" | name the object by its path under the namespace, such as recordings/<run>/<file> | refused, exit 1, HTTP 403 | tests/artifacts/object-failures.mjs::an_object_without_a_key_is_refused |
key-character | naming the private object {key} in {namespace} | the key holds {character}, which a stado:// address cannot carry unescaped (a backslash, ?, # or a control character) | key: {key} | rename the file or step the key comes from without {character} | refused, exit 1, HTTP 403 | tests/artifacts/object-failures.mjs::an_object_key_holding_a_reserved_character_is_refused |
key-segment | naming the private object {key} in {namespace} | segment {index} of the key is {segment}: a key is relative and every segment names one step down (no empty segment, no . or ..) | key: {key} | name the object by a relative path without empty, . or .. segments and without a leading or trailing / | refused, exit 1, HTTP 403 | tests/artifacts/object-failures.mjs::an_object_key_with_a_dot_segment_is_refused |
api-unreachable | {method} {uri} at the Stado object API {origin} | the object API did not answer: nothing listens at {origin}, the forward to it is down, or the network refused the connection | {answer} | `stado service directory connect stado-object-api --consumer weles` shows the route this host reaches it at; once it answers, the run's artifacts are uploaded again when the task is retained | infra_down, exit 69, HTTP 503, retryable | tests/artifacts/object-failures.mjs::an_object_put_to_a_closed_port_names_the_origin |
answer-unread | reading the answer of {method} {uri} | the object API answered HTTP {status} and the connection broke before its body was read | {answer} | ask again; if it repeats, `stado service directory connect stado-object-api --consumer weles` shows the route this host reaches it at and check the forward's logs | infra_down, exit 69, HTTP 503, retryable | tests/artifacts/object-failures.mjs::an_object_answer_cut_off_is_named |
bearer-refused | {method} {uri} | the object API refused WELES_STADO_OBJECT_API_TOKEN (HTTP {status}): it is not the bearer the API issued to weles, or it was revoked | HTTP {status}: {body} | state WELES_STADO_OBJECT_API_TOKEN from `stado credentials get --role weles-object-api --field token` and start the worker again | auth, exit 1, HTTP 401 | tests/artifacts/object-failures.mjs::an_object_put_with_a_bearer_stado_refuses_is_named |
api-failing | {method} {uri} | the object API failed on its side (HTTP {status}), so the object was not {done} | HTTP {status}: {body} | ask again later; if it repeats, `stado service logs stado-object-api` shows why the API fails | infra_down, exit 69, HTTP 503, retryable | tests/artifacts/object-failures.mjs::an_object_api_failing_on_its_side_is_named |
request-refused | {method} {uri} | the object API refused the request (HTTP {status}): its answer says why | HTTP {status}: {body} | correct what the answer names (the object's size, type or address) and ask again; `stado service logs stado-object-api` shows the API's side of the refusal | refused, exit 1, HTTP 403 | tests/artifacts/object-failures.mjs::an_object_request_stado_refuses_quotes_its_answer |
object-absent | reading back {uri} | the object API holds no object at that address: it was never acknowledged, or it was removed after its locator was published | HTTP {status}: {body} | upload the run's artifacts again (the task's retention does it), then read it back | not_found, exit 1, HTTP 404 | tests/artifacts/object-failures.mjs::an_object_readback_of_an_absent_object_is_named |
receipt-not-json | reading the storage receipt of PUT {uri} | the object API answered HTTP {status} with a body that is not JSON, so nothing acknowledges the object was stored | {bytes} bytes beginning {body}; parser: {answer} | `stado service directory connect stado-object-api --consumer weles` shows the route this host reaches it at; a page that is no receipt means the route reaches something other than the object API | config, exit 1, HTTP 503 | tests/artifacts/object-failures.mjs::an_object_receipt_that_is_not_json_is_named |
receipt-invalid | reading the storage receipt of PUT {uri} | the receipt does not acknowledge exactly these bytes at this address: its {member} is {found} where {expected} was sent | receipt: {receipt} | compare the object API's version with the stado.storage-put-receipt.v1 Weles reads; the object is not published until a receipt acknowledges it | config, exit 1, HTTP 503 | tests/artifacts/object-failures.mjs::an_object_receipt_of_other_bytes_is_named |
readback-limit-invalid | reading back {uri} | the byte limit of a readback is a whole number above zero, and {limit} is not | limit: {limit} | pass the size the retention manifest recorded for the object as the limit | refused, exit 1, HTTP 403 | tests/artifacts/object-failures.mjs::an_object_readback_without_a_byte_limit_is_refused |
readback-uri-invalid | reading back {uri} | the address is {why}, so it names no private object Weles wrote | uri: {uri} | read back the stado://<namespace>/<key> URI a storage receipt acknowledged, unchanged | refused, exit 1, HTTP 403 | tests/artifacts/object-failures.mjs::an_object_readback_of_an_address_that_is_no_object_is_refused |
readback-stream-broken | reading back {uri} | the object API's answer broke off after {chunks} chunk(s) and {bytes} bytes, so the object cannot be compared | {answer} | read it back again; if it repeats, `stado service directory connect stado-object-api --consumer weles` shows the route this host reaches it at | infra_down, exit 69, HTTP 503, retryable | tests/artifacts/object-failures.mjs::an_object_readback_cut_off_is_named |
readback-too-large | reading back {uri} | the object holds more than the {limit} bytes it was recorded with: something else wrote to that address after the receipt | more than {limit} bytes streamed ({bytes} read before stopping) | upload the run's artifacts again so the address holds what its receipt acknowledged, and find what else writes there | config, exit 1, HTTP 503 | tests/artifacts/object-failures.mjs::an_object_larger_than_its_record_is_named |
tree-unreadable | listing the recordings of run {run} | Artifact directory read failed for {directory}: the worker user cannot list it ({code}), or it is not a directory | {answer} | give the worker's user read and search access to {directory} (`ls -ld {directory}` shows its owner and mode), or retain the task only once its run has recorded into it | config, exit 1, HTTP 503 | tests/artifacts/uploads/upload-failures.mjs::a_recordings_directory_the_worker_cannot_list_names_it |
file-unreadable | reading {file} of run {run} to store it | the worker user cannot read the file ({code}): its mode changed, or it was removed while the run was uploaded | {answer}; {stored} of {count} file(s) were already stored | give the worker's user read access to {file} (`ls -l {file}`), then retain the task again; objects already stored are acknowledged again without being rewritten | config, exit 1, HTTP 503 | tests/artifacts/uploads/upload-failures.mjs::a_recording_the_worker_cannot_read_names_it |
proof-unstatable | writing the upload proof of run {run} | Artifact proof stat failed for {file}: every object was acknowledged, but the file can no longer be examined ({code}), so its size cannot enter the proof | {answer} | find what removed or locked {file} after it was stored, restore it, then retain the task again; without a proof the run reads as not mirrored | config, exit 1, HTTP 503 | tests/artifacts/uploads/upload-failures.mjs::a_recording_removed_before_the_proof_names_it |
proof-unwritable | writing the upload proof of run {run} | every object was acknowledged, but {file} cannot be written ({code}): the run directory is read-only to the worker user, or its disk is full | {answer} | give the worker's user write access to {directory} and free space on its disk, then retain the task again; without a proof the run reads as not mirrored | config, exit 1, HTTP 503 | tests/artifacts/uploads/upload-failures.mjs::a_run_directory_refusing_the_proof_names_it |
artifacts#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
locators-not-object | checking a run's artifact locators | the artifacts are not one object of locator lists keyed by kind | artifacts: {found} | pass the locators uploadArtifacts answered for the run, unchanged: an object with exactly the keys {kinds} | refused, exit 1, HTTP 400 | tests/artifacts/delivery/signing-failures.mjs::locators_that_are_no_object_are_refused |
locators-kinds | checking a run's artifact locators | the artifacts hold the kinds {found} where exactly {kinds} are declared (src/worker/artifacts/artifact-kinds.json) | missing: {missing}; not declared: {extra} | pass the locators uploadArtifacts answered for the run, unchanged: one list for each of {kinds}, empty where the run recorded none | refused, exit 1, HTTP 400 | tests/artifacts/delivery/signing-failures.mjs::locators_of_other_kinds_are_refused |
locators-kind-not-list | checking a run's artifact locators | artifacts.{kind} is not a list of locators | artifacts.{kind}: {found} | pass the locators uploadArtifacts answered for the run, unchanged: artifacts.{kind} as a list, empty when the run recorded none of that kind | refused, exit 1, HTTP 400 | tests/artifacts/delivery/signing-failures.mjs::a_kind_that_is_no_list_is_refused |
locator-not-text | checking a run's artifact locators | {place} is not text, or has blanks around it: a locator is stado://weles/recordings/<run uuid>/<path>, with a lowercase run UUID and a path of plain segments | {place}: {found} | pass the locators uploadArtifacts answered for the run, unchanged | refused, exit 1, HTTP 400 | tests/artifacts/delivery/signing-failures.mjs::a_locator_that_is_no_text_is_refused |
locator-namespace | checking a run's artifact locators | {place} is not under stado://weles/recordings/: a locator is stado://weles/recordings/<run uuid>/<path>, with a lowercase run UUID and a path of plain segments | {place}: {found} | pass the locators uploadArtifacts answered for the run, unchanged | refused, exit 1, HTTP 400 | tests/artifacts/delivery/signing-failures.mjs::a_locator_outside_the_recordings_is_refused |
locator-character | checking a run's artifact locators | {place} holds {character}, which could leave the recordings path: a locator is stado://weles/recordings/<run uuid>/<path>, with a lowercase run UUID and a path of plain segments | {place}: {found} | pass the locators uploadArtifacts answered for the run, unchanged | refused, exit 1, HTTP 400 | tests/artifacts/delivery/signing-failures.mjs::a_locator_holding_a_reserved_character_is_refused |
locator-path | checking a run's artifact locators | {place} has no file under its run, or an empty, . or .. segment: a locator is stado://weles/recordings/<run uuid>/<path>, with a lowercase run UUID and a path of plain segments | {place}: {found} | pass the locators uploadArtifacts answered for the run, unchanged | refused, exit 1, HTTP 400 | tests/artifacts/delivery/signing-failures.mjs::a_locator_with_a_dot_segment_is_refused |
locator-run | checking a run's artifact locators | {place} names the run {run}, which is not a lowercase UUID: a locator is stado://weles/recordings/<run uuid>/<path>, with a lowercase run UUID and a path of plain segments | {place}: {found} | pass the locators uploadArtifacts answered for the run, unchanged | refused, exit 1, HTTP 400 | tests/artifacts/delivery/signing-failures.mjs::a_locator_of_no_run_uuid_is_refused |
delivery-unreachable | Weles artifact delivery POST {endpoint} | the artifact delivery service did not answer: nothing listens at {origin}, or the network refused the connection | {answer} | check WELES_ARTIFACT_DELIVERY_URL names the delivery service that runs for this deployment, and ask again once it answers | infra_down, exit 69, HTTP 503, retryable | tests/artifacts/delivery/signing-failures.mjs::signing_at_a_closed_port_names_the_service |
delivery-answer-unread | Weles artifact delivery POST {endpoint} | the delivery service answered HTTP {status} and the connection broke before its body was read | {answer} | ask again; if it repeats, read the delivery service's log for the request | infra_down, exit 69, HTTP 503, retryable | tests/artifacts/delivery/signing-failures.mjs::a_signing_answer_cut_off_is_named |
delivery-answer-not-json | Weles artifact delivery POST {endpoint} | the delivery service answered HTTP {status} with a body that is not JSON: WELES_ARTIFACT_DELIVERY_URL reaches something other than the delivery service | {bytes} bytes beginning {body}; parser: {answer} | set WELES_ARTIFACT_DELIVERY_URL to the delivery service's origin; a page that is no JSON is another server | config, exit 1, HTTP 503 | tests/artifacts/delivery/signing-failures.mjs::a_signing_answer_that_is_not_json_is_named |
delivery-refused | Weles artifact delivery POST {endpoint} | the delivery service refused to sign (HTTP {status}): {detail} | HTTP {status}; its cause {served}; its fix: {served_fix}; body: {body} | correct what the service names (a 401 is WELES_ARTIFACT_DELIVERY_TOKEN, a 400 or 403 the locators) and ask again; `weles errors show artifacts` lists the service's causes | refused, exit 1, HTTP 403 | tests/artifacts/delivery/signing-failures.mjs::a_signing_refusal_quotes_the_service |
delivery-answer-envelope | Weles artifact delivery POST {endpoint} | the answer is not an object holding expires_at as text and artifacts as an object, so it signs nothing | answer: {found} | compare the delivery service's release with this Weles release; it answers an object of expires_at (an ISO time) and artifacts (one list of signed URLs per kind) | config, exit 1, HTTP 503 | tests/artifacts/delivery/signing-failures.mjs::a_signed_answer_without_its_envelope_is_named |
delivery-answer-expiry | Weles artifact delivery POST {endpoint} | expires_at {found} is not a time after now ({now}), so every URL it signs is already dead or undated | expires_at: {found} | check the delivery host's clock against this host's, then compare the delivery service's release with this Weles release | config, exit 1, HTTP 503 | tests/artifacts/delivery/signing-failures.mjs::a_signed_answer_already_expired_is_named |
delivery-answer-count | Weles artifact delivery POST {endpoint} | artifacts.{kind} holds {found} where {expected} locator(s) of that kind were sent, so a URL cannot be matched to the locator it signs | artifacts.{kind}: {answer} | compare the delivery service's release with this Weles release | config, exit 1, HTTP 503 | tests/artifacts/delivery/signing-failures.mjs::a_signed_answer_dropping_a_locator_is_named |
delivery-answer-url | Weles artifact delivery POST {endpoint} | artifacts.{kind}[{index}] {why}, so it is not the signed URL of {locator} | artifacts.{kind}[{index}]: {found} | compare the delivery service's release with this Weles release; a signed URL is {origin}/v1/artifacts/object?uri=<locator>&expires=<epoch seconds>&signature=<hex> | config, exit 1, HTTP 503 | tests/artifacts/delivery/signing-failures.mjs::a_signed_answer_with_a_foreign_url_is_named |
serve-host-invalid | reading the artifact delivery service's configuration | WELES_ARTIFACT_DELIVERY_HOST holds {character}, so it names a path rather than the address the service listens on | WELES_ARTIFACT_DELIVERY_HOST: {host} | set WELES_ARTIFACT_DELIVERY_HOST to a host name or IP address (127.0.0.1 behind a forward) and start the service again | config, exit 1, HTTP 503 | tests/artifacts/delivery/delivery-server-failures.mjs::a_listen_address_that_is_a_path_is_refused |
serve-credentials-repeated | reading the artifact delivery service's configuration | {first} and {second} hold the same value: the signing bearer, the signing secret, the Stado object bearer and the subscriptions bearer are each its own, so one leaked value opens one door only | {first} equals {second}; the values are not shown | give {second} a value of its own (`stado credentials get --role <its role> --field token` answers each), and start the service again | config, exit 1, HTTP 503 | tests/artifacts/delivery/delivery-server-failures.mjs::credentials_that_repeat_each_other_are_refused |
serve-route-unknown | serving {method} {path} | the service answers only {routes}, and {method} {path} with the query {query} is none of them | method {method}; path {path}; query {query} | ask one of GET /v1/subscriptions (bearer OKO_WELES_SUBSCRIPTIONS_TOKEN), POST /v1/artifacts/sign (bearer WELES_ARTIFACT_DELIVERY_TOKEN) and GET /v1/artifacts/object with the uri, expires and signature a signing answered | not_found, exit 1, HTTP 404 | tests/artifacts/delivery/delivery-server-failures.mjs::a_request_for_no_route_names_the_routes |
serve-bearer-missing | serving {method} {path} | the request carries {found} where the route needs Authorization: Bearer <{setting}> | Authorization: {found} | send the request with Authorization: Bearer and the value of {setting} the service was started with | auth, exit 1, HTTP 401 | tests/artifacts/delivery/delivery-server-failures.mjs::a_request_without_its_bearer_is_refused |
serve-bearer-wrong | serving {method} {path} | the bearer the request carries ({length} characters) is not the value of {setting} the service was started with | a Bearer of {length} characters; the value is not shown | send the current value of {setting}; when it was rotated, start the service again with the new one | auth, exit 1, HTTP 401 | tests/artifacts/delivery/delivery-server-failures.mjs::a_request_with_another_bearer_is_refused |
serve-media-type-missing | serving {method} {path} | the request states no Content-Type, and the locators to sign are read only from a JSON body | Content-Type: absent | send the locators with Content-Type: application/json | refused, exit 1, HTTP 415 | tests/artifacts/delivery/delivery-server-failures.mjs::a_signing_request_stating_no_type_is_refused |
serve-media-type-wrong | serving {method} {path} | the request states the type {found}, and the locators to sign are read only from application/json | Content-Type: {found} | send the locators with Content-Type: application/json | refused, exit 1, HTTP 415 | tests/artifacts/delivery/delivery-server-failures.mjs::a_signing_request_of_another_type_is_refused |
serve-body-unread | serving {method} {path} | the connection broke after {bytes} bytes of the request body, before the whole body arrived | {answer} | send the request again; a body that never arrives whole is the caller or the network between it and the service | infra_down, exit 69, HTTP 400, retryable | tests/artifacts/delivery/delivery-server-failures.mjs::a_signing_body_cut_off_is_named |
serve-body-not-json | serving {method} {path} | the request body is not JSON, so it holds no locators to sign | {bytes} bytes beginning {body}; parser: {answer} | send {"artifacts": {<kind>: [<locator>, ...]}} as the body, one list per declared kind | refused, exit 1, HTTP 400 | tests/artifacts/delivery/delivery-server-failures.mjs::a_signing_body_that_is_not_json_is_refused |
serve-body-fields | serving {method} {path} | the request body holds {found} where it holds exactly one member, artifacts | body: {body} | send {"artifacts": {<kind>: [<locator>, ...]}} and nothing beside it | refused, exit 1, HTTP 400 | tests/artifacts/delivery/delivery-server-failures.mjs::a_signing_body_beside_its_artifacts_is_refused |
serve-signature-malformed | serving {method} {path} | the object URL does not carry exactly uri, expires (whole epoch seconds) and a hex signature: {why} | query: {query} | have the locator signed again (POST /v1/artifacts/sign) and fetch the URL that answer holds, unchanged | refused, exit 1, HTTP 403 | tests/artifacts/delivery/delivery-server-failures.mjs::an_object_url_without_its_signature_is_refused |
serve-url-expired | serving {method} {path} | the signed URL expired at {expires} ({expiry}), {ago} seconds before this request | expires {expires}; now {now} | have the locator signed again (POST /v1/artifacts/sign) and fetch the URL that answer holds, unchanged | refused, exit 1, HTTP 403 | tests/artifacts/delivery/delivery-server-failures.mjs::an_expired_object_url_is_refused |
serve-url-beyond-ttl | serving {method} {path} | the URL claims to live until {expires}, {ahead} seconds from now, past the {ttl} seconds (WELES_ARTIFACT_URL_TTL_SECONDS) this service signs for: the service never signed it | expires {expires}; now {now}; WELES_ARTIFACT_URL_TTL_SECONDS {ttl} | have the locator signed again (POST /v1/artifacts/sign) and fetch the URL that answer holds, unchanged | refused, exit 1, HTTP 403 | tests/artifacts/delivery/delivery-server-failures.mjs::an_object_url_living_past_the_ttl_is_refused |
serve-signature-wrong | serving {method} {path} | the signature does not sign {uri} until {expires} with WELES_ARTIFACT_SIGNING_SECRET: the URL was changed, or signed with a secret this service no longer holds | uri {uri}; expires {expires}; the signatures are not shown | have the locator signed again (POST /v1/artifacts/sign) and fetch the URL that answer holds, unchanged | refused, exit 1, HTTP 403 | tests/artifacts/delivery/delivery-server-failures.mjs::an_object_url_whose_signature_was_changed_is_refused |
serve-subscriptions-not-list | listing the subscriptions of Weles's service_subscriptions setting in Skarbiec | the setting holds {found} where it holds a list of subscription objects | service_subscriptions: {found} | write the subscription again through the worker's subscription route, so the setting holds one object per subscription with service_name and provider as text | config, exit 1, HTTP 502 | tests/artifacts/delivery/delivery-server-failures.mjs::subscriptions_that_are_no_list_are_named |
serve-subscription-row-invalid | listing the subscriptions of Weles's service_subscriptions setting in Skarbiec | subscription {index} (id {row_id}) {why} | service_subscriptions[{index}]: {row} | write the subscription again through the worker's subscription route, so the setting holds one object per subscription with service_name and provider as text | config, exit 1, HTTP 502 | tests/artifacts/delivery/delivery-server-failures.mjs::a_subscription_row_of_another_shape_is_named |
stream-object-unnamed | streaming {uri} out of the Stado object API {stado} | the locator {uri} ends in no object name, so there is no file to offer for download | uri {uri} | sign the locator of a file the run recorded, as uploadArtifacts answered it | refused, exit 1, HTTP 400 | tests/artifacts/delivery/delivery-server-failures.mjs::an_object_url_naming_no_file_is_refused |
stream-stado-unreachable | streaming {uri} out of the Stado object API {stado} | the object API did not answer GET {request}: nothing listens at {stado}, the forward to it is down, or the network refused the connection | {answer} | `stado service directory connect stado-object-api --consumer weles` shows the route this host reaches it at; set STADO_API_URL to it and fetch the URL again | infra_down, exit 69, HTTP 502, retryable | tests/artifacts/delivery/delivery-server-failures.mjs::an_object_from_a_stado_that_does_not_answer_names_it |
stream-stado-answer-unread | streaming {uri} out of the Stado object API {stado} | the object API answered HTTP {upstream_status} to GET {request} and the connection broke before its body was read | {answer} | fetch the URL again; if it repeats, `stado service directory connect stado-object-api --consumer weles` shows the route this host reaches it at and check the forward's logs | infra_down, exit 69, HTTP 502, retryable | tests/artifacts/delivery/delivery-server-failures.mjs::an_object_answer_stado_cuts_off_is_named |
stream-object-absent | streaming {uri} out of the Stado object API {stado} | the object API holds no object at {uri}: it was never stored, or it was removed after its locator was signed | GET {request}: HTTP {upstream_status}: {upstream_detail} | upload the run's artifacts again (the task's retention does it), have the locator signed again and fetch that URL | not_found, exit 1, HTTP 404 | tests/artifacts/delivery/delivery-server-failures.mjs::an_object_stado_does_not_hold_is_named |
stream-stado-bearer-refused | streaming {uri} out of the Stado object API {stado} | the object API refused WELES_STADO_OBJECT_API_TOKEN (HTTP {upstream_status}): it is not the bearer the API issued to weles, or it was revoked | GET {request}: HTTP {upstream_status}: {upstream_detail} | state WELES_STADO_OBJECT_API_TOKEN from `stado credentials get --role weles-object-api --field token` and start the delivery service again | config, exit 1, HTTP 502 | tests/artifacts/delivery/delivery-server-failures.mjs::an_object_stado_refuses_the_bearer_for_is_named |
stream-stado-failing | streaming {uri} out of the Stado object API {stado} | the object API failed on its side (HTTP {upstream_status}), so the object cannot be delivered | GET {request}: HTTP {upstream_status}: {upstream_detail} | fetch the URL again later; if it repeats, `stado service logs stado-object-api` shows why the API fails | infra_down, exit 69, HTTP 502, retryable | tests/artifacts/delivery/delivery-server-failures.mjs::an_object_stado_fails_to_read_is_named |
stream-stado-refused | streaming {uri} out of the Stado object API {stado} | the object API refused the read (HTTP {upstream_status}) for a reason other than the bearer or a missing object; its answer says which | GET {request}: HTTP {upstream_status}: {upstream_detail} | compare the object API's release with the GET /api/object?uri= read this service makes; `stado service logs stado-object-api` shows the API's side of the refusal | config, exit 1, HTTP 502 | tests/artifacts/delivery/delivery-server-failures.mjs::an_object_read_stado_refuses_quotes_its_answer |
stream-broken | streaming {uri} out of the Stado object API {stado} | the object API's answer broke off after the download began (HTTP {upstream_status} already sent to the downloader), so the download ends short | {answer} | fetch the URL again; if it repeats, `stado service directory connect stado-object-api --consumer weles` shows the route this host reaches it at and check the forward's logs | infra_down, exit 69, HTTP 503, retryable | tests/artifacts/delivery/delivery-server-failures.mjs::an_object_stado_breaks_off_mid_stream_is_named |
stream-download-abandoned | streaming {uri} out of the Stado object API {stado} | the downloader closed its connection before the object was sent whole: it cancelled, or the network between dropped | {answer} | fetch the URL again while it lives; the service needs nothing changed | infra_down, exit 69, HTTP 503, retryable | tests/artifacts/delivery/delivery-server-failures.mjs::a_download_the_downloader_closes_is_named |
credentials#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
bearer-missing | admitting the caller of POST /api/v1/credential-operations | one bearer is required, and the request carries no Authorization header | no Authorization header | ask the operator of the calling product for a bearer Skarbiec issues for audience weles with call:weles#credential-operations, and send it as the one Authorization: Bearer line | auth, exit 1, HTTP 401 | tests/credentials/admission-failures.mjs::a_request_without_a_bearer_is_refused |
bearer-repeated | admitting the caller of POST /api/v1/credential-operations | one bearer is required, and the request carries {count} Authorization lines, so which caller speaks is ambiguous | {count} Authorization lines | send exactly one Authorization: Bearer line | auth, exit 1, HTTP 401 | tests/credentials/admission-failures.mjs::a_request_with_two_bearers_is_refused |
bearer-malformed | admitting the caller of POST /api/v1/credential-operations | one bearer is required, and the Authorization line is not "Bearer <token>" with a token of no spaces | scheme {scheme}, {length} characters after it | ask the operator of the calling product for a bearer Skarbiec issues for audience weles with call:weles#credential-operations, and send it as the one Authorization: Bearer line | auth, exit 1, HTTP 401 | tests/credentials/admission-failures.mjs::a_request_whose_authorization_is_no_bearer_is_refused |
introspection-token-absent | admitting the caller of POST /api/v1/credential-operations | the worker holds no token of its own to ask Skarbiec with: {path} does not exist, so no caller can be introspected | {path} is absent | have Stado issue the worker's introspection token again (`stado credentials token mint` for consumer weles-credential-admission writes ~/.stado/weles-credential-admission-skarbiec-token, owner-only), then send the request again | config, exit 1, HTTP 503 | tests/credentials/admission-failures.mjs::a_worker_without_its_introspection_token_refuses |
introspection-token-unsafe | admitting the caller of POST /api/v1/credential-operations | the worker's introspection token file cannot be used: {reason} | {path} {reason} (cause {lower}) | make {path} a regular file only the worker's account can read (`chmod 600 {path}`), or have Stado issue the worker's introspection token again (`stado credentials token mint` for consumer weles-credential-admission writes ~/.stado/weles-credential-admission-skarbiec-token, owner-only), then send the request again | config, exit 1, HTTP 503 | tests/credentials/admission-failures.mjs::an_introspection_token_others_can_read_is_refused |
introspection-token-unread | admitting the caller of POST /api/v1/credential-operations | the system refused to read the worker's introspection token {path} ({errno}) | {path}: {errno}; system: {answer} | give the worker's account read access to {path} (`ls -l {path}`), or have Stado issue the worker's introspection token again (`stado credentials token mint` for consumer weles-credential-admission writes ~/.stado/weles-credential-admission-skarbiec-token, owner-only), then send the request again | config, exit 1, HTTP 503 | tests/credentials/admission-failures.mjs::an_introspection_token_the_worker_cannot_read_is_refused |
introspection-token-invalid | admitting the caller of POST /api/v1/credential-operations | the worker's introspection token {path} is {shape}, and a token is one line of no spaces | {path}: {bytes} bytes, {shape}; the value is not shown | have Stado issue the worker's introspection token again (`stado credentials token mint` for consumer weles-credential-admission writes ~/.stado/weles-credential-admission-skarbiec-token, owner-only), then send the request again | config, exit 1, HTTP 503 | tests/credentials/admission-failures.mjs::an_introspection_token_holding_spaces_is_refused |
skarbiec-route-unresolved | admitting the caller of POST /api/v1/credential-operations | no Skarbiec is reached to introspect the bearer: the secrets alternative refused ({lower}) | {answer} | {fix} | config, exit 1, HTTP 503 | tests/credentials/admission-failures.mjs::a_worker_whose_stado_names_no_skarbiec_refuses |
introspection-unreached | asking Skarbiec at {endpoint} to introspect the bearer | Skarbiec token introspection at {endpoint} failed: the connection did not answer ({errno}) | POST {endpoint}/v1/tokens/introspect: {answer} | `stado capabilities resolve secrets --consumer weles --json` names the Skarbiec route; start that Skarbiec or correct its route, then send the request again | infra_down, exit 69, HTTP 503, retryable | tests/credentials/authority-failures.mjs::an_introspection_skarbiec_does_not_answer_is_named |
introspection-unread | reading what Skarbiec at {endpoint} answered the introspection | Skarbiec token introspection at {endpoint} failed: its HTTP {status} answer is not one bounded JSON object, so it is not Skarbiec's introspection | HTTP {status} {type}; reader: {answer} | `stado capabilities resolve secrets --consumer weles --json` names the Skarbiec route; a route reaching something other than Skarbiec is corrected there | infra_down, exit 69, HTTP 503, retryable | tests/credentials/authority-failures.mjs::an_introspection_answer_that_is_not_json_is_named |
introspection-refused | asking Skarbiec at {endpoint} to introspect the bearer as weles-credential-admission | Skarbiec refused weles-credential-admission token introspection with HTTP {status}: it does not admit the worker's own token | POST {endpoint}/v1/tokens/introspect: HTTP {status}; Skarbiec: {said} | {fix}; or have Stado issue the worker's introspection token again (`stado credentials token mint` for consumer weles-credential-admission writes ~/.stado/weles-credential-admission-skarbiec-token, owner-only), then send the request again | auth, exit 1, HTTP 503 | tests/credentials/authority-failures.mjs::an_introspection_skarbiec_refuses_is_named |
bearer-inactive | admitting the caller of POST /api/v1/credential-operations | bearer is not active: Skarbiec answers active {active} for it, so it expired or was revoked | Skarbiec at {endpoint}: active {active}, consumer {consumer} | ask the operator of the calling product for a bearer Skarbiec issues for audience weles with call:weles#credential-operations, and send it as the one Authorization: Bearer line | auth, exit 1, HTTP 401 | tests/credentials/authority-failures.mjs::a_bearer_skarbiec_answers_inactive_is_refused |
audience-other | admitting the caller of POST /api/v1/credential-operations | bearer requires audience weles and call:weles#credential-operations, and Skarbiec answers audience {audience} | consumer {consumer}, audience {audience} | ask the operator of the calling product for a bearer Skarbiec issues for audience weles with call:weles#credential-operations, and send it as the one Authorization: Bearer line | auth, exit 1, HTTP 403 | tests/credentials/authority-failures.mjs::a_bearer_of_another_audience_is_refused |
consumer-unnamed | admitting the caller of POST /api/v1/credential-operations | bearer requires audience weles and call:weles#credential-operations, and Skarbiec names no consumer for it ({consumer}), so no record can say who asked | audience {audience}, consumer {consumer} | ask the operator of the calling product for a bearer Skarbiec issues for audience weles with call:weles#credential-operations, and send it as the one Authorization: Bearer line | auth, exit 1, HTTP 403 | tests/credentials/authority-failures.mjs::a_bearer_naming_no_consumer_is_refused |
capability-missing | admitting the caller of POST /api/v1/credential-operations | bearer requires audience weles and call:weles#credential-operations, and the scopes Skarbiec answers do not include it | consumer {consumer}: scopes {scopes} | ask the operator of the calling product for a bearer Skarbiec issues for audience weles with call:weles#credential-operations, and send it as the one Authorization: Bearer line | auth, exit 1, HTTP 403 | tests/credentials/authority-failures.mjs::a_bearer_without_the_credential_scope_is_refused |
method-refused | {method} /api/v1/credential-operations | the credential-operations route takes POST only, and the request is {method} | method {method} | POST the credential-operation request as one JSON object | refused, exit 2, HTTP 405 | tests/credentials/admission-failures.mjs::a_credential_route_asked_with_get_is_refused |
request-unparsed | reading the credential-operation request | credential request must be one bounded JSON object, and the weles-client reader refused the body | weles-client reader: {answer}; the request stated Content-Type {contentType} and Content-Length {contentLength} | send the request as the weles-client credential request builder writes it | refused, exit 2, HTTP 400 | tests/credentials/contract-failures.mjs::a_credential_request_that_is_not_json_is_refused |
request-invalid | reading the credential-operation request | the request is JSON, and the weles-client validator refused its fields | weles-client validator: {answer}; request {request}, version {version}, fields {members}; Content-Type {contentType} | send the fields the weles-client credential request contract names, as its builder writes them | refused, exit 2, HTTP 400 | tests/credentials/contract-failures.mjs::a_credential_request_of_another_shape_is_refused |
dry-run-refused | checking credential request {request} as a dry run | the dry run's acquisition refused the request before anything ran | acquisition: {answer} | correct what the acquisition names, then send the dry run again; nothing was recorded | refused, exit 2, HTTP 400 | tests/credentials/contract-failures.mjs::a_dry_run_its_acquisition_refuses_is_named |
mode-refused | reading the credential-operation request | self-hosted credential admission accepts submit and status, not resume | mode {mode} for request {request} | send mode submit to start the operation, or status with its action log id to read it | refused, exit 2, HTTP 409 | tests/credentials/contract-failures.mjs::a_credential_request_to_resume_is_refused |
admission-stopping | admitting credential request {request} | Weles is draining credential operations: the worker is shutting down and admits no new one | {running} operation(s) still finishing | send the request again once `weles worker status` answers the restarted worker | infra_down, exit 69, HTTP 503, retryable | tests/credentials/contract-failures.mjs::a_credential_request_while_the_worker_stops_is_refused |
contract-undeclared | matching credential request {request} ({credential}, provider {provider}) to its declared acquisition | No acquisition is declared for {credential} (provider {provider}); a managed password is declared in the deployment's managed password declarations, and {source} | credential {credential}, provider {provider}; {source} | send the item, field and writer the deployment's managed password declaration names (`weles errors show credentials` lists the declared causes); the declaration is the file WELES_MANAGED_PASSWORD_CONTRACTS_FILE names | refused, exit 1, HTTP 409 | tests/credentials/contract-failures.mjs::a_credential_no_acquisition_declares_is_refused |
contract-unresolved | matching credential request {request} ({credential}, provider {provider}) to its declared acquisition | credential item, field, and writer must match the exact Weles acquisition contract, and the definition names secret {secret}, which no credential contract resolves | definition secret {secret} | send the item, field and writer the deployment's managed password declaration names (`weles errors show credentials` lists the declared causes); the declaration is the file WELES_MANAGED_PASSWORD_CONTRACTS_FILE names | refused, exit 1, HTTP 409 | tests/credentials/contract-failures.mjs::a_definition_whose_secret_no_contract_resolves_is_refused |
contract-other | matching credential request {request} ({credential}, provider {provider}) to its declared acquisition | credential item, field, and writer must match the exact Weles acquisition contract, and the request names another {what} | requested {requested}; declared {declared} | send the item, field and writer the deployment's managed password declaration names (`weles errors show credentials` lists the declared causes); the declaration is the file WELES_MANAGED_PASSWORD_CONTRACTS_FILE names | refused, exit 1, HTTP 409 | tests/credentials/contract-failures.mjs::a_credential_of_another_field_or_writer_is_refused |
directory-mismatch | matching credential request {request} ({credential}, provider {provider}) to its declared acquisition | directory identity must match the requested provider: {why} | provider {provider}, directory {directory} | name a directory (tenant, principal object and UPN) only for provider microsoft_entra, with directory.provider microsoft_entra; omit it for every other provider | refused, exit 1, HTTP 409 | tests/credentials/contract-failures.mjs::a_directory_of_another_provider_is_refused |
origin-mismatch | matching credential request {request} ({credential}, provider {provider}) to its declared acquisition | signup origin differs from the registered credential capture origin | requested {requested}, registered {declared} | send signup_origin {declared}, or none | refused, exit 1, HTTP 409 | tests/credentials/contract-failures.mjs::a_credential_of_another_signup_origin_is_refused |
store-unmade | creating the credential admission records directory {path} | the system refused to create it ({errno}), so no credential operation can be recorded | {path}: {errno}; system: {answer} | give the account the worker runs as write access to {path}, then `weles worker restart` | config, exit 1, HTTP 503 | tests/credentials/record-failures.mjs::a_records_directory_that_cannot_be_made_is_named |
record-unsafe | reading the record of credential request {request} | credential admission record is not an owner-only regular file: {path} {reason} | {path}: {reason} | move {path} aside (the worker never removes what it did not write) and send the request again; `weles worker status` shows the worker that answers | config, exit 1, HTTP 503 | tests/credentials/record-failures.mjs::a_record_others_can_read_is_refused |
record-unread | reading the record of credential request {request} | the system refused to {step} {path} ({errno}) | {path}: {errno}; system: {answer} | give the account the worker runs as read access to {path} (`ls -l {path}`), then send the request again | config, exit 1, HTTP 503 | tests/credentials/record-failures.mjs::a_record_the_worker_cannot_read_is_named |
record-not-json | reading the record of credential request {request} | credential admission record {path} is not one JSON document: the worker writes it whole, so it was truncated or written by something else | {path}: {bytes} bytes; parser: {answer} | move {path} aside (the worker never removes what it did not write) and send the request again; `weles worker status` shows the worker that answers | config, exit 1, HTTP 503 | tests/credentials/record-failures.mjs::a_record_that_is_not_json_is_refused |
record-identity-invalid | reading the record of credential request {request} | credential admission record identity is invalid: {path} holds {what} {found}, not {expected} | {path}: {what} {found} | move {path} aside (the worker never removes what it did not write) and send the request again; `weles worker status` shows the worker that answers | config, exit 1, HTTP 503 | tests/credentials/record-failures.mjs::a_record_of_another_request_is_refused |
record-phase-unknown | reading the record of credential request {request} | credential admission record {path} names the phase {phase}; the phases are {phases} | {path}: phase {phase} | compare `weles worker version` with the release that wrote it; a record of another release is read by that release, or moved aside | config, exit 1, HTTP 503 | tests/credentials/record-failures.mjs::a_record_of_an_unknown_phase_is_refused |
request-consumer-other | reading the record of credential request {request} | credential request id is already bound to another identity or request: consumer {recorded} admitted it, and {consumer} asks | recorded consumer {recorded}, asking consumer {consumer} | read the operation with mode status and its action log id; a new operation needs a new request id | refused, exit 1, HTTP 409 | tests/credentials/record-failures.mjs::a_request_id_another_consumer_holds_is_refused |
request-fingerprint-other | reading the record of credential request {request} | credential request id is already bound to another identity or request: the request differs from the one admitted under it | recorded fingerprint {recorded}, this request {fingerprint} | read the operation with mode status and its action log id; a new operation needs a new request id | refused, exit 1, HTTP 409 | tests/credentials/record-failures.mjs::a_request_id_reused_for_another_request_is_refused |
status-task-other | reading the record of credential request {request} | credential status names another action log: request {request} runs as {recorded} | action log asked {asked}, recorded {recorded} | ask the status with action_log_id {recorded} | refused, exit 1, HTTP 409 | tests/credentials/record-failures.mjs::a_status_naming_another_action_log_is_refused |
request-not-found | reading the record of credential request {request} | credential request was not admitted here: no record of it is under {root} | no {path} | submit the request first, or ask the worker that admitted it | not_found, exit 1, HTTP 404 | tests/credentials/record-failures.mjs::a_status_of_a_request_never_admitted_is_not_found |
record-id-invalid | naming the record of credential request {request} | invalid credential request id: it is not 64 hex digits, and the validator admits only those, so this is a defect of the release | request id {request} | report it with `weles worker version`; nothing was recorded | unknown, exit 1, HTTP 500 | tests/credentials/record-failures.mjs::a_record_of_an_invalid_id_is_named |
phase-move-invalid | recording credential operation {request} | credential operation {request} cannot move from {from} to {to}; from {from} it moves to {allowed}: a defect of the release | phase {from}, asked {to} | report it with `weles worker version` and the record; the record keeps phase {from} | unknown, exit 1, HTTP 500 | tests/credentials/record-failures.mjs::a_phase_move_the_record_does_not_allow_is_named |
trajectories-several | queuing credential operation {request} | one credential operation cannot enqueue multiple trajectories, and its acquisition asked for {action} after {first}: a defect of the acquisition catalog | first {first}, then {action} | report it with `weles worker version`; nothing ran | unknown, exit 1, HTTP 502 | tests/credentials/record-failures.mjs::an_acquisition_enqueuing_two_trajectories_is_named |
trajectory-none | queuing credential operation {request} | credential acquisition queued no executable trajectory: it answered operation_queued without asking for one | acquisition answered {status} | report it with `weles worker version`; nothing ran | unknown, exit 1, HTTP 502 | tests/credentials/record-failures.mjs::an_acquisition_queuing_nothing_is_named |
admission-failed | admitting credential operation {request} through its acquisition | the acquisition refused before any trajectory ran ({lower}) | {answer} | {fix} | infra_down, exit 69, HTTP 502, retryable | tests/credentials/record-failures.mjs::an_acquisition_that_refuses_settles_the_operation |
execution-not-started | starting credential operation {request} | Weles stopped before starting the credential operation | the worker began stopping with the operation queued | submit the operation again under a new request id once the worker answers | infra_down, exit 69, HTTP 502, retryable | tests/credentials/record-failures.mjs::an_operation_queued_while_the_worker_stops_is_settled |
execution-failed | running trajectory {action} for credential operation {request} | the run failed before it answered ({lower}) | {answer} | {fix} | infra_down, exit 69, HTTP 502, retryable | tests/credentials/record-failures.mjs::an_operation_whose_run_fails_is_settled |
execution-interrupted | answering credential operation {request} | Weles {when} before recording a final credential result; the operation was not replayed | phase {phase}, no executor for it here | read the provider side for what the run changed, then submit a new operation under a new request id | infra_down, exit 69, HTTP 502, retryable | tests/credentials/record-failures.mjs::an_operation_left_unfinished_by_a_restart_is_settled |
result-unpersisted | recording the final reply of credential operation {request} | the record could not be written ({lower}), so the reply is only in the worker log | {answer} | free space and write access under the run results directory; the next status answers execution-interrupted | infra_down, exit 69, HTTP 502, retryable | tests/credentials/record-failures.mjs::an_operation_whose_reply_cannot_be_recorded_is_logged |
run-file-not-json | reading what run {run} stated in {path} | {path} is not JSON: the run writes it whole, so it was cut off or written by something else | {path}: {bytes} bytes; parser: {answer} | `weles runs show {run}` shows the run; submit the operation again under a new request id | infra_down, exit 69, HTTP 502, retryable | tests/credentials/record-failures.mjs::a_run_statement_that_is_not_json_is_named |
run-file-unread | reading what run {run} stated in {path} | the system refused to read {path} ({errno}) | {path}: {errno}; system: {answer} | give the account the worker runs as read access to {path}, then read the status again | infra_down, exit 69, HTTP 502, retryable | tests/credentials/record-failures.mjs::a_run_statement_the_worker_cannot_read_is_named |
enrol-field | reading the enrolment request {request} | an enrolment {mode} names {field} as {expects}, and the request does not | {field}: {found} | run it as skarbiec credential enrol role:<role> --provider google --order <order>, which writes every field this route reads | refused, exit 2, HTTP 400 | tests/credentials/enrolment-failures.mjs::an_enrolment_missing_a_field_is_refused_naming_it |
enrol-provider | enrolling an authenticator for {credential} at {provider} | Weles enrols an authenticator at {providers} only, and the request names {provider} | provider {provider} | keep the seed of an account elsewhere with skarbiec totp set from the setup key the site shows | refused, exit 1, HTTP 409 | tests/credentials/enrolment-failures.mjs::an_enrolment_at_another_provider_is_refused |
enrol-unknown | {doing} {request} | this worker admitted no enrolment under request {request}, so there is no run to answer or read | enrolment records under {root}: none for {request} | start it with skarbiec credential enrol role:<role> --provider google --order <order> on the worker that will run it; `weles worker status` names the worker that answers | not_found, exit 1, HTTP 404 | tests/credentials/enrolment-failures.mjs::answering_an_enrolment_never_admitted_is_refused |
enrol-not-waiting | answering {answer} to enrolment {request} | the run is {phase} and waits for {waiting}, so the answer {answer} has nothing to answer | run {run}: phase {phase}, waiting for {waiting} | read where it stands with skarbiec credential enrol role:<role> --status --wait, and answer once it waits for you | refused, exit 1, HTTP 409 | tests/credentials/enrolment-failures.mjs::an_answer_the_run_does_not_wait_for_is_refused |
enrol-gate-unmade | opening the place enrolment {request} waits for the owner at | the system refused to listen at {path} ({errno}), so the run could never hear the owner say ready and nothing was started | {path}: {errno}; system: {answer} | give the account the worker runs as write access to {root}; `weles doctor` names the run results directory | config, exit 1, HTTP 503 | tests/credentials/enrolment-failures.mjs::an_enrolment_whose_gate_cannot_open_is_refused |
enrol-unanswered | enrolment {request} | the run ended while it waited for the owner to say {waiting}, so nothing was sent to the provider after that point | run {run} ended waiting for {waiting} | the order was spent; the vault owner issues a new one in Skarbiec Desktop and starts the enrolment again when the phone is in hand | refused, exit 1, HTTP 409 | tests/credentials/enrolment-failures.mjs::a_run_that_ends_waiting_is_named |
constraints-absent | reading the credential storage constraints of this run | credential storage constraints are absent from this task: it was not started as a credential operation | neither WELES_CREDENTIAL_CONSTRAINTS nor GENERIC_TASK_CONSTRAINTS is set for the run | start the credential operation again through POST /api/v1/credential-operations, which writes the constraints the acquisition declares; a run started otherwise stores no credential | config, exit 1, HTTP 503 | tests/credentials/capture-failures.mjs::a_run_without_constraints_stores_nothing |
constraints-repeated | reading the credential storage constraints of this run | multiple credential constraint sources are not allowed: WELES_CREDENTIAL_CONSTRAINTS and GENERIC_TASK_CONSTRAINTS are both set, so which acquisition the run serves is ambiguous | both WELES_CREDENTIAL_CONSTRAINTS and GENERIC_TASK_CONSTRAINTS are set; the values are not shown | start the credential operation again through POST /api/v1/credential-operations, which writes the constraints the acquisition declares; a run started otherwise stores no credential | config, exit 1, HTTP 503 | tests/credentials/capture-failures.mjs::a_run_with_two_constraint_sources_is_refused |
constraints-not-json | reading the credential storage constraints of this run | credential storage constraints are not JSON | {bytes} bytes; parser: {answer} | start the credential operation again through POST /api/v1/credential-operations, which writes the constraints the acquisition declares; a run started otherwise stores no credential | config, exit 1, HTTP 503 | tests/credentials/capture-failures.mjs::constraints_that_are_not_json_are_refused |
constraints-not-object | reading the credential storage constraints of this run | credential storage constraints are not an object | the constraints are {kind} | start the credential operation again through POST /api/v1/credential-operations, which writes the constraints the acquisition declares; a run started otherwise stores no credential | config, exit 1, HTTP 503 | tests/credentials/capture-failures.mjs::constraints_that_are_no_object_are_refused |
storage-unauthorized | reading the credential storage constraints of this run | credential storage is not authorized for this task: store_secret_target is {target}, and only skarbiec stores | store_secret_target {target} | start the credential operation again through POST /api/v1/credential-operations, which writes the constraints the acquisition declares; a run started otherwise stores no credential | config, exit 1, HTTP 503 | tests/credentials/capture-failures.mjs::constraints_storing_elsewhere_are_refused |
secret-unnamed | reading the credential storage constraints of this run | credential storage target is not in the exact Weles acquisition allowlist: the constraints name no secret | secret {secret} | start the credential operation again through POST /api/v1/credential-operations, which writes the constraints the acquisition declares; a run started otherwise stores no credential | config, exit 1, HTTP 503 | tests/credentials/capture-failures.mjs::constraints_naming_no_secret_are_refused |
secret-uncontracted | reading the credential storage constraints of this run | credential storage target is not in the exact Weles acquisition allowlist: no acquisition contract is declared for secret {secret} | secret {secret} | start the credential operation again through POST /api/v1/credential-operations, which writes the constraints the acquisition declares; a run started otherwise stores no credential | config, exit 1, HTTP 503 | tests/credentials/capture-failures.mjs::constraints_of_an_undeclared_secret_are_refused |
target-other | reading the credential storage constraints of this run | credential storage target is not in the exact Weles acquisition allowlist: the constraints name another {what} than the contract of {secret} | constrained {constrained}; contract {declared} | start the credential operation again through POST /api/v1/credential-operations, which writes the constraints the acquisition declares; a run started otherwise stores no credential | config, exit 1, HTTP 503 | tests/credentials/capture-failures.mjs::constraints_of_another_item_or_field_are_refused |
field-unclassed | reading the credential storage constraints of this run | credential storage field {field} has no field class: it is one of {classes} | contract field {field} of secret {secret} | start the credential operation again through POST /api/v1/credential-operations, which writes the constraints the acquisition declares; a run started otherwise stores no credential | config, exit 1, HTTP 503 | tests/credentials/capture-failures.mjs::a_contract_field_of_no_class_is_refused |
account-email-missing | reading the credential storage constraints of this run | password credential storage requires an exact account email, and the constraints carry {email} | account_email {email} for secret {secret} | start the credential operation again through POST /api/v1/credential-operations, which writes the constraints the acquisition declares; a run started otherwise stores no credential | config, exit 1, HTTP 503 | tests/credentials/capture-failures.mjs::a_password_without_its_account_is_refused |
request-unnamed | reading the credential storage constraints of this run | credential storage requires an exact request id and operation: {what} | request_id {request}, operation {operation}; the operations are {operations} | start the credential operation again through POST /api/v1/credential-operations, which writes the constraints the acquisition declares; a run started otherwise stores no credential | config, exit 1, HTTP 503 | tests/credentials/capture-failures.mjs::constraints_without_their_request_are_refused |
capture-target-empty | storing a credential | credential target is empty: the step names no element, label or textbox the credential is shown in | target {target} | name the element the page shows the credential in (a selector, a label or a textbox name) in the step | refused, exit 2, HTTP 403 | tests/credentials/capture-failures.mjs::a_capture_naming_no_target_is_refused |
capture-field-class-other | storing the credential the page shows in Skarbiec item {item} field {field} | credential field class does not match the exact Weles acquisition allowlist: the step stores a {asked}, and the contract a {declared} | step field class {asked}; contract field class {declared} | store the credential with the field class the acquisition contract names ({declared}) | refused, exit 2, HTTP 403 | tests/credentials/capture-failures.mjs::a_capture_of_another_field_class_is_refused |
capture-origin-invalid | storing the credential the page shows in Skarbiec item {item} field {field} | credential capture origin is not one absolute https origin: {origin} | capture origin {origin} ({from}) | open the provider page over https, or declare its signup origin in the acquisition, then start the operation again | config, exit 1, HTTP 503 | tests/credentials/capture-failures.mjs::a_capture_from_a_page_not_on_https_is_refused |
capture-origin-unopened | storing the credential the page shows in Skarbiec item {item} field {field} | credential source origin mismatch: no open page is on {origin}, so nothing the run shows may be captured | {count} open page(s): {origins} | go to the provider page on {origin} before the capture step | config, exit 1, HTTP 503 | tests/credentials/capture-failures.mjs::a_capture_with_no_page_on_its_origin_waits |
capture-shape-unshown | storing the credential the page shows in Skarbiec item {item} field {field} | credential element was not found or did not match the required secret shape: {frames} frame(s) on {origin} show no value of the shape secret {secret} takes | target {target}; {frames} frame(s) searched, {seen} candidate value(s) of another shape; no value is shown | reach the provider page that shows the credential, and name its element as the step target | config, exit 1, HTTP 503 | tests/credentials/capture-failures.mjs::a_capture_whose_page_shows_no_credential_waits |
capture-record-unwritten | recording the capture of request {request} in {path} | the system refused to write the capture record ({errno}); the credential is stored in Skarbiec, and the worker answers the run as stating no capture | {path}: {errno}; system: {answer} | give the run write access to its recordings directory, then read the operation status: Skarbiec already holds the stored value | infra_down, exit 69, HTTP 503, retryable | tests/credentials/capture-failures.mjs::a_capture_whose_record_cannot_be_written_is_named |
declarations#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
declaration-unreadable | reading the {kind} declaration {path} | the declaration cannot be read: it is absent from this release, or this user may not read it | {answer} | reinstall the Weles release (`weles doctor` names what the installed release lacks), or make {path} readable | config, exit 1, HTTP 503 | tests/declarations/failures.mjs::an_absent_declaration_is_named |
declaration-not-json | reading the {kind} declaration {path} | the declaration is not JSON | {path} ({bytes} bytes): {answer} | correct the {kind} declaration {path} in the Weles checkout and release it again | config, exit 1, HTTP 503 | tests/declarations/failures.mjs::a_declaration_that_is_not_json_is_named |
declaration-shape | reading the {kind} declaration {path} | the declaration is not one object of schema {schema} holding a non-empty {member} array | found {found} | correct the {kind} declaration {path} in the Weles checkout and release it again | config, exit 1, HTTP 503 | tests/declarations/failures.mjs::a_declaration_of_another_shape_is_named |
entry-incomplete | checking {kind} entry {index} of {path} | the entry is not an object naming {fields} as non-empty text | entry {index}: {found} | give entry {index} every one of {fields}; correct the {kind} declaration {path} in the Weles checkout and release it again | config, exit 1, HTTP 503 | tests/declarations/failures.mjs::an_entry_without_its_fields_is_named |
name-invalid | checking {kind} {name} of {path} | its name is not <platform>.<verb> in lower case, or not the platform and verb the entry states | entry {index} names {name} for platform {platform} and verb {verb} | name the entry {expected}, or correct its platform and verb; correct the {kind} declaration {path} in the Weles checkout and release it again | config, exit 1, HTTP 503 | tests/declarations/failures.mjs::an_entry_whose_name_is_not_its_platform_and_verb_is_named |
name-repeated | checking {kind} {name} of {path} | two entries declare the same name, so which one a run means is unknown | {name} is entries {first} and {second} | keep one of entries {first} and {second}; correct the {kind} declaration {path} in the Weles checkout and release it again | config, exit 1, HTTP 503 | tests/declarations/failures.mjs::a_name_declared_twice_is_named |
trajectory-outside | checking {kind} {name} of {path} | it names a trajectory outside src/trajectories/, where every reviewed trajectory lives | trajectory {trajectory} | name a reviewed trajectory under src/trajectories/ without ..; correct the {kind} declaration {path} in the Weles checkout and release it again | config, exit 1, HTTP 503 | tests/declarations/failures.mjs::an_entry_naming_a_trajectory_outside_the_reviewed_ones_is_named |
trajectory-missing | checking {kind} {name} of {path} | the reviewed trajectory it names is not in this release | {trajectory} does not exist under {root} | add the trajectory to the release, or name one that exists; correct the {kind} declaration {path} in the Weles checkout and release it again | config, exit 1, HTTP 503 | tests/declarations/failures.mjs::an_entry_naming_a_missing_trajectory_is_named |
observation-invalid | checking observation {name} of {path} | what it reads, its origin or its dwell is not one an observation can run with: {why} | reads {reads}, origin {origin}, dwell {dwell} | make it read one of feed, notifications, search_results (an origin carrying the query placeholder) or profile (one carrying the handle placeholder), from an https origin whose placeholders are among query, handle and subreddit, for a whole number of scrolls; correct the {kind} declaration {path} in the Weles checkout and release it again | config, exit 1, HTTP 503 | tests/declarations/failures.mjs::an_observation_that_cannot_run_is_named |
document-invalid | checking document {name} of {path} | its origin, its media type or its login role is not one a document run can fetch with: {why} | origin {origin}, media type {media_type}, login role {login_role} | give it an https origin, one of the media types {media_types} and a Skarbiec role (lowercase letters, digits and hyphens) as its login_role; correct the {kind} declaration {path} in the Weles checkout and release it again | config, exit 1, HTTP 503 | tests/declarations/documents.mjs::a_document_that_cannot_be_fetched_is_named |
name-unnamed | admitting a {action} run | the run names its {kind} by something other than text | {kind}: {given} | name a declared {kind} as <platform>.<verb>; the declaration {path} lists them | refused, exit 2, HTTP 403 | tests/declarations/failures.mjs::a_run_naming_its_entry_by_no_text_is_refused |
name-undeclared | admitting a {action} run of {kind} {name} | no entry of the declaration has that name | {count} declared, e.g. {examples} | name one of the {kind}s {path} declares, or declare {name} there and release it | refused, exit 2, HTTP 403 | tests/declarations/failures.mjs::a_run_naming_an_undeclared_entry_is_refused |
both-named | admitting a {action} run | it names a declared {kind} and {other}, two answers to what the run does | {kind}: {name}; {other}: {given} | leave out {other}, or leave out {kind} | refused, exit 2, HTTP 403 | tests/declarations/failures.mjs::a_run_naming_an_entry_and_its_own_target_is_refused |
placeholder-unfilled | admitting observation {name} | its origin {origin} carries the placeholder {placeholder}, and the run states no text for it | {placeholder}: {given} | state {placeholder} with the {reads} the run reads | refused, exit 2, HTTP 403 | tests/declarations/failures.mjs::an_observation_without_its_placeholder_is_refused |
saved-trajectory-absent | admitting generic_saved_task run of saved trajectory {id} | the run was queued without the saved trajectory it replays | params.trajectory is {found} | queue the run through Weles Web, which carries the saved trajectory in params.trajectory | refused, exit 2, HTTP 403 | tests/declarations/failures.mjs::a_saved_run_without_its_trajectory_is_refused |
saved-trajectory-other | admitting generic_saved_task run of saved trajectory {id} | the saved trajectory the run carries is another one than it names | params.trajectory.id is {carried} | queue the run again through Weles Web, so it carries trajectory {id} | refused, exit 2, HTTP 403 | tests/declarations/failures.mjs::a_saved_run_carrying_another_trajectory_is_refused |
design#
| Cause | What failed | Why | Observed | Fix | Code, exit, HTTP | Test |
|---|---|---|---|---|---|---|
export-file-unread | reading the design export request {request} | the {label} {path} could not be read ({errno}): it is absent, a directory, or the user may not read it | {problem}; nothing was asked of Git, the vault or Figma | place the {label} at {path}, or correct the path the request names | config, exit 1, HTTP 503 | tests/design/exports/export-request-failures.mjs::an_inventory_that_cannot_be_read_is_named |
export-file-not-json | reading the design export request {request} | the {label} {path} is not JSON | {bytes} bytes; the parser said: {problem} | write the {label} as one JSON object | config, exit 1, HTTP 503 | tests/design/exports/export-request-failures.mjs::a_request_that_is_not_json_is_named |
export-field-empty | reading the design export request {request} | design export request requires a non-empty {field}, and {file} gives {given} | {field}: {given} | write {request} as {"inventory", "checkout", "remote", "branch", "credential": {"consumer", "item", "field", "scopeFile", "workloadId", "signingKeyFile"}[, "output"]}, paths relative to the request | config, exit 1, HTTP 503 | tests/design/exports/export-request-failures.mjs::a_request_without_a_field_is_named |
export-path-absent | reading the design export request {request} | the {field} the request names does not resolve to an existing path ({errno}) | {field}: {path}, resolved against {root} | create {path}, or name an existing one relative to the request file | config, exit 1, HTTP 503 | tests/design/exports/export-request-failures.mjs::a_checkout_that_does_not_exist_is_named |
export-remote-url | reading the design export request {request} | design export remote must name a configured Git remote, not a URL: a URL would push where no checkout configured | remote {remote} | name the remote as `git -C {checkout} remote` lists it (such as origin) | config, exit 1, HTTP 503 | tests/design/exports/export-request-failures.mjs::a_remote_that_is_a_url_is_refused |
export-output-outside | reading the design export request {request} | design export output must be a directory inside the checkout, relative to it and outside .git | output {output}, normalized to {managed} | name the managed directory relative to the checkout (such as figma or libraries/<name>/figma) | config, exit 1, HTTP 503 | tests/design/exports/export-request-failures.mjs::an_output_outside_the_checkout_is_refused |
export-input-inside-output | reading the design export request {request} | design export {label} must be outside the managed output directory {managed}, which the export replaces whole | {label} {path} lies in {output} | move {path} out of {managed}, or name another output | config, exit 1, HTTP 503 | tests/design/exports/export-request-failures.mjs::an_input_inside_the_output_is_refused |
export-files-empty | reading the design export request {request} | the Figma inventory {path} lists no company file, so there is nothing to export | companyFiles: {found} | list each Figma file as {"name", "url"} in companyFiles of {path} | config, exit 1, HTTP 503 | tests/design/exports/export-request-failures.mjs::an_inventory_without_files_is_refused |
export-file-url-unparsed | reading the design export request {request} | {where}.url is not a URL | {where}.url: {url} | give the file's address as Figma shows it (https://www.figma.com/<kind>/<key>/<title>) | config, exit 1, HTTP 503 | tests/design/exports/export-request-failures.mjs::a_file_url_that_is_no_url_is_refused |
export-file-url-foreign | reading the design export request {request} | {where}.url must be an HTTPS Figma file URL (https://www.figma.com/<kind>/<key>/...): {why} | {where}.url: {url} | give the file's address as Figma shows it, without a user or password | config, exit 1, HTTP 503 | tests/design/exports/export-request-failures.mjs::a_file_url_of_another_host_is_refused |
export-file-repeated | reading the design export request {request} | Figma inventory repeats file {key}, so its export would be written twice | {first} and {where} both name file {key} | keep one entry for file {key} in {path} | config, exit 1, HTTP 503 | tests/design/exports/export-request-failures.mjs::an_inventory_repeating_a_file_is_refused |
export-git-unstarted | {step} in {checkout} | git could not be started ({errno}): it is not installed or not on PATH, or {checkout} is not a directory git can enter | `{command}`: {problem}; PATH {path}; {left} | install git or put it on PATH (`command -v git` shows which one runs), then run the export again | config, exit 1, HTTP 503 | tests/design/exports/export-checkout-failures.mjs::an_export_without_git_is_refused |
export-git-killed | {step} in {checkout} | git was ended by {signal} before it answered: the host stopped it, it ran out of memory, or the export was interrupted | `{command}` ended on {signal}; it said: {said}; {left} | run the export again | infra_down, exit 69, HTTP 503, retryable | tests/design/exports/export-checkout-failures.mjs::a_git_ended_by_a_signal_is_named |
export-checkout-not-repository | checking the checkout {checkout} a design export publishes into | the checkout is no git repository, so nothing can be committed or pushed from it | `{command}` in {checkout} exited {status}: {said}; nothing was generated or changed | name in the request the root of the clone the export publishes into (`git -C <dir> rev-parse --show-toplevel` shows a clone's root) | config, exit 1, HTTP 503 | tests/design/exports/export-checkout-failures.mjs::a_checkout_that_is_no_repository_is_refused |
export-checkout-unreadable | checking the checkout {checkout} a design export publishes into | the checkout holds {dotgit} and git still refused to read it as a repository: another user owns it (git's safe.directory), or its .git is damaged | `{command}` in {checkout} exited {status}: {said}; nothing was generated or changed | `git -C {checkout} status` shows what git refuses; `ls -ld {dotgit}` shows its owner; repair it, or name a clone this user owns | config, exit 1, HTTP 503 | tests/design/exports/export-checkout-failures.mjs::a_repository_git_refuses_to_read_is_named |
export-checkout-not-root | checking the checkout {checkout} a design export publishes into | the checkout is a directory inside the repository rooted at {root}, not its root, so the managed directory would be read against another tree | git rev-parse --show-toplevel answered {root}; nothing was generated or changed | name {root} as the checkout in the request, and the managed directory relative to it | config, exit 1, HTTP 503 | tests/design/exports/export-checkout-failures.mjs::a_checkout_below_its_repository_root_is_refused |
export-checkout-unborn | checking the checkout {checkout} a design export publishes into | branch {branch} of the checkout holds no commit yet, so there is nothing the export can publish on top of | `{command}` in {checkout} exited {status}: {said}; nothing was generated or changed | make the first commit on {branch} and push it to the remote (`git -C {checkout} commit --allow-empty -m start`), then run the export again | config, exit 1, HTTP 503 | tests/design/exports/export-checkout-failures.mjs::a_checkout_without_a_commit_is_refused |
export-branch-invalid | checking the checkout {checkout} a design export publishes into | the request names branch {branch}, which is not a valid git branch name | `{command}` in {checkout} exited {status}: {said}; nothing was generated or changed | name the branch the export publishes to as git names it (`git check-ref-format --branch <name>`) | refused, exit 2, HTTP 403 | tests/design/exports/export-checkout-failures.mjs::an_invalid_branch_name_is_refused |
export-head-detached | checking the checkout {checkout} a design export publishes into | the checkout is on no branch (a detached HEAD), so nothing it commits can be pushed to {branch} | `{command}` in {checkout} exited {status}: {said}; HEAD is at {revision}; nothing was generated or changed | `git -C {checkout} switch {branch}`, then run the export again | config, exit 1, HTTP 503 | tests/design/exports/export-checkout-failures.mjs::a_checkout_on_no_branch_is_refused |
export-branch-other | checking the checkout {checkout} a design export publishes into | the checkout is on branch {current}, and the request publishes to {branch} | git symbolic-ref --short HEAD answered {current}; nothing was generated or changed | `git -C {checkout} switch {branch}`, or name {current} as the branch in the request | config, exit 1, HTTP 503 | tests/design/exports/export-checkout-failures.mjs::a_checkout_on_another_branch_is_refused |
export-remote-unknown | checking the checkout {checkout} a design export publishes into | the checkout has no remote {remote} | `{command}` in {checkout} exited {status}: {said}; its remotes: {remotes}; nothing was generated or changed | name one of the remotes the request's checkout has, or `git -C {checkout} remote add {remote} <url>` | config, exit 1, HTTP 503 | tests/design/exports/export-checkout-failures.mjs::a_remote_the_checkout_lacks_is_refused |
export-remote-urls | checking the checkout {checkout} a design export publishes into | the remote {remote} has {count} push URLs, and an export pushes to exactly one and reads it back | push URLs: {urls}; nothing was generated or changed | keep one push URL on {remote} (`git -C {checkout} remote set-url --push {remote} <url>`) | config, exit 1, HTTP 503 | tests/design/exports/export-checkout-failures.mjs::a_remote_with_two_push_urls_is_refused |
export-identity-unset | checking the checkout {checkout} a design export publishes into | git has no {who} identity in this checkout, so the export commit could not be made | `{command}` in {checkout} exited {status}: {said}; nothing was generated or changed | `git -C {checkout} config user.name <name>` and `git -C {checkout} config user.email <address>`, then run the export again | config, exit 1, HTTP 503 | tests/design/exports/export-checkout-failures.mjs::a_checkout_without_a_committer_is_refused |
export-checkout-dirty | {when} the checkout {checkout} | the checkout holds {count} uncommitted or untracked paths, which the export would not replace and could not tell from its own | git status --porcelain lists {count}: {paths}; {left} | commit, stash or remove them (`git -C {checkout} status` lists them), then run the export again | config, exit 1, HTTP 503 | tests/design/exports/export-checkout-failures.mjs::a_dirty_checkout_is_refused |
export-ignored-inside | {when} the checkout {checkout} | the managed directory {output} holds {count} ignored files, which the export would delete with the directory it replaces | git ls-files --others --ignored lists {count}: {paths}; {left} | move the ignored files out of {output}, or stop ignoring them and commit them elsewhere | config, exit 1, HTTP 503 | tests/design/exports/export-checkout-failures.mjs::ignored_files_in_the_managed_directory_are_refused |
export-checkout-moved | {when} the checkout {checkout} | the checkout's {what} changed while the export ran (from {before} to {now}): another program works in it | {what} {before} at the start, {now} now; {left} | let the other program finish, then run the export again | refused, exit 1, HTTP 403 | tests/design/exports/export-checkout-failures.mjs::a_checkout_that_moved_during_the_export_is_refused |
export-remote-unreachable | reading branch {branch} of remote {remote} ({url}) | the remote could not be read: its host does not answer, refuses the credentials, or holds no repository there | `{command}` in {checkout} exited {status}: {said}; nothing was generated or changed | `git -C {checkout} ls-remote {remote}` shows what the remote answers; run the export again once it answers | infra_down, exit 69, HTTP 503, retryable | tests/design/exports/export-checkout-failures.mjs::a_remote_that_does_not_answer_is_named |
export-remote-branch-absent | reading branch {branch} of remote {remote} ({url}) | the remote holds no branch {branch}, so the export cannot tell it publishes on top of what the remote holds | git ls-remote --exit-code answered no refs/heads/{branch} (exit {status}); nothing was generated or changed | `git -C {checkout} push {remote} HEAD:refs/heads/{branch}` creates it, then run the export again | config, exit 1, HTTP 503 | tests/design/exports/export-checkout-failures.mjs::a_remote_without_the_branch_is_refused |
export-remote-divergent | reading branch {branch} of remote {remote} ({url}) | the remote branch is at {remoteRevision}, not the checkout's {revision}, so a push would rewrite or lose commits | local {revision}, remote {remoteRevision}; nothing was generated or changed | `git -C {checkout} pull --ff-only {remote} {branch}` (or push the local commits), then run the export again | config, exit 1, HTTP 503 | tests/design/exports/export-checkout-failures.mjs::a_divergent_remote_branch_is_refused |
export-git-read-refused | {step} in {checkout} | git refused a read of a checkout it had read a moment before: another program changed, moved or locked the repository meanwhile | `{command}` in {checkout} exited {status}: {said}; {left} | `git -C {checkout} status` shows what git says now; once it answers, run the export again | config, exit 1, HTTP 503 | tests/design/exports/export-checkout-failures.mjs::an_index_broken_during_the_export_is_named |
export-output-not-directory | reading the managed directory {output} of {checkout} | the managed directory is a {kind}, not a directory, so it cannot be replaced by an export | {path} is a {kind}; nothing was generated or changed | move {path} aside, or name another managed directory in the request | config, exit 1, HTTP 503 | tests/design/exports/export-publish-failures.mjs::a_managed_path_that_is_a_file_is_refused |
export-inventory-unread | reading the managed directory {output} of {checkout} | the directory exists and its inventory.json could not be read ({errno}), so it cannot be told an earlier export | {path}: {problem}; nothing was generated or changed | name a managed directory that holds an earlier export or none yet; a directory of other files is not replaced | config, exit 1, HTTP 503 | tests/design/exports/export-publish-failures.mjs::a_managed_directory_without_an_inventory_is_refused |
export-inventory-not-json | reading the managed directory {output} of {checkout} | its inventory.json is not JSON, so the directory cannot be told an earlier export | {path}: {bytes} characters; the parser said: {problem}; nothing was generated or changed | `git -C {checkout} log -- {path}` shows who wrote it; restore the export's inventory or move the directory aside | config, exit 1, HTTP 503 | tests/design/exports/export-publish-failures.mjs::a_managed_inventory_that_is_not_json_is_refused |
export-inventory-foreign | reading the managed directory {output} of {checkout} | design export refuses {output}, a directory without an export inventory: its inventory.json holds no files list and generatedAt | {path} holds {members}; nothing was generated or changed | name the directory an earlier export wrote, or move {output} aside | config, exit 1, HTTP 503 | tests/design/exports/export-publish-failures.mjs::a_managed_directory_of_another_kind_is_refused |
export-build-not-directory | preparing the working directory {build} of {checkout} | .build is a {kind}, not a directory, and the export keeps its lock and working files there | {build} is a {kind}; nothing was generated or changed | move {build} aside, then run the export again | config, exit 1, HTTP 503 | tests/design/exports/export-publish-failures.mjs::a_build_path_that_is_a_file_is_refused |
export-build-not-ignored | preparing the working directory {build} of {checkout} | .build/ is not ignored by the checkout, so the export's working files would show as changes and could be committed | `{command}` in {checkout} exited {status}: {said}; nothing was generated or changed | add `.build/` to {checkout}/.gitignore and commit it, then run the export again | config, exit 1, HTTP 503 | tests/design/exports/export-publish-failures.mjs::a_checkout_that_does_not_ignore_build_is_refused |
export-lock-held | taking the export lock {lock} | another design export holds the lock of this checkout, or one that ended without releasing it left it | {lock} exists since {since} ({age} seconds); nothing was generated or changed | wait for the other export to finish; when none runs (`pgrep -fl "weles design export"` lists none), remove {lock} and run again | refused, exit 1, HTTP 403 | tests/design/exports/export-publish-failures.mjs::a_lock_another_export_holds_is_refused |
export-directory-unmade | creating {what} {path} | the system refused to create it ({errno}): {build} is not writable by this user, or its disk is full | {problem}; nothing was generated or changed; {left} | `ls -ld {build}` shows its owner and mode, `df -h {build}` its free space; correct it, then run the export again | config, exit 1, HTTP 503 | tests/design/exports/export-publish-failures.mjs::a_build_directory_this_user_cannot_write_is_refused |
export-install-failed | publishing the design export into {output} of {checkout} | the new tree could not be moved into place ({errno}) at the step {step} | {problem}; {left} | `ls -ld {path}` shows the directory the move needed; correct it, then run the export again | config, exit 1, HTTP 503 | tests/design/exports/export-publish-failures.mjs::an_install_the_system_refuses_is_named |
export-commit-refused | publishing the design export into {output} of {checkout} | git refused to {step} the new tree: a hook rejected it, or the index is locked by another git | `{command}` in {checkout} exited {status}: {said}; {left} | `git -C {checkout} status` shows what is staged; resolve what git said, then commit and push {output} by hand or run the export again | config, exit 1, HTTP 503 | tests/design/exports/export-publish-failures.mjs::a_commit_a_hook_refuses_is_named |
export-push-refused | pushing the export commit {commit} to {remote}/{branch} ({url}) | the remote refused the push: the branch moved on since it was read, it is protected, or the credentials may not write it | `{command}` in {checkout} exited {status}: {said}; {left} | `git -C {checkout} push {remote} HEAD:refs/heads/{branch}` once what git said is resolved | refused, exit 1, HTTP 403 | tests/design/exports/export-publish-failures.mjs::a_push_the_remote_refuses_is_named |
export-readback-differs | reading back {remote}/{branch} ({url}) after the export | the remote branch is at {observed} after the push, not the export's commit {commit}: another push landed meanwhile, or the push was not kept | local {commit}, remote {observed}; {left} | `git -C {checkout} fetch {remote}` and `git -C {checkout} log {remote}/{branch}` show what the remote holds; publication was not confirmed | infra_down, exit 69, HTTP 503, retryable | tests/design/exports/export-publish-failures.mjs::a_remote_that_reads_back_another_commit_is_named |
export-cleanup-failed | removing {what} {path} after the export {outcome} | the system refused to remove it ({errno}): it was removed, moved or made unwritable by another program while the export ran | {problem}; {left} | `ls -ld {path}` shows what is there; remove it by hand (`rm -r {path}`) when no export runs | config, exit 1, HTTP 503 | tests/design/exports/export-publish-failures.mjs::a_lock_removed_during_the_export_is_named |
parse-document-unread | reading the Figma document {source} | the document could not be read ({errno}): it does not exist, is a directory, or this user may not read it | {problem}; nothing was written | `ls -l {source}` shows what is there; name a document file `weles design export` wrote (document.json.gz of a file folder) | config, exit 1, HTTP 503 | tests/design/parse-failures.mjs::a_document_that_does_not_exist_is_named |
parse-document-not-gzip | reading the Figma document {source} | its name ends in .gz and its {bytes} bytes are not gzip data, so it cannot be unpacked | zlib said: {problem}; nothing was written | name the document without .gz when it is plain JSON, or the document.json.gz the export wrote | config, exit 1, HTTP 503 | tests/design/parse-failures.mjs::a_gz_document_that_is_not_gzip_is_refused |
parse-document-not-json | reading the Figma document {source} | its {bytes} bytes are not one JSON document, so no node of it can be read | the parser stopped: {problem}; nothing was written | export the file again (`weles design export --provider figma --request <export-request.json>`); a document cut short was downloaded while the connection dropped | config, exit 1, HTTP 503 | tests/design/parse-failures.mjs::a_document_that_is_not_json_is_refused |
parse-document-not-object | reading the Figma document {source} | it holds {found}, where a Figma file is the JSON object GET /v1/files/<key> answers | {bytes} bytes parsed; nothing was written | name the document.json.gz a design export wrote, not another JSON file | config, exit 1, HTTP 503 | tests/design/parse-failures.mjs::a_document_that_is_no_object_is_refused |
parse-output-unwritten | writing the {what} {path} of the Figma document {source} | the system refused the write ({errno}): its directory does not exist, is not writable by this user, or the disk is full | {problem}; written before it: {written} | `ls -ld {directory}` shows the directory's owner and mode, `df -h {directory}` its free space; correct it, then parse again | config, exit 1, HTTP 503 | tests/design/parse-failures.mjs::an_output_in_a_missing_directory_is_named |
figma-unreachable | asking Figma for {what} ({host}{path}) | the request got no answer ({errno}): the network, DNS or Figma is down, or a proxy refused the connection | {problem}; nothing was published; {left} | `curl -sSI https://api.figma.com/v1/me` shows whether Figma answers from this host; run the export again once it does | infra_down, exit 69, HTTP 503, retryable | tests/design/figma-failures.mjs::a_figma_that_does_not_answer_is_named |
figma-token-refused | asking Figma for {what} ({host}{path}) | Figma refused the token the export acquired: it expired, was revoked, or may not read this file | HTTP {status} from {host}{path}{retry}: {body}; nothing was published; {left} | store a token that reads the team's files under the request's credential item and field (`stado credentials doctor` names the vault), then run the export again | auth, exit 1, HTTP 401 | tests/design/figma-failures.mjs::a_token_figma_refuses_is_named |
figma-file-absent | asking Figma for {what} ({host}{path}) | Figma holds no {what} there: the file was deleted or moved, or the inventory names a wrong key | HTTP {status} from {host}{path}{retry}: {body}; nothing was published; {left} | open the file in Figma and copy its URL into the inventory's companyFiles, then run the export again | not_found, exit 1, HTTP 404 | tests/design/figma-failures.mjs::a_file_figma_does_not_hold_is_named |
figma-rate-limited | asking Figma for {what} ({host}{path}) | Figma refused the request because the token asked too often in its window | HTTP {status} from {host}{path}{retry}: {body}; nothing was published; {left} | run the export again after the Retry-After Figma named (or a minute when it named none) | rate_limit, exit 69, HTTP 429, retryable | tests/design/figma-failures.mjs::a_rate_limited_request_is_named |
figma-unavailable | asking Figma for {what} ({host}{path}) | Figma answered with a server error: its service failed or is overloaded | HTTP {status} from {host}{path}{retry}: {body}; nothing was published; {left} | https://status.figma.com shows an outage; run the export again once Figma answers | infra_down, exit 69, HTTP 503, retryable | tests/design/figma-failures.mjs::a_figma_server_error_is_named |
figma-request-refused | asking Figma for {what} ({host}{path}) | Figma refused the request itself as one it will not serve: too many node ids or too long a URL for one render | HTTP {status} from {host}{path}{retry}: {body}; nothing was published; {left} | split the file's top-level frames across pages in Figma, or report the file to Weles's maintainers with this refusal | refused, exit 1, HTTP 403 | tests/design/figma-failures.mjs::a_request_figma_will_not_serve_is_named |
figma-status-other | asking Figma for {what} ({host}{path}) | Figma answered with a status no request of the export expects (neither success, nor a refusal of the token, the file, the rate or the request) | HTTP {status} from {host}{path}{retry}: {body}; nothing was published; {left} | `curl -sSI https://api.figma.com{path}` shows what Figma answers now; report the status to Weles's maintainers | unknown, exit 1, HTTP 500 | tests/design/figma-failures.mjs::a_status_figma_was_not_expected_to_give_is_named |
figma-answer-unread | asking Figma for {what} ({host}{path}) | the connection dropped while the body of Figma's HTTP {status} answer was read | {problem}; nothing was published; {left} | run the export again; a large document needs a connection that holds for its whole download | infra_down, exit 69, HTTP 503, retryable | tests/design/figma-failures.mjs::an_answer_cut_short_is_named |
figma-answer-not-json | asking Figma for {what} ({host}{path}) | Figma answered HTTP {status} with {bytes} bytes that are not JSON, where its API answers JSON | the parser said: {problem}; content-type {type}; nothing was published; {left} | run the export again; a proxy between this host and Figma may have answered in its place (`curl -sS https://api.figma.com/v1/me` shows who answers) | unknown, exit 1, HTTP 500 | tests/design/figma-failures.mjs::an_answer_that_is_not_json_is_named |
figma-answer-shape | asking Figma for {what} ({host}{path}) | Figma answered {what} without {field}, which its API documents for that answer | the answer holds {members}; nothing was published; {left} | `curl -sS -H "X-Figma-Token: <token>" https://api.figma.com{path}` shows the answer; report it to Weles's maintainers if Figma changed it | unknown, exit 1, HTTP 500 | tests/design/figma-failures.mjs::an_answer_without_its_list_is_named |
figma-asset-url-invalid | downloading {what} from the address Figma gave | the address is no absolute URL ({problem}), so it cannot be fetched | the address is {length} characters, starting {start}; nothing was published; {left} | run the export again; report the file to Weles's maintainers if Figma keeps giving such an address | unknown, exit 1, HTTP 500 | tests/design/figma-failures.mjs::an_asset_address_that_is_no_url_is_refused |
figma-media-type-other | asking Figma for {what} ({host}{path}) | Figma served it as {type}, and neither that media type nor its path names a kind of file the export writes ({types}) | the download from {host}{path} was not written; nothing was published; {left} | report the file and this media type to Weles's maintainers, who add its extension to src/figma/transfer.mjs | unknown, exit 1, HTTP 500 | tests/design/figma-failures.mjs::a_download_of_an_unknown_media_type_is_refused |
figma-write-failed | writing {what} {path} into the export's working tree | the system refused the write ({errno}): the working tree was removed, is not writable by this user, or the disk is full | {problem}; nothing was published; {left} | `ls -ld {directory}` shows its owner and mode, `df -h {directory}` its free space; correct it, then run the export again | config, exit 1, HTTP 503 | tests/design/figma-failures.mjs::a_working_tree_that_cannot_be_written_is_named |