@revyl/verify 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/LICENSE +201 -0
- package/README.md +137 -2
- package/SECURITY.md +13 -0
- package/dist/api/client.d.ts +63 -0
- package/dist/api/client.js +201 -0
- package/dist/api/types.d.ts +222 -0
- package/dist/api/types.js +1 -0
- package/dist/auth.d.ts +17 -0
- package/dist/auth.js +65 -0
- package/dist/cleanup.d.ts +13 -0
- package/dist/cleanup.js +72 -0
- package/dist/cli/init.d.ts +14 -0
- package/dist/cli/init.js +115 -0
- package/dist/cli/main.d.ts +3 -0
- package/dist/cli/main.js +127 -0
- package/dist/computer-session.d.ts +43 -0
- package/dist/computer-session.js +418 -0
- package/dist/config.d.ts +88 -0
- package/dist/config.js +212 -0
- package/dist/errors.d.ts +9 -0
- package/dist/errors.js +14 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +3 -0
- package/dist/open.d.ts +13 -0
- package/dist/open.js +33 -0
- package/dist/screen-readiness.d.ts +1 -0
- package/dist/screen-readiness.js +27 -0
- package/dist/session.d.ts +153 -0
- package/dist/session.js +443 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +6 -0
- package/docs/README.md +19 -0
- package/docs/agents.md +43 -0
- package/docs/api.md +120 -0
- package/docs/ci.md +70 -0
- package/docs/computers.md +83 -0
- package/docs/configuration.md +92 -0
- package/docs/end-to-end.md +105 -0
- package/docs/errors.md +39 -0
- package/docs/getting-started.md +88 -0
- package/docs/linux-computers-contract.md +53 -0
- package/docs/llms.txt +19 -0
- package/docs/mobile.md +49 -0
- package/docs/web.md +37 -0
- package/package.json +71 -4
package/docs/ci.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# CI and test runners
|
|
2
|
+
|
|
3
|
+
Verify is a library, so it runs wherever your code runs. Browsers, devices, and computers run in Revyl's cloud, so the runner needs nothing but Node.js and an API key. Both Mac and Linux computer targets work from a Linux runner; no local guest runtime is needed. Use the same staging URL overrides and organization/OS enablement as [computers.md](computers.md).
|
|
4
|
+
|
|
5
|
+
## A script in GitHub Actions
|
|
6
|
+
|
|
7
|
+
Add `REVYL_API_KEY` as a repository secret, then run your script with `node`. A failed instruction or validation makes the script exit non-zero, which fails the job.
|
|
8
|
+
|
|
9
|
+
```yaml
|
|
10
|
+
name: Verify
|
|
11
|
+
on:
|
|
12
|
+
pull_request:
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
verify:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
timeout-minutes: 30
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v5
|
|
20
|
+
- uses: actions/setup-node@v5
|
|
21
|
+
with:
|
|
22
|
+
node-version: 24
|
|
23
|
+
cache: npm
|
|
24
|
+
- run: npm ci
|
|
25
|
+
- run: node verify/checkout.ts
|
|
26
|
+
env:
|
|
27
|
+
REVYL_API_KEY: ${{ secrets.REVYL_API_KEY }}
|
|
28
|
+
- if: ${{ !cancelled() }}
|
|
29
|
+
uses: actions/upload-artifact@v4
|
|
30
|
+
with:
|
|
31
|
+
name: verify-screenshots
|
|
32
|
+
path: .verify/
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
To open each pull request's preview deployment on web, read the URL from the environment in `verify.config.ts`:
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
web: { platform: 'web', app: { appId: process.env.REVYL_APP_ID!, url: process.env.PREVIEW_URL!, branch: process.env.GITHUB_HEAD_REF } }
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Inside a test runner
|
|
42
|
+
|
|
43
|
+
Verify has no test runner of its own. Call it from the one you already use, and close the session in `finally` or an `after` hook: test runners handle errors and shutdown themselves, so Verify's automatic cleanup steps aside for them.
|
|
44
|
+
|
|
45
|
+
### node:test
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { test } from 'node:test';
|
|
49
|
+
import { open } from '@revyl/verify';
|
|
50
|
+
|
|
51
|
+
test('a shopper can check out', async () => {
|
|
52
|
+
const session = await open('ios');
|
|
53
|
+
try {
|
|
54
|
+
await session.instruction('Add the first product to the cart and check out with the saved card');
|
|
55
|
+
await session.validation('An order confirmation with an order number is shown');
|
|
56
|
+
} finally {
|
|
57
|
+
await session.close();
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Vitest, Jest, or Playwright Test
|
|
63
|
+
|
|
64
|
+
The same code works with `import { test } from 'vitest'`, Jest's global `test`, or `import { test } from '@playwright/test'`. Give the test a timeout long enough for a session to start and the steps to run, for example `test('…', async () => { … }, 600_000)` in Vitest and Jest, or `test.setTimeout(600_000)` in Playwright Test.
|
|
65
|
+
|
|
66
|
+
A thrown `VerifyError` fails the test with its code, Revyl's reason, and the session link.
|
|
67
|
+
|
|
68
|
+
## Cost and parallelism
|
|
69
|
+
|
|
70
|
+
Each `open()` holds one Revyl session until `close()`. Sessions run in parallel when you open several at once, so close each one as soon as you are done with it. A browser/mobile session that loses its process stops itself after `idleTimeoutSeconds` (10 minutes by default). Computers instead have a server-bounded one-hour lifetime and no configurable idle timeout; close in `finally` and do not treat a timed-out CI job as proof of physical release.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Mac and Linux computers (early access)
|
|
2
|
+
|
|
3
|
+
Verify opens a Revyl cloud macOS or Linux computer for commands, files, processes, and screenshots. It does not open an assigned physical computer. Both operating systems are in **early access and not yet available in Revyl production**. They work only on Revyl staging for organizations enabled for the requested OS, which also need compute credits and available capacity. Ask [support@revyl.ai](mailto:support@revyl.ai) about staging access. No production availability date is promised.
|
|
4
|
+
|
|
5
|
+
Get the backend and matching app endpoints from Revyl, then set `REVYL_BACKEND_URL` and `REVYL_APP_URL` for your approved early-access environment before running:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
export REVYL_API_KEY=... # staging credentials for your enabled organization
|
|
9
|
+
npx @revyl/verify init --platform mac
|
|
10
|
+
npm install --save-dev @revyl/verify
|
|
11
|
+
node verify/example.ts
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
`init` only writes local files; running the script starts a paid computer. No app id, build, device selector, image selector, or local Mac is needed. The generated script checks commands and a file round trip without agent steps.
|
|
15
|
+
|
|
16
|
+
`REVYL_BACKEND_URL` is an optional environment override for Verify generally, but currently required for computer early access on staging. Production origins remain the defaults; never mix credentials between environments.
|
|
17
|
+
|
|
18
|
+
`init` intentionally generates only Mac computer projects. For Linux, install the package and use `open({ platform: 'linux' })` inline, or add `linux: { platform: 'linux' }` to the `targets` in `verify.config.ts`. The methods below are shared by both operating systems; installed tools, paths, resources, and desktop dimensions are not fixed SDK guarantees.
|
|
19
|
+
|
|
20
|
+
## Commands and files
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { open } from '@revyl/verify';
|
|
24
|
+
|
|
25
|
+
const computer = await open({ platform: 'mac', startTimeout: 600_000 });
|
|
26
|
+
try {
|
|
27
|
+
const result = await computer.exec(['uname', '-s']);
|
|
28
|
+
console.log(result.stdout);
|
|
29
|
+
await computer.writeFile('hello.txt', 'Hello!\n');
|
|
30
|
+
const bytes = await computer.readFile('hello.txt');
|
|
31
|
+
console.log(bytes.toString('utf8'));
|
|
32
|
+
console.log(await computer.stat('hello.txt'));
|
|
33
|
+
await computer.screenshot({ path: '.verify/mac.png' });
|
|
34
|
+
} finally {
|
|
35
|
+
await computer.close();
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
| Call | Result |
|
|
40
|
+
| --- | --- |
|
|
41
|
+
| `exec(argv: string[], { timeout?, stdin?, check? }?)` | `{ exitCode: number, stdout: string, stderr: string }`. Output strings are UTF-8 decoded. Nonzero exit throws `COMMAND_FAILED` unless `check: false`. |
|
|
42
|
+
| `readFile(path, { timeout? }?)` | A `Buffer`, up to the first 1 MiB. Use this for exact binary bytes. |
|
|
43
|
+
| `writeFile(path, data: string \| Uint8Array, { timeout? }?)` | `void`. Strings are UTF-8 encoded. Writes up to 64 KiB. |
|
|
44
|
+
| `stat(path, { timeout? }?)` | `{ kind: 'file' \| 'directory', size: number }`, with size in bytes. |
|
|
45
|
+
| `processes({ timeout? }?)` | Up to 64 `{ id: string, state: 'running' \| 'exited', exitCode?: number }` entries from commands on this computer. |
|
|
46
|
+
| `terminate(id, { timeout? }?)` | `void` after confirmed termination. Use an id from `processes()`, not an operating-system PID. |
|
|
47
|
+
| `screenshot({ path? }?)` | PNG `Uint8Array`; optionally saves it locally. |
|
|
48
|
+
|
|
49
|
+
`exec` takes structured argv, not a shell line. For shell syntax, explicitly invoke a shell, such as `['/bin/sh', '-c', 'printf hello']`. Never interpolate untrusted input into shell text. `stdin` accepts a string or `Uint8Array`. At most 64 arguments and 16,384 UTF-8 argv plus stdin bytes are allowed. Revyl keeps only the first 1 MiB of combined stdout/stderr; the command still runs to completion. Commands are synchronous at the library boundary; there is no interactive terminal or background-process creation API.
|
|
50
|
+
|
|
51
|
+
Paths are relative to the computer's workspace: no leading slash, empty components, `.` or `..`, backslashes, colons, or control characters. A path is at most 1,024 UTF-8 bytes. To make a directory, run a command such as `['mkdir', '-p', 'output']` before writing into it. There is no recursive sync or snapshot API.
|
|
52
|
+
|
|
53
|
+
Operation `timeout` is milliseconds, defaults to 30,000, and must be an integer from 1 to 300,000. It is also bounded by the run's remaining lifetime. A computer has a hard 60-minute lifetime **from create**, not from readiness or last activity; `idleTimeoutSeconds` and the agent `stepTimeout` do not configure computer lifetime or commands. `expiresAt` is a `Date` when the API returns `expires_at`, and otherwise undefined (including on devices and older APIs). Verify refuses new operations after that deadline and bounds operation/screenshot observation by it; expiry does not prevent explicit close or recording retrieval. Revyl allows at most 256 operations per run.
|
|
54
|
+
|
|
55
|
+
## Recording metadata
|
|
56
|
+
|
|
57
|
+
After terminal state **and** confirmed release, `await computer.recording()` returns `{ url, durationSeconds?, startedAt? }` or `null` if the recording is unavailable or still processing. `durationSeconds` is a finite nonnegative number; `startedAt` is a `Date`. Missing or nullable optional fields are omitted. Calling before terminal state and confirmed release throws `SESSION_BUSY`; Verify does not wait for the recording or download it. The method works after `close()` and checks the report before requesting the recording.
|
|
58
|
+
|
|
59
|
+
The HTTPS URL is signed and short-lived. Keep it private, including its query string; do not log or publish it. Call `recording()` again when a fresh URL is needed. The report carries no recording URL; `viewerUrl` remains a Revyl UI link, not a download URL. Web/mobile sessions reject `recording()` locally with `NOT_AVAILABLE_ON_PLATFORM`.
|
|
60
|
+
|
|
61
|
+
## Availability and errors
|
|
62
|
+
|
|
63
|
+
Mac and Linux computers are early access on Revyl staging only, not yet available in Revyl production. A `503 runtime_unavailable` or typed `computer_not_enabled` becomes `NOT_AVAILABLE_ON_PLATFORM` with this availability guidance. A `429 computer_limit` becomes `COMPUTER_LIMIT`: queued work (including iOS) or the concurrency limit can cause it, and the response does not identify which. Verify does not retry the create or send a stop after these definite admission refusals. `operation_limit` remains `SESSION_BUSY`. Availability is determined by the server, not a client-side production-URL block.
|
|
64
|
+
|
|
65
|
+
The default `startTimeout` remains 600000 ms; override it explicitly if needed. An unclaimed start can transition to `failed` with `failure_reason: 'no_host'` after 120 seconds from create. Verify stops readiness polling and throws `SESSION_START_FAILED` with that context, then attempts bounded cleanup. This is not a 120-second timeout for every start: claimed guests may take longer. `device_limit` also yields `SESSION_START_FAILED`, with queued-work/concurrency context. A start timeout or unconfirmed close does not prove resources were released. Inspect the session and contact support rather than blindly repeating a start or stop.
|
|
66
|
+
|
|
67
|
+
`instruction()` and `validation()` throw `NOT_AVAILABLE_ON_PLATFORM` on both computer operating systems without sending agent requests. Screenshots are supported, but screenshot readiness is separate from command readiness. Agent control of the screen is not part of this release. On web, iOS, and Android, shell/file/process methods throw `NOT_AVAILABLE_ON_PLATFORM`.
|
|
68
|
+
|
|
69
|
+
For the first 30 seconds after a computer becomes active, `screenshot()` retries exact structured `screen_unavailable` responses (HTTP 409 or 503) and `screen_starting` (HTTP 409). Backoff starts at 500 ms and doubles up to 2 seconds; the fixed window does not restart on each call. Cancellation, close, expiry, and the observation deadline still bound the wait. Exhausting that window yields `SESSION_BUSY` with a desktop-not-ready message, rather than requiring a caller sleep. Authentication failures, unrelated errors, malformed responses, and transport failures are not replayed. Commands and other mutations are never retried by this screenshot behavior.
|
|
70
|
+
|
|
71
|
+
Linux named and inline targets use the same computer surface as Mac. `init --platform linux` is intentionally unavailable; use an inline target or write its config entry. See [linux-computers-contract.md](linux-computers-contract.md) for the implemented shared contract.
|
|
72
|
+
|
|
73
|
+
Missing files and access denials throw `NOT_FOUND` and `ACCESS_DENIED`. An operation timeout with confirmed termination throws `TIMEOUT`; unconfirmed termination or execution throws `ACTION_UNCERTAIN`. Neither implies rollback. Execution outcome, evidence recording, and physical release are independent: an observed successful command can return its result while logging pending/failed report evidence. Do not repeat a mutation merely because its response was lost. Check the computer in Revyl before deciding what to do next. See [errors.md](errors.md).
|
|
74
|
+
|
|
75
|
+
## Lifecycle and transport
|
|
76
|
+
|
|
77
|
+
Verify mints a session UUID, creates `/api/v1/computer-runs` with `os: 'macos'` for `platform: 'mac'` or `os: 'linux'` for `platform: 'linux'`, then polls `GET /computer-runs/{id}/report` until `state: 'active'`, which means the owner route is ready. `starting` alone is not readiness. The public Mac target spelling is `mac`, not `macos`.
|
|
78
|
+
|
|
79
|
+
Each command is submitted once to `/computer-runs/{id}/operations` with its own UUID `request_id` and `Prefer: respond-async`. The operation id is UUIDv5 with the computer UUID as namespace and the request UUID string as name. A `202` response has JSON `null`; Verify reads the operation by id until it settles instead of submitting it again. Binary operation fields use padded URL-safe base64 on the wire, with standard base64 accepted as input. Screenshot uses standard base64 PNG instead.
|
|
80
|
+
|
|
81
|
+
Verify bounds operation observation to the earlier of 310 seconds or the returned expiry, and explicit stop requests to 310 seconds independently of expiry. This is separate from the command's `timeout`: the backend may hold the async submission for 60 seconds before returning `202`. Automatic process-exit cleanup has a shorter 15-second budget and is best effort; use explicit `await close()` in `finally` to observe confirmed release.
|
|
82
|
+
|
|
83
|
+
`close()` posts `/computer-runs/{id}/stop`. Always close in `finally` or use `await using`; the same automatic process cleanup as other sessions applies. A terminal state alone is not confirmed physical release. Keep computer output and screenshots private: they can contain application data and credentials. Verify does not log file contents, argv, stdin, or command output automatically.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
`open('name')` reads targets from `verify.config.ts` (or `.mts`, `.js`, `.mjs`), looking in the current folder and then each folder above it. You can skip the config and pass a target object to `open()` instead.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { defineConfig } from '@revyl/verify';
|
|
7
|
+
|
|
8
|
+
export default defineConfig({
|
|
9
|
+
targets: {
|
|
10
|
+
web: { platform: 'web', app: { appId: '…', url: 'https://staging.example.com' }, viewport: { width: 1280, height: 800 } },
|
|
11
|
+
ios: { platform: 'ios', app: { appId: '…' }, device: { model: 'iPhone 16', osVersion: '18.5' } },
|
|
12
|
+
pixel: { platform: 'android', app: { buildId: '…' }, device: { model: 'Pixel 8' } },
|
|
13
|
+
mac: { platform: 'mac' },
|
|
14
|
+
},
|
|
15
|
+
stepTimeout: 240_000,
|
|
16
|
+
});
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Unknown keys are rejected, so a typo fails loudly instead of being ignored.
|
|
20
|
+
|
|
21
|
+
## Top-level options
|
|
22
|
+
|
|
23
|
+
Mac and Linux targets currently require staging early access for an organization enabled for the requested OS through the optional `REVYL_BACKEND_URL` override and matching credentials; they are not yet available in Revyl production. See [computer availability](computers.md#availability-and-errors).
|
|
24
|
+
|
|
25
|
+
| Option | Default | Meaning |
|
|
26
|
+
| --- | --- | --- |
|
|
27
|
+
| `targets` | required | Named targets. The key is the name you pass to `open()`: letters, digits, dots, dashes, and underscores. |
|
|
28
|
+
| `stepTimeout` | `180000` | Milliseconds one instruction or validation may run before it is cancelled |
|
|
29
|
+
|
|
30
|
+
## Web targets
|
|
31
|
+
|
|
32
|
+
| Option | Meaning |
|
|
33
|
+
| --- | --- |
|
|
34
|
+
| `platform: 'web'` | |
|
|
35
|
+
| `app.appId` | Required. The Revyl web app. |
|
|
36
|
+
| `app.url` | A public HTTPS URL to open, registered as a build each time a session opens. |
|
|
37
|
+
| `app.buildId` | An exact Revyl web build to open instead of a URL. |
|
|
38
|
+
| `app.branch` | Branch to register the URL on, or to take the newest build from. |
|
|
39
|
+
| `viewport` | `{ width, height, deviceScaleFactor? }` in CSS pixels |
|
|
40
|
+
| `signIn` | `{ path, secret, persona? }`: a test-only sign-in route. See [web.md](web.md). |
|
|
41
|
+
| `startTimeout` | Milliseconds to wait for the browser. Default 600000. |
|
|
42
|
+
| `idleTimeoutSeconds` | Revyl stops the session after this much inactivity. Default 600. |
|
|
43
|
+
|
|
44
|
+
## iOS and Android targets
|
|
45
|
+
|
|
46
|
+
| Option | Meaning |
|
|
47
|
+
| --- | --- |
|
|
48
|
+
| `platform: 'ios'` or `'android'` | |
|
|
49
|
+
| `app.appId` | A Revyl app; each session installs its newest build. |
|
|
50
|
+
| `app.buildId` | An exact Revyl build to install. |
|
|
51
|
+
| `app.artifactUrl` | An HTTPS URL of an `.apk`, `.app.zip`, or `.ipa` to install. |
|
|
52
|
+
| `app.bundleId` | An app already on the device to launch instead, such as a system app. |
|
|
53
|
+
| `device` | `{ model?, osVersion? }`, for example `{ model: 'Pixel 8', osVersion: '15' }` |
|
|
54
|
+
| `startTimeout` | Milliseconds to wait for the device. Default 600000. |
|
|
55
|
+
| `idleTimeoutSeconds` | Default 600. |
|
|
56
|
+
|
|
57
|
+
Give exactly one of `appId`, `buildId`, `artifactUrl`, or `bundleId`.
|
|
58
|
+
|
|
59
|
+
## Computer targets
|
|
60
|
+
|
|
61
|
+
| Option | Meaning |
|
|
62
|
+
| --- | --- |
|
|
63
|
+
| `platform: 'mac'` | A cloud macOS computer, without `app` or `device` configuration |
|
|
64
|
+
| `platform: 'linux'` | A cloud Linux computer with the same SDK methods as Mac, without `app` or `device` configuration |
|
|
65
|
+
| `startTimeout` | Milliseconds to wait for an active owner route; default 600000, range 10000..1800000 |
|
|
66
|
+
|
|
67
|
+
Computers do not accept `idleTimeoutSeconds`, image, provider, cwd, or lifetime selectors. The server bounds a run to one hour; per-operation timeout defaults to 30000 ms and accepts 1..300000 ms. See [computers.md](computers.md).
|
|
68
|
+
|
|
69
|
+
## Environment variables
|
|
70
|
+
|
|
71
|
+
| Variable | Meaning |
|
|
72
|
+
| --- | --- |
|
|
73
|
+
| `REVYL_API_KEY` | API key. Wins over a stored CLI login unless that login is usable and marked `local_auth_override`. Without an environment key, a usable stored login is used; expired access tokens fall back to a stored API key. |
|
|
74
|
+
| `REVYL_BACKEND_URL` | Optional API origin override; production is the default. Get the endpoint for your approved early-access environment from Revyl and use matching credentials. |
|
|
75
|
+
| `REVYL_APP_URL` | Optional session-link origin override; production is the default. Get the matching app endpoint from Revyl. |
|
|
76
|
+
|
|
77
|
+
## Command line
|
|
78
|
+
|
|
79
|
+
Use the explicitly scoped package command below. The `verify` and `revyl-verify` bins work only after installing `@revyl/verify`; their names are not standalone npm package installation instructions. Scripts run with `node`, not through the CLI.
|
|
80
|
+
|
|
81
|
+
```text
|
|
82
|
+
npx @revyl/verify init [--platform web|ios|android|mac] [--app-id <id>] [--url <https-url>] [--force]
|
|
83
|
+
npx @revyl/verify whoami
|
|
84
|
+
npx @revyl/verify --help
|
|
85
|
+
npx @revyl/verify --version
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
- `init` writes `verify.config.ts` and `verify/example.ts`, and adds `.verify/` to `.gitignore`. Without `--platform` it asks its questions interactively. It never overwrites files unless you pass `--force`.
|
|
89
|
+
- `npx @revyl/verify init --platform mac` needs no app id and generates a command/file script. `--app-id` is rejected for Mac, `--url` applies only to web, and Linux init is intentionally unavailable. Use `open({ platform: 'linux' })` or a Linux config target instead.
|
|
90
|
+
- `whoami` prints the Revyl account and organization your credentials belong to.
|
|
91
|
+
|
|
92
|
+
Exit codes: `0` success, `1` a Revyl or file error, `2` a usage or authentication problem.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Verify working end to end
|
|
2
|
+
|
|
3
|
+
These live runs were made on **2026-10-27**. The baseline uses source commit [`6029513369f13e73914b550d0cd26ece615b4e13`](https://github.com/RevylAI/verify/tree/6029513369f13e73914b550d0cd26ece615b4e13); the separate published-package check used `@revyl/verify@0.1.0`. The [re-verification below](#re-verification-at-061e5cfa) uses `061e5cfa31c5489aa304c2b640bbaf40e3d88561`, including an unpublished packed 0.2.0 tarball. Neither is a publication announcement.
|
|
4
|
+
|
|
5
|
+
Only reviewed public scripts, sanitized transcripts, result projections, and two reviewed screenshots were used here. No private reports, device identifiers, session links, signed recording URLs, or recordings are published.
|
|
6
|
+
|
|
7
|
+
## Baseline live results
|
|
8
|
+
|
|
9
|
+
Web, iOS, and Android ran in production. Mac and Linux ran on staging with early-access access enabled; computers are not yet available in production. Durations below are observed script wall times, not performance targets.
|
|
10
|
+
|
|
11
|
+
| SDK | Platform and workload | Result | Total time | Cleanup |
|
|
12
|
+
| --- | --- | --- | --- | --- |
|
|
13
|
+
| TypeScript source | Web: add a TodoMVC task, validate it, capture PNG | Passed | 66.6 s | Stop acknowledged |
|
|
14
|
+
| TypeScript source | iOS Settings: General → About, validate OS version, capture PNG | Passed | 66.3 s | Stop acknowledged |
|
|
15
|
+
| TypeScript source | Android Settings: About, validate visible OS version, capture PNG | Passed | 88.2 s | Stop acknowledged |
|
|
16
|
+
| Go source | Web: same TodoMVC task, validation, PNG | Passed | 67.7 s | Stop acknowledged |
|
|
17
|
+
| Go source | iOS Settings: General → About, OS-version validation, PNG | Passed | 75.3 s | Stop acknowledged |
|
|
18
|
+
| TypeScript source | Mac: command, file round-trip, PNG | Passed | 18.3 s | Terminal state and release confirmed |
|
|
19
|
+
| Go source | Mac: command, file round-trip, PNG | Passed | 18.0 s | Terminal state and release confirmed |
|
|
20
|
+
|
|
21
|
+
Every passing web/mobile row includes a passing instruction and validation plus a captured screenshot. Device close acknowledges the stop request; it does not itself prove physical release. The computer reports independently recorded `state: stopped` and `release: confirmed`.
|
|
22
|
+
|
|
23
|
+
### Web proof
|
|
24
|
+
|
|
25
|
+
The instruction was `Add a task named Verify readiness proof to the todo list.` The validation was `The todo list contains an active task named Verify readiness proof.` Both SDKs passed. The TypeScript transcript records:
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{"platform":"web","status":"passed","close":"acknowledged","steps":[{"type":"instruction","passed":true,"durationMs":42564},{"type":"validation","passed":true,"durationMs":14669}],"totalDurationMs":66633}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+

|
|
32
|
+
|
|
33
|
+
### Mobile proof and the Android recipe
|
|
34
|
+
|
|
35
|
+
The iOS scripts launched `com.apple.Preferences`, instructed `Open General, then About.`, then validated `The screen shows the operating system version.` TypeScript and Go both passed.
|
|
36
|
+
|
|
37
|
+
The TypeScript Android run passed the OS-version validation. The baseline Go Android run did **not**: About opened, but the version was offscreen, so `VALIDATION_FAILED` was the correct outcome. The corrected recipe explicitly scrolls or navigates until the version is visible before validating it; its passing re-verification is recorded separately below.
|
|
38
|
+
|
|
39
|
+
For example, the maintained [Settings example](https://github.com/RevylAI/verify/tree/main/examples/mobile-settings) now asks the agent to open About **and scroll or navigate until the operating system version is visible**. Validating a screen immediately after navigation is not a substitute for bringing the specific fact into view.
|
|
40
|
+
|
|
41
|
+
### Mac proof
|
|
42
|
+
|
|
43
|
+
Both SDKs opened a Mac computer, ran `uname -a` with exit code 0, wrote `Verify readiness proof\n` to a workspace-relative file, read back identical bytes, captured a PNG, and closed with confirmed release. They used argv arrays, not an implicit shell. This proves those operations for those runs; it does not establish process-control behavior or future host capacity.
|
|
44
|
+
|
|
45
|
+
## Original failures and limits
|
|
46
|
+
|
|
47
|
+
The release changes three paths. Their earlier results remain failures or caveated observations, not silently promoted to successes:
|
|
48
|
+
|
|
49
|
+
- **Generated iOS example:** published 0.1.0 failed twice with `VALIDATION_FAILED` on a blank white screen. Its screenshot came from the failed validation report because the script's later screenshot line was never reached. The new initializer first instructs the agent to wait for the app's first screen; the changed stranger path passed in the separate re-verification below.
|
|
50
|
+
- **Go Android recipe:** the OS-version validation failed while its target was offscreen. The corrected instruction's explicit scrolling/navigation passed in the separate re-verification below.
|
|
51
|
+
- **Linux screenshot:** TypeScript commands and file round-trip passed, but immediate capture returned `SESSION_BUSY` with `screen_unavailable`; release was confirmed. Go captured successfully only after a manually prescribed 15-second wait. Neither result proves the new SDK-managed retry path. Linux remains early access, and screenshot readiness is separate from command readiness.
|
|
52
|
+
|
|
53
|
+

|
|
54
|
+
|
|
55
|
+
Both SDKs now make a bounded, best-effort mobile blank-frame observation after connection/launch. It can time out or skip unavailable images without failing open; **the runtime has no rendered-readiness signal**, and non-blank pixels do not prove the app is foreground or loaded. The generated example's first instruction remains necessary. See [mobile readiness](mobile.md#steps).
|
|
56
|
+
|
|
57
|
+
Computer capture retries only structured startup screen-not-ready responses during a fixed 30-second window after active readiness, bounded by cancellation and expiry. Callers do not need a sleep. That behavior has fake-server coverage; **Linux screenshot retry has not been proven live**. See [computers](computers.md).
|
|
58
|
+
|
|
59
|
+
## Re-verification at 061e5cfa
|
|
60
|
+
|
|
61
|
+
The following checks ran on **2026-10-27** against [`061e5cfa31c5489aa304c2b640bbaf40e3d88561`](https://github.com/RevylAI/verify/tree/061e5cfa31c5489aa304c2b640bbaf40e3d88561). TypeScript used a packed 0.2.0 tarball installed into fresh projects, not workspace links or a published npm 0.2.0. Its SHA-256 was `c2791c850f80b0a9c0b16be93aea324be8e9ae5967e22d0caaed5823d79e0d33`. Go resolved the exact commit through `go get`. The rows come from reviewed public scripts, setup/terminal transcripts, and result projections.
|
|
62
|
+
|
|
63
|
+
| SDK and case | Result | Script wall time | Cleanup and report |
|
|
64
|
+
| --- | --- | --- | --- |
|
|
65
|
+
| Packed TypeScript init, iOS Settings | Passed the generated first-screen instruction and validation, then captured PNG | 44.1 s | Stop acknowledged; terminal report completed, 1/1 validations passed |
|
|
66
|
+
| Go Android, corrected visible-version recipe | Passed instruction and OS-version validation, then captured PNG | 104.4 s | Stop acknowledged; terminal report completed, 1/1 validations passed |
|
|
67
|
+
| Packed TypeScript Linux, immediate screenshot | Blocked before session creation: `COMPUTER_LIMIT` | 0.8 s; one retry 0.6 s | No session created; no screenshot requests or release required |
|
|
68
|
+
|
|
69
|
+
The iOS check ran `init --platform ios` in a fresh tarball-install project and changed only the generated app selection to `bundleId: 'com.apple.Preferences'`. It kept the generated instruction, validation, screenshot, and close sequence. This proves that sequence for that run, not that blank-frame observation guarantees readiness or caused the pass.
|
|
70
|
+
|
|
71
|
+
The Go Android instruction was `Open About phone or About device, then scroll as needed and open Android version so the operating system version number is visible on screen.` The validation was `The screen shows the operating system version.` Both returned `passed: true`; the report independently recorded zero effective failed steps and one passing validation.
|
|
72
|
+
|
|
73
|
+
Both Linux attempts were admission refusals, **not an SDK screenshot failure or a successful retry proof**. The script had no manual sleep/retry, but never reached its screenshot call. The new retry behavior remains tests-only, and Linux remains staging-only early access. No further live attempt was authorized. The original 0.1.0 iOS failures, offscreen Android failure, and immediate Linux screenshot failure above remain part of the evidence.
|
|
74
|
+
|
|
75
|
+
## Reading the verdict
|
|
76
|
+
|
|
77
|
+
Fetch a web/mobile report with:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
revyl device report --session-id <session-id> --json
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Do not infer a passing validation from `status: success`, `passed_steps`, or `failed_steps`. These can reflect successful execution of a validation whose semantic verdict is false. Read each step's **`effective_status` and `validation_result`**, and compare **`validations_passed` with `total_validations`**. Effective passed/failed step counts summarize semantic outcomes where provided. The SDK maps the actual validation verdict to `StepResult.passed` and throws `VALIDATION_FAILED` by default.
|
|
84
|
+
|
|
85
|
+
For the failed Go Android baseline, the public projection records transport counts `passed_steps: 3`, `failed_steps: 0`, but `effective_failed_steps: 1`, `total_validations: 1`, and `validations_passed: 0`; the validation step has `effective_status: failed` and projected `validationPassed: false`. The transport counts do not override that failure.
|
|
86
|
+
|
|
87
|
+
## Reproducing the workflow
|
|
88
|
+
|
|
89
|
+
Use an authorized account and the exact source revision when comparing to the baseline. These commands prepare the TypeScript source; they do not publish it:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
git clone https://github.com/RevylAI/verify.git
|
|
93
|
+
cd verify
|
|
94
|
+
git checkout 6029513369f13e73914b550d0cd26ece615b4e13
|
|
95
|
+
npm ci
|
|
96
|
+
npm run build
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
For Go, pin the same baseline with `go get github.com/RevylAI/verify/go@6029513369f13e73914b550d0cd26ece615b4e13` in your own module. Use the [TypeScript API](getting-started.md) or [Go API](https://github.com/RevylAI/verify/tree/main/go) to open the target, run an instruction, validate the visible outcome, capture a screenshot, and close in `finally` or a bounded deferred cleanup context. The web workload uses `https://demo.playwright.dev/todomvc` and a Revyl web app ID supplied privately through `REVYL_WEB_APP_ID`.
|
|
100
|
+
|
|
101
|
+
Do not use the baseline's blank-screen initializer or offscreen Android assertion as a recommended recipe. To test the fixes, use the reviewed new source revision rather than the baseline commit or the older npm package. Ask Revyl for the approved computer endpoints and matching credentials; never place credentials in argv or publish signed recording URLs.
|
|
102
|
+
|
|
103
|
+
## Limits
|
|
104
|
+
|
|
105
|
+
Fake-server tests cover contracts, observation bounds, errors, expiry, and cleanup. They do not prove live availability, app rendering, installed software, recording generation, or successful physical release. Downloaded recordings existed for the baseline runs but remain private and are not evidence published here. No claim is made about successful live Linux screenshot retries; that path could not be reached during re-verification.
|
package/docs/errors.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Errors
|
|
2
|
+
|
|
3
|
+
Everything Verify throws on purpose is a `VerifyError` with a stable `code`. Messages name the problem and, for step failures, end with the session link.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { isVerifyError } from '@revyl/verify';
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
await session.validation('The cart is empty');
|
|
10
|
+
} catch (error) {
|
|
11
|
+
if (isVerifyError(error) && error.code === 'VALIDATION_FAILED') { /* … */ }
|
|
12
|
+
throw error;
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
| Code | What happened | What to do |
|
|
17
|
+
| --- | --- | --- |
|
|
18
|
+
| `INVALID_ARGUMENT` | A call got a bad value: an unknown target name, an empty step, two steps at once on one session | The message names the argument |
|
|
19
|
+
| `CONFIG_NOT_FOUND` | `open('name')` found no `verify.config.ts` at or above the current folder | Run `npx @revyl/verify init`, pass `options.config`, or pass a target object |
|
|
20
|
+
| `CONFIG_INVALID` | The config failed validation or did not load | The message names the exact option and the rule |
|
|
21
|
+
| `AUTH_MISSING` | No `REVYL_API_KEY` and no Revyl CLI login | Set `REVYL_API_KEY` or run `revyl auth login` |
|
|
22
|
+
| `AUTH_REJECTED` | Revyl rejected the credentials | Create a new API key, or log in again |
|
|
23
|
+
| `API_ERROR` | Revyl returned an unexpected response | Try again; if it persists, report it with the session link |
|
|
24
|
+
| `SESSION_START_FAILED` | The browser, device, or computer did not start | Computer `no_host` reports the 120-second unclaimed-start context; `device_limit` reports queued-work/concurrency context. Inspect the session and cleanup before starting again. |
|
|
25
|
+
| `SESSION_ENDED` | The session was closed, stopped, or expired | Close explicitly; computer expiry does not prove release |
|
|
26
|
+
| `SESSION_BUSY` | A step conflict, computer operation limit, or recording requested before terminal state and confirmed release | Inspect the state before another call; a terminal computer alone does not prove release |
|
|
27
|
+
| `COMPUTER_LIMIT` | Computer admission was refused due to queued work (including iOS) or the concurrency limit | Wait for capacity; Verify does not retry or stop a definitely refused start |
|
|
28
|
+
| `NOT_AVAILABLE_ON_PLATFORM` | The call is unavailable: computer agent steps, device shell/file/recording methods, computer OS/organization not enabled or runtime unavailable, or a backend without web steps | See [web.md](web.md) and [computers.md](computers.md#availability-and-errors) |
|
|
29
|
+
| `COMMAND_FAILED` | A computer command exited nonzero | Read its reason; use `exec(argv, { check: false })` to inspect the exit code and output instead |
|
|
30
|
+
| `NOT_FOUND` | A computer file or resource was not found | Check the workspace-relative path |
|
|
31
|
+
| `ACCESS_DENIED` | The computer refused access | Check permissions; do not assume host/root access |
|
|
32
|
+
| `ACTION_FAILED` | Revyl refused or failed a request on the session | Read the reason in the message |
|
|
33
|
+
| `ACTION_UNCERTAIN` | The connection failed mid-request, so the step may or may not have run | Verify does not repeat it; check the session link before deciding what to do |
|
|
34
|
+
| `INSTRUCTION_FAILED` | Revyl's agent could not do what the instruction asked | Make the instruction more specific, or split it into smaller steps |
|
|
35
|
+
| `VALIDATION_FAILED` | The validation did not hold | The message has Revyl's reason; watch the recording at the session link |
|
|
36
|
+
| `STEP_FAILED` | Revyl could not run the step to an outcome: it errored, it was cancelled on Revyl, or a validation came back without a verdict. This says nothing about your app, and `soft` does not cover it. | Watch the session at the link, then run the step again |
|
|
37
|
+
| `TIMEOUT` | A step, a session start, or a request ran out of time | Inspect the session before retrying; computer starts require staging enablement. The default 10-minute start timeout is separate from the 120-second unclaimed-start failure. See [computer availability](computers.md#availability-and-errors). |
|
|
38
|
+
|
|
39
|
+
Computer operations use `TIMEOUT` for bounded runtime with confirmed termination, and `ACTION_UNCERTAIN` when termination, execution, or cleanup is unconfirmed. An observed successful operation can return its result while logging pending/failed evidence. Timeout never proves rollback, and failed evidence does not prove the command never ran. Never automatically retry a mutation with a fresh request id; inspect the computer first. See [computers.md](computers.md).
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Getting started
|
|
2
|
+
|
|
3
|
+
Verify needs Node.js 22.18 or later, which runs TypeScript files directly, and a Revyl account.
|
|
4
|
+
|
|
5
|
+
## 1. Create a config and an example script
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx @revyl/verify init
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
`init` asks what you want to open (web, iOS, Android, or Mac), your Revyl app id for web/mobile, and for web, the URL to open. It writes two files and adds `.verify/` to `.gitignore`:
|
|
12
|
+
|
|
13
|
+
- `verify.config.ts`: the targets `open()` can start, by name
|
|
14
|
+
- `verify/example.ts`: a script that opens the target, runs steps and takes a screenshot (web/mobile) or checks commands and files (Mac), and closes
|
|
15
|
+
|
|
16
|
+
In scripts and CI, pass the answers as flags:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npx @revyl/verify init --platform web --app-id <revyl-app-id> --url https://staging.example.com
|
|
20
|
+
npx @revyl/verify init --platform ios --app-id <revyl-app-id>
|
|
21
|
+
npx @revyl/verify init --platform mac
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## 2. Install Verify
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install --save-dev @revyl/verify
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## 3. Authenticate
|
|
31
|
+
|
|
32
|
+
Verify uses the same credential precedence as the Revyl CLI:
|
|
33
|
+
|
|
34
|
+
1. A usable login in `~/.revyl/credentials.json` marked `local_auth_override` takes precedence, even when `REVYL_API_KEY` is set.
|
|
35
|
+
2. Otherwise, `REVYL_API_KEY`, an API key you create in Revyl's settings.
|
|
36
|
+
3. Otherwise, a usable login stored by the Revyl CLI (`revyl auth login`). Expired access tokens fall back to a stored API key when present.
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
export REVYL_API_KEY=... # or: revyl auth login
|
|
40
|
+
npx @revyl/verify whoami # checks the credentials without starting a session
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## 4. Point the config at your app
|
|
44
|
+
|
|
45
|
+
Web and mobile targets need an app. Open the app in Revyl and copy its id into `app.appId`, or set `REVYL_APP_ID`, which the generated config reads. Mac and Linux targets need no app id. Linux init is intentionally unavailable; use `open({ platform: 'linux' })` or add a Linux config target.
|
|
46
|
+
|
|
47
|
+
- **Web:** set `app.url` to a public HTTPS URL, such as a staging or preview deployment. Revyl's cloud browser cannot reach `localhost`. See [web.md](web.md).
|
|
48
|
+
- **iOS and Android:** upload a build to the app in Revyl, for example with `revyl build upload`; each session installs the newest one. See [mobile.md](mobile.md).
|
|
49
|
+
- **Mac and Linux:** early access is not yet available in Revyl production. Both work only on Revyl staging for organizations enabled for the requested OS through the optional `REVYL_BACKEND_URL` override and matching staging credentials. Agent instruction/validation are unavailable. See [computers.md](computers.md#availability-and-errors).
|
|
50
|
+
|
|
51
|
+
## 5. Run the script
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
node verify/example.ts
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
If Node warns that the module type is not specified, add `"type": "module"` to your `package.json` or rename the script to `example.mts`.
|
|
58
|
+
|
|
59
|
+
Illustrative output (durations vary):
|
|
60
|
+
|
|
61
|
+
```text
|
|
62
|
+
[verify] ios: starting ios session https://app.revyl.ai/sessions/…
|
|
63
|
+
[verify] ios: ready
|
|
64
|
+
[verify] ios: ✓ instruction "Wait for the app to finish opening and show its first screen" (12.0s)
|
|
65
|
+
[verify] ios: ✓ validation "The app has opened and shows its first screen without an error" (9.8s)
|
|
66
|
+
[verify] ios: closed
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Open the link to watch the session live. After the session ends, the link keeps its report and any available recording. The Revyl CLI prints the stored report with `revyl device report --session-id <id>` for web/mobile, or `revyl computer report <id>` for computers. Computer recordings can be absent or still processing after release; `recording()` retrieves optional private metadata without waiting or downloading.
|
|
70
|
+
|
|
71
|
+
For validation verdicts in device reports, inspect each step's `effective_status` and `validation_result`, and compare `validations_passed` with `total_validations`. `passed_steps`, `failed_steps`, and transport `status: success` can describe successful execution of a validation whose verdict is false. See the [live example](end-to-end.md#reading-the-verdict).
|
|
72
|
+
|
|
73
|
+
## The whole API
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import { open } from '@revyl/verify';
|
|
77
|
+
|
|
78
|
+
const session = await open('ios'); // a target from verify.config.ts
|
|
79
|
+
try {
|
|
80
|
+
await session.instruction('Add the first product to the cart'); // Revyl's agent does it
|
|
81
|
+
await session.validation('The cart badge shows 1'); // throws if it does not hold
|
|
82
|
+
await session.screenshot({ path: '.verify/cart.png' });
|
|
83
|
+
} finally {
|
|
84
|
+
await session.close();
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
A failed instruction or validation throws a `VerifyError`. An uncaught error in a plain script exits non-zero. Automatic cleanup is best effort, bounded to 15 seconds, and skips signal/error handlers that already have listeners. Always close in `try/finally`; a successful web/mobile close acknowledges the stop request, not confirmed physical release. Computer close requires confirmed terminal release. See [api.md](api.md) for cleanup details and every option, and [ci.md](ci.md) for calling Verify from a test runner.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Linux computers: implemented shared contract
|
|
2
|
+
|
|
3
|
+
Verify 0.2.0 enables Linux named and inline targets with the same computer API as Mac. Both operating systems are **early access on staging for organizations enabled for the requested OS, not yet available in production**. Server admission decides availability; SDK support is not a guarantee that capacity is available. No live Linux run was performed for this SDK change.
|
|
4
|
+
|
|
5
|
+
Use `open({ platform: 'linux', startTimeout: 600_000 })`, or add `linux: { platform: 'linux' }` under `targets` in `verify.config.ts`. There is no app, image, provider, cwd, TTL, or device selector. `init --platform linux` remains intentionally unavailable; it does not block inline or named Linux SDK targets. See [computers.md](computers.md) for staging setup and usage.
|
|
6
|
+
|
|
7
|
+
## Public surface
|
|
8
|
+
|
|
9
|
+
- `exec(argv, { timeout?, stdin?, check? }?)` returns `{ exitCode, stdout, stderr }`. There is no implicit shell. Nonzero exit throws `COMMAND_FAILED` unless `check: false`.
|
|
10
|
+
- `readFile`, `writeFile`, and `stat` use workspace-relative paths and preserve binary bytes.
|
|
11
|
+
- `processes` and `terminate` use run-owned handles, never OS PIDs.
|
|
12
|
+
- `screenshot({ path? }?)` returns PNG bytes with dimensions from the response; screenshot readiness is independent of command readiness. The SDK retries only structured startup screen-not-ready responses within its fixed 30-second window after active readiness; [computer docs](computers.md) describe the bounds and typed exhaustion error.
|
|
13
|
+
- `close()` succeeds only on terminal state and confirmed physical release.
|
|
14
|
+
- `recording()` returns private recording metadata or null after terminal state and confirmed release, and works after close. It does not wait or download.
|
|
15
|
+
- `expiresAt` exposes a validated API timestamp as a `Date`, or is undefined on older APIs. The hard lifetime is 60 minutes from create, not readiness or activity. Expired operations are rejected, pending observations are bounded by expiry, and close remains possible.
|
|
16
|
+
|
|
17
|
+
`instruction` and `validation` are rejected locally on both computer operating systems. There is no computer agent-step endpoint. The assigned-physical-computer `shell-sessions` API is not part of this contract. Installed tools, guest paths, resources, architecture, and screen dimensions are not SDK guarantees; use portable commands or inspect what the selected guest provides.
|
|
18
|
+
|
|
19
|
+
## Requests and readiness
|
|
20
|
+
|
|
21
|
+
All routes use `/api/v1/computer-runs`, the selected `REVYL_BACKEND_URL`, bearer authentication, `X-Revyl-Client: verify`, and Verify's versioned user agent. Ownership is enforced by the server; possession of a UUID grants no access. `REVYL_APP_URL` controls UI links.
|
|
22
|
+
|
|
23
|
+
| Request | Contract |
|
|
24
|
+
| --- | --- |
|
|
25
|
+
| `POST /computer-runs` | `{ computer_session_id: C1, os: 'linux' }`; Mac sends `os: 'macos'`. Mint C1 once, never replay an uncertain create. |
|
|
26
|
+
| `GET /computer-runs/C1/report` | Validate C1 and lifecycle state. Poll while starting, resolve on active, fail on other states. `failed/no_host` means an unclaimed start reached the 120-second window, not that all starts must finish within it. The default client start timeout stays 10 minutes. |
|
|
27
|
+
| `POST /computer-runs/C1/operations` | `{ computer_session_id: C1, request_id: R1, command }`, with `Prefer: respond-async`. Response is a command result or `202` with exact JSON null. |
|
|
28
|
+
| `GET /computer-runs/C1/operations/O1` | Read the retained result, or `202`/null while running. O1 is UUIDv5(namespace=C1, name=R1 string). Poll reads without replaying mutations. |
|
|
29
|
+
| `POST /computer-runs/C1/stop` | Report state and release separately. Close requires `stopped`, `failed`, or `owner_lost` plus `release: 'confirmed'`. |
|
|
30
|
+
| `GET /computer-runs/C1/recording` | After a separate report confirms terminal state and release: `{ url, duration_seconds?, started_at? }`, or `404 { error: 'recording_unavailable' }`. Optional fields may be null. No URL is carried by the normal report. |
|
|
31
|
+
|
|
32
|
+
Recording URLs must be HTTPS and are signed, short-lived, and private, including the query string. Duration must be finite and nonnegative, and timestamps must include a timezone. Malformed responses produce a generic error that does not echo the URL. The SDK never fetches that URL. A pre-release recording request throws `SESSION_BUSY`; missing recording returns null without automatic waiting.
|
|
33
|
+
|
|
34
|
+
## Commands and uncertainty
|
|
35
|
+
|
|
36
|
+
Commands retain the shared limits: 30-second default / 5-minute maximum timeout, 256 operations per run, 64 argv entries, 16,384 bytes of argv plus stdin, first 1 MiB combined stdout/stderr, 1 MiB reads, 64 KiB writes, and 64 listed handles. File paths must be workspace-relative without traversal. See [computers.md](computers.md) for complete validation rules.
|
|
37
|
+
|
|
38
|
+
Operation responses identify C1 and O1 and separate `execution` from `evidence: 'recorded' | 'pending' | 'failed'`. Successful execution can return a result even when report evidence is pending or failed. An observed nonzero exec result is distinct from transport failure. Unknown outcome, unconfirmed timeout termination, and lost mutation responses produce `ACTION_UNCERTAIN` and never authorize replay. Terminal lifecycle, physical release, and recording availability are independent.
|
|
39
|
+
|
|
40
|
+
| Condition | Verify code |
|
|
41
|
+
| --- | --- |
|
|
42
|
+
| `computer_not_enabled`, or `503 runtime_unavailable` | `NOT_AVAILABLE_ON_PLATFORM` |
|
|
43
|
+
| `429 computer_limit` | `COMPUTER_LIMIT`: queued work (including iOS) or concurrency limit; no automatic start retry or stop after definite admission refusal |
|
|
44
|
+
| `operation_limit`, other conflict refusals | `SESSION_BUSY` |
|
|
45
|
+
| `failed/no_host`, `failed/device_limit`, `start_failed` | `SESSION_START_FAILED`; safe no-host or queued-work/concurrency context when known |
|
|
46
|
+
| `run_closed`, or local expiry | `SESSION_ENDED` |
|
|
47
|
+
| `inaccessible`, `invalid_handle`, operation `not_found` | `NOT_FOUND` |
|
|
48
|
+
| Operation `access_denied` | `ACCESS_DENIED` |
|
|
49
|
+
| Operation `exec_nonzero` | `COMMAND_FAILED` unless `check: false` |
|
|
50
|
+
| Unknown execution, operation unavailable, uncertain mutation or release | `ACTION_UNCERTAIN` |
|
|
51
|
+
| Confirmed operation timeout | `TIMEOUT` |
|
|
52
|
+
|
|
53
|
+
Other HTTP errors retain the shared authentication, input, or API classifications described in [errors.md](errors.md). Do not infer a capacity cause from `computer_limit` alone or expose arbitrary failure text, command data, credentials, or recording URLs in diagnostics.
|
package/docs/llms.txt
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Verify
|
|
2
|
+
|
|
3
|
+
> Verify (@revyl/verify) is an open-source TypeScript library for Revyl. It opens cloud browsers and iOS/Android devices for agent instruction/validation steps, or Mac and Linux computers for commands, files, processes, screenshots, and optional private recording metadata after release. Call it from a plain script, coding agent, CI, or any test runner. It has no test runner of its own.
|
|
4
|
+
|
|
5
|
+
## Docs
|
|
6
|
+
|
|
7
|
+
Both computer operating systems are early access and not yet available in Revyl production. They work only on Revyl staging for organizations enabled for the requested OS using the optional REVYL_BACKEND_URL override and matching staging credentials. Linux init remains intentionally unavailable; use an inline target or config entry. See computers.md#availability-and-errors.
|
|
8
|
+
|
|
9
|
+
- [Getting started](getting-started.md): install, authenticate, init, run a script
|
|
10
|
+
- [API](api.md): open(), agent steps, screenshot(), exec(), file/process methods, close()
|
|
11
|
+
- [Configuration](configuration.md): verify.config.ts targets, environment variables, the init and whoami commands
|
|
12
|
+
- [Web](web.md): Revyl web sessions, URLs and builds, sign-in, steps, and optional staging access
|
|
13
|
+
- [Mobile](mobile.md): iOS and Android devices and installing apps
|
|
14
|
+
- [Computers](computers.md#availability-and-errors): staging-only Mac/Linux early access, expiry, capacity errors, recording metadata, and confirmed release
|
|
15
|
+
- [Verify working end to end](end-to-end.md): live verification scope, observed outcomes, and limits of the evidence
|
|
16
|
+
- [Linux contract](linux-computers-contract.md): implemented shared requests/responses, readiness, errors, and uncertainty
|
|
17
|
+
- [CI and test runners](ci.md): GitHub Actions, node:test, Vitest, Jest, Playwright Test
|
|
18
|
+
- [Errors](errors.md): every error code and what to do about it
|
|
19
|
+
- [For coding agents](agents.md): the workflow and rules for agents using Verify
|
package/docs/mobile.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# iOS and Android
|
|
2
|
+
|
|
3
|
+
An `ios` or `android` target opens your app on a Revyl cloud simulator or emulator.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
{ platform: 'ios', app: { appId: '…' }, device: { model: 'iPhone 16', osVersion: '18.5' } }
|
|
7
|
+
{ platform: 'android', app: { buildId: '…' }, device: { model: 'Pixel 8' } }
|
|
8
|
+
{ platform: 'android', app: { artifactUrl: 'https://ci.example.com/app-release.apk' } }
|
|
9
|
+
{ platform: 'ios', app: { bundleId: 'com.apple.Preferences' } } // an app already on the device
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Choosing the app
|
|
13
|
+
|
|
14
|
+
- `app.appId` installs the app's newest build in Revyl. Upload builds with the Revyl CLI (`revyl build upload`) or from your CI.
|
|
15
|
+
- `app.buildId` pins one build, so every session runs the same binary.
|
|
16
|
+
- `app.artifactUrl` installs from an HTTPS URL.
|
|
17
|
+
- `app.bundleId` launches an app that is already on the device, such as Settings, instead of installing one.
|
|
18
|
+
|
|
19
|
+
## Devices
|
|
20
|
+
|
|
21
|
+
`device.model` and `device.osVersion` pick the simulator or emulator. Without them, Revyl picks its default for the platform. To run the same script on several devices, give each its own target and open them one after another or in parallel:
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
targets: {
|
|
25
|
+
'iphone-se': { platform: 'ios', app: { appId }, device: { model: 'iPhone SE (3rd generation)' } },
|
|
26
|
+
'iphone-16': { platform: 'ios', app: { appId }, device: { model: 'iPhone 16' } },
|
|
27
|
+
},
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
for (const name of ['iphone-se', 'iphone-16']) {
|
|
32
|
+
const session = await open(name);
|
|
33
|
+
try {
|
|
34
|
+
await session.validation('The onboarding screen fits without overlapping text');
|
|
35
|
+
} finally {
|
|
36
|
+
await session.close();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Steps
|
|
42
|
+
|
|
43
|
+
`instruction` and `validation` run as live steps on the device. Verify polls each step until Revyl reports it done, and cancels it on Revyl if it runs past its timeout.
|
|
44
|
+
|
|
45
|
+
After connection and any explicit bundle launch, `open()` makes a best-effort first-screen observation for up to 10 seconds, bounded by `startTimeout`. It looks for different RGB pixels in the central 80% of a PNG, excluding status and navigation bars. This is a blank-frame heuristic, not proof that the requested app is foreground or finished rendering: the runtime exposes no rendered-readiness signal. A timeout, unavailable screenshot, or undecodable image does not fail `open()`; it emits a `debug:` progress note through the configured logger and continues. `log: false` suppresses that note.
|
|
46
|
+
|
|
47
|
+
The generated mobile example starts with an instruction to wait for the first screen before validating it. For your own scripts, make the first instruction describe the screen or action you actually need; a screenshot with content can still show a loading screen or another app.
|
|
48
|
+
|
|
49
|
+
For Android Settings, instruct the agent to scroll or navigate until the OS version is visible before validating it. Opening About alone can leave the version offscreen.
|