Skip to content

Commit 5df02ab

Browse files
committed
feat: add managed jobs surface
1 parent 969f2ea commit 5df02ab

23 files changed

Lines changed: 1030 additions & 11 deletions

AGENTS.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ When adding a new tool/command, add it to the `SURFACE` array **and** `SDK_BY_CA
6868
## Architecture
6969

7070
```
71-
@run402/sdk (typed TypeScript kernel — 20 namespaces, ~100 methods)
71+
@run402/sdk (typed TypeScript kernel — 21 namespaces, ~100 methods)
7272
7373
│ /index.ts (isomorphic: Node + sandbox)
7474
│ /node (Node-only: keystore + allowance + x402-wrapped fetch + fileSetFromDir)
@@ -93,9 +93,9 @@ The SDK is the canonical kernel — a single typed client with a `CredentialsPro
9393
- **`kernel.ts`** — Request function, `Client` interface. Only place that calls `globalThis.fetch`.
9494
- **`errors.ts`**`Run402Error` hierarchy: `PaymentRequired`, `ProjectNotFound`, `Unauthorized`, `ApiError`, `NetworkError`, `Run402DeployError` (the v1.34+ structured envelope from the deploy state machine). Never calls `process.exit`.
9595
- **`credentials.ts`**`CredentialsProvider` interface. Required: `getAuth`, `getProject`. Optional: `saveProject`, `updateProject`, `removeProject`, `setActiveProject`, `getActiveProject`, `readAllowance`, `saveAllowance`, `createAllowance`, `getAllowancePath`.
96-
- **`namespaces/*.ts`** — One class per resource group (projects, assets, functions, email, CI/OIDC, …). Namespaces hold a `Client` and expose typed methods. The canonical apply primitive lives at **`namespaces/deploy.ts`** (the file kept its historical name; the public-facing symbol is `r.project(id).apply`, with `r.deploy` retired in v2.0 in favor of an internal `_applyEngine`). Shared types in `deploy.types.ts` — see "Unified Apply" below.
96+
- **`namespaces/*.ts`** — One class per resource group (projects, assets, functions, jobs, email, CI/OIDC, …). Namespaces hold a `Client` and expose typed methods. The canonical apply primitive lives at **`namespaces/deploy.ts`** (the file kept its historical name; the public-facing symbol is `r.project(id).apply`, with `r.deploy` retired in v2.0 in favor of an internal `_applyEngine`). Shared types in `deploy.types.ts` — see "Unified Apply" below.
9797
- **`node/*.ts`** — Node-only entry point (`@run402/sdk/node`). Wraps `core/` keystore + allowance into `NodeCredentialsProvider`. Sets up x402-wrapped fetch via `createLazyPaidFetch()`. Adds `fileSetFromDir(path)` for filesystem byte sources and the deploy manifest adapter (`loadDeployManifest(path)`, `normalizeDeployManifest(input)`) for CLI/MCP-compatible JSON.
98-
- **`scoped.ts`**`ScopedRun402` sub-client. Returned by `r.project(id?)` and `r.useProject(id)`. Wraps every project-id-bearing namespace method with the id pre-bound, so `p.apply({ site })` (no `project`), `p.functions.list()`, `p.assets.put(key, src)` all "just work" once the scope is set. Caller-supplied `project_id` / `project` still wins (override-friendly). The unwrapped namespaces (`r.assets`, `r.functions`, …) keep their required-id signatures unchanged — scoped is sugar, not a replacement. The apply primitive is only exposed via the scoped client (`r.project(id).apply(spec)`); there is no public `r.deploy` since v2.0.
98+
- **`scoped.ts`**`ScopedRun402` sub-client. Returned by `r.project(id?)` and `r.useProject(id)`. Wraps every project-id-bearing namespace method with the id pre-bound, so `p.apply({ site })` (no `project`), `p.functions.list()`, `p.jobs.get(jobId)`, `p.assets.put(key, src)` all "just work" once the scope is set. Caller-supplied `project_id` / `project` still wins (override-friendly). The unwrapped namespaces (`r.assets`, `r.functions`, `r.jobs`, …) keep their required-id signatures unchanged — scoped is sugar, not a replacement. The apply primitive is only exposed via the scoped client (`r.project(id).apply(spec)`); there is no public `r.deploy` since v2.0.
9999

100100
### Project-scoped sub-client (`r.project(id?)`)
101101

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ const p = await r.project(project.project_id);
294294
await p.assets.put("hello.txt", { content: "hi" });
295295
```
296296

297-
The SDK is organised as 20 namespaces: `projects`, `assets`, `ci`, `sites`, `functions`, `secrets`, `subdomains`, `domains`, `email` (+ `webhooks`), `senderDomain`, `auth`, `apps`, `tier`, `billing`, `contracts`, `ai`, `allowance`, `service`, `admin`, plus the `r.project(id).apply` hero for atomic mixed writes (release slices + assets slice via `/apply/v1/*`). Every operation throws a typed `Run402Error` subclass on failure: `PaymentRequired`, `ProjectNotFound`, `Unauthorized`, `ApiError`, `NetworkError`, `LocalError`, `Run402DeployError`. `apply()` automatically re-plans safe current-base `BASE_RELEASE_CONFLICT` races and emits `apply.retry` progress events. See [`sdk/README.md`](./sdk/README.md).
297+
The SDK is organised as 21 namespaces: `projects`, `assets`, `ci`, `sites`, `functions`, `jobs`, `secrets`, `subdomains`, `domains`, `email` (+ `webhooks`), `senderDomain`, `auth`, `apps`, `tier`, `billing`, `contracts`, `ai`, `allowance`, `service`, `admin`, plus the `r.project(id).apply` hero for atomic mixed writes (release slices + assets slice via `/apply/v1/*`). Every operation throws a typed `Run402Error` subclass on failure: `PaymentRequired`, `ProjectNotFound`, `Unauthorized`, `ApiError`, `NetworkError`, `LocalError`, `Run402DeployError`. `apply()` automatically re-plans safe current-base `BASE_RELEASE_CONFLICT` races and emits `apply.retry` progress events. See [`sdk/README.md`](./sdk/README.md).
298298

299299
## CLI — `run402`
300300

@@ -441,6 +441,7 @@ The full MCP surface — every tool is a thin shim over an SDK call.
441441
| `update_function` | Update schedule / timeout / memory without redeploying code. |
442442
| `list_functions` / `delete_function` | List / remove functions. |
443443
| `set_secret` / `list_secrets` / `delete_secret` | Manage `process.env` secrets injected into all functions. Values are write-only; list returns keys and timestamps only. |
444+
| `jobs_submit` / `jobs_get` / `jobs_logs` / `jobs_cancel` | Submit and inspect fixed platform-managed jobs. Requests use the gateway jobs shape; the SDK supplies the required idempotency header. |
444445

445446
### Auth & email
446447

SKILL.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,7 @@ No `route_scopes` means no CI route-declaration authority. With route scopes, CI
316316
- **`update_function`** — change schedule / timeout / memory without redeploying code.
317317
- **`list_functions`** / **`delete_function`** — list / remove.
318318
- **`set_secret`** / **`list_secrets`** / **`delete_secret`**`process.env` secrets injected into every function. Values are write-only; `list_secrets` returns keys and timestamps only. Deploy specs use `secrets.require[]` as a dependency gate, not as a value carrier or per-function allowlist.
319+
- **`jobs_submit`** / **`jobs_get`** / **`jobs_logs`** / **`jobs_cancel`** — fixed platform-managed jobs. Submit the gateway-shaped request with `job_type`, `input["input.json"]`, and `max_cost_usd_micros`; this is not arbitrary Docker execution.
319320

320321
Function authoring limits per tier: prototype 10s / 128 MB / 1 scheduled fn / 15 min, hobby 30s / 256 MB / 3 / 5 min, team 60s / 512 MB / 10 / 1 min. Deploy preflights literal unified-deploy function values before plan/upload and returns structured `BAD_FIELD` details.
321322

cli-help.test.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ const MATRIX = {
6868
specific: ["deploy", "invoke", "logs", "update", "list", "delete"],
6969
},
7070
secrets: { shared: [], specific: ["set", "list", "delete"] },
71+
jobs: { shared: [], specific: ["submit", "get", "logs", "cancel"] },
7172
assets: {
7273
shared: [],
7374
specific: ["put", "get", "ls", "rm", "sign"],

cli/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,17 @@ run402 deploy apply --manifest run402.deploy.json # manifest uses secrets.requ
143143

144144
Secret values are write-only. `list` returns keys and timestamps only; deploy manifests should declare dependencies with `secrets.require` and never contain values.
145145

146+
### Jobs
147+
148+
```bash
149+
run402 jobs submit --file job.json --project prj_...
150+
run402 jobs get job_abc123 --project prj_...
151+
run402 jobs logs job_abc123 --project prj_... --tail 100
152+
run402 jobs cancel job_abc123 --project prj_...
153+
```
154+
155+
Jobs are fixed platform-managed runners, not arbitrary Docker execution. Submit the gateway-shaped JSON request (`job_type`, `input["input.json"]`, `max_cost_usd_micros`) and the CLI handles the required idempotency header through the SDK.
156+
146157
### Email
147158

148159
```bash

cli/cli.mjs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Commands:
2727
projects Manage projects (provision, list, query, inspect, delete)
2828
deploy Unified deploy operations (requires active tier)
2929
ci Link GitHub Actions OIDC deploy bindings
30+
jobs Submit and inspect fixed platform-managed jobs
3031
functions Manage serverless functions (deploy, invoke, logs, list, delete)
3132
secrets Manage project secrets (set, list, delete)
3233
assets Direct-to-S3 asset storage (put, get, ls, rm, sign, diagnose) — up to 5 TiB
@@ -52,6 +53,7 @@ Examples:
5253
run402 allowance create
5354
run402 allowance fund
5455
run402 deploy apply --manifest app.json
56+
run402 jobs submit --file job.json
5557
run402 projects list
5658
run402 projects sql <project_id> "SELECT * FROM users LIMIT 5"
5759
run402 functions deploy <project_id> my-fn --file handler.ts
@@ -129,6 +131,11 @@ switch (cmd) {
129131
await run(sub, rest);
130132
break;
131133
}
134+
case "jobs": {
135+
const { run } = await import("./lib/jobs.mjs");
136+
await run(sub, rest);
137+
break;
138+
}
132139
case "functions": {
133140
const { run } = await import("./lib/functions.mjs");
134141
await run(sub, rest);

0 commit comments

Comments
 (0)