run402 4.79.2 → 4.80.1
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/README.md +22 -4
- package/gitvault-surface.json +1 -1
- package/lib/command-manifest.mjs +4 -1
- package/lib/deploy-v2.mjs +18 -2
- package/lib/doctor.mjs +164 -30
- package/lib/errors.mjs +5 -3
- package/lib/functions.mjs +153 -42
- package/lib/gitvault-scaffold.mjs +19 -7
- package/lib/harness-context.mjs +6 -2
- package/lib/image.mjs +3 -1
- package/lib/init.mjs +5 -3
- package/lib/logs.mjs +125 -96
- package/lib/repos.mjs +40 -8
- package/lib/up.mjs +67 -21
- package/package.json +1 -1
- package/sdk/dist/actions.d.ts +27 -9
- package/sdk/dist/actions.d.ts.map +1 -1
- package/sdk/dist/errors.d.ts +1 -1
- package/sdk/dist/errors.d.ts.map +1 -1
- package/sdk/dist/errors.js.map +1 -1
- package/sdk/dist/index.d.ts +1 -1
- package/sdk/dist/index.d.ts.map +1 -1
- package/sdk/dist/index.js +1 -1
- package/sdk/dist/index.js.map +1 -1
- package/sdk/dist/namespaces/deploy.d.ts.map +1 -1
- package/sdk/dist/namespaces/deploy.js +35 -3
- package/sdk/dist/namespaces/deploy.js.map +1 -1
- package/sdk/dist/namespaces/deploy.types.d.ts +15 -8
- package/sdk/dist/namespaces/deploy.types.d.ts.map +1 -1
- package/sdk/dist/namespaces/deploy.types.js +1 -1
- package/sdk/dist/namespaces/deploy.types.js.map +1 -1
- package/sdk/dist/namespaces/errors.d.ts +4 -2
- package/sdk/dist/namespaces/errors.d.ts.map +1 -1
- package/sdk/dist/namespaces/errors.js +19 -9
- package/sdk/dist/namespaces/errors.js.map +1 -1
- package/sdk/dist/namespaces/functions.d.ts +17 -1
- package/sdk/dist/namespaces/functions.d.ts.map +1 -1
- package/sdk/dist/namespaces/functions.js +131 -2
- package/sdk/dist/namespaces/functions.js.map +1 -1
- package/sdk/dist/namespaces/functions.types.d.ts +62 -0
- package/sdk/dist/namespaces/functions.types.d.ts.map +1 -1
- package/sdk/dist/namespaces/gitvault.d.ts +38 -0
- package/sdk/dist/namespaces/gitvault.d.ts.map +1 -1
- package/sdk/dist/namespaces/gitvault.js +122 -14
- package/sdk/dist/namespaces/gitvault.js.map +1 -1
- package/sdk/dist/node/actions-node.d.ts.map +1 -1
- package/sdk/dist/node/actions-node.js +176 -16
- package/sdk/dist/node/actions-node.js.map +1 -1
- package/sdk/dist/node/client-detect.d.ts +16 -1
- package/sdk/dist/node/client-detect.d.ts.map +1 -1
- package/sdk/dist/node/client-detect.js +26 -9
- package/sdk/dist/node/client-detect.js.map +1 -1
- package/sdk/dist/node/deploy-manifest.d.ts +42 -0
- package/sdk/dist/node/deploy-manifest.d.ts.map +1 -1
- package/sdk/dist/node/deploy-manifest.js +175 -1
- package/sdk/dist/node/deploy-manifest.js.map +1 -1
- package/sdk/dist/node/index.d.ts +2 -2
- package/sdk/dist/node/index.d.ts.map +1 -1
- package/sdk/dist/node/index.js +1 -1
- package/sdk/dist/node/index.js.map +1 -1
- package/sdk/dist/scoped.d.ts +2 -1
- package/sdk/dist/scoped.d.ts.map +1 -1
- package/sdk/dist/scoped.js +3 -0
- package/sdk/dist/scoped.js.map +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Command-line interface for [Run402](https://run402.com) — provision Postgres d
|
|
|
4
4
|
|
|
5
5
|
The CLI lets an agent act as its own first-class Run402 principal rather than borrowing a human account. Identity records who invoked an action; organization roles, grants, delegates, freshness, and spend policy determine what that principal may do. People remain first-class owners and collaborators without becoming routine CLI operators.
|
|
6
6
|
|
|
7
|
-
For the full CLI reference (every flag, every subcommand)
|
|
7
|
+
For the full CLI reference (every flag, every subcommand) start at **<https://docs.run402.com/llms-cli.txt>** — the index holds the first-deploy contract and links one fetchable `/llms-cli-<slice>.txt` per topic (deploy, commands, repos, orgs, functions, assets, ops, errors, frontend, platform); <https://docs.run402.com/llms-cli-full.txt> is the whole reference as one document.
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
@@ -44,6 +44,7 @@ run402 up --manifest run402.deploy.ts --plan
|
|
|
44
44
|
run402 up --manifest run402.deploy.ts --require-plan plan_...
|
|
45
45
|
run402 up --manifest run402.deploy.json --project prj_...
|
|
46
46
|
run402 up verify --project prj_...
|
|
47
|
+
run402 up --nested -y # app root inside another repo: its own nested repo + run402 remote
|
|
47
48
|
```
|
|
48
49
|
|
|
49
50
|
`up` is a thin CLI shim over the Node SDK action runner. It discovers `run402.json`, `run402.deploy.json`, then `app.json`. A `run402.json` carrying the app schema/app markers uses the app-install graph; a release-shaped `run402.json` is normalized through the same ReleaseSpec path as `deploy apply` for compatibility. It validates the deploy input before any mutation, resolves the project as `--project` → `.run402/project.json` → manifest `project_id` → approved creation from `--name` → approved active-project fallback, then applies the manifest. `--name` is creation/link metadata only; it is not a manifest field and never renames an existing project. When everything is already configured, plain `run402 up` deploys. Non-interactive recursive prerequisites/local writes require `-y/--yes`.
|
|
@@ -52,6 +53,8 @@ For app manifests with `verify.http[]`, `up` runs HTTP checks after deploy. Fres
|
|
|
52
53
|
|
|
53
54
|
For typed `run402.deploy.ts` configs, pass `--manifest` explicitly because TypeScript/JavaScript configs execute local code. Use `--check` for local-only import/normalize/file validation, `--print-spec` to inspect the normalized `ReleaseSpec`, `--plan` for a gateway-reviewed non-deploying plan, and `--require-plan <plan_id>` to apply only that reviewed intent. Warning flags are not used with `--require-plan`; the reviewed plan binds the exact warning/destructive set. Run402 Core skips Cloud allowance/tier prerequisites and fails closed when no Core project is selected.
|
|
54
55
|
|
|
56
|
+
Every mode (and `deploy apply`) verifies the files the manifest references before any gateway call: migration `sql_path`/`sql_file`, function `source`/`files`, site `{ path }` entries and `dir()` targets, `assets.put` sources. A missing one is `MANIFEST_FILE_MISSING` (`details.missing[]` = `{ field_path, path, kind }`, one `create_file` next action per file). With no manifest in the working directory, `UP_MANIFEST_REQUIRED` looks one directory down (`details.nearby_manifests[]`, a leading `run_in_directory` next action such as `run402 up --check --dir <dir>`); `--manifest <path>` to a missing file is a typed `MANIFEST_NOT_FOUND` with a `create_manifest` next action. An app root that lies inside another repository is never scaffolded into it: the skip carries a `create_nested_repo` next action, and `--nested` gives the app its own nested repository (`git init -b main`, the `run402` remote, the first push) plus exactly one line in the enclosing repository's local `.git/info/exclude`, nothing else touched. `up` also sets this principal's display name when it has none: `RUN402_AGENT_NAME` (overrides an existing name), else the detected client (`claude-code`, `codex`, `cursor`, `grok`, or `RUN402_CLIENT=<name>` for one with no marker), and `result.identity` reports `detected` plus `detection: { applied, reason }` either way.
|
|
57
|
+
|
|
55
58
|
### repos — host-blind encrypted git repos (zero deploy ceremony)
|
|
56
59
|
|
|
57
60
|
One noun, fourteen verbs (`repo` singular resolves identically). `run402 gitvault <verb>` answers `COMMAND_MOVED`/`COMMAND_REMOVED`.
|
|
@@ -63,12 +66,13 @@ git push -u origin main # allocates the vault on first pus
|
|
|
63
66
|
|
|
64
67
|
# or, one call: provision + allocate + scaffold, nothing deployed
|
|
65
68
|
run402 repos create my-notes
|
|
69
|
+
run402 repos create --nested --project prj_xyz # app inside another repo: its own nested repo + remote
|
|
66
70
|
run402 repos view --human
|
|
67
71
|
run402 repos list --org org_1a2b3c
|
|
68
72
|
run402 repos delete --project prj_xyz --force # refuses without --force while the vault holds generations
|
|
69
73
|
```
|
|
70
74
|
|
|
71
|
-
A hosted git remote, encrypted before it leaves the machine — no deploy, no manifest, no app. `origin` is claimed additively — an existing `origin` is never touched, the run402 remote falls back to `run402` instead. Every mutating `repos` verb (`create`, `rename`, `delete`, `snapshot`, `policy`, `mirror`, `gc`, `handoff`, `resume`, `invite`, `join`) is CLI/OpenClaw-only by design — no MCP tool exists or will exist for them (one-shot recovery receipts, immutable generations, irreversible delete, single-use bearer keys). Three READ-ONLY tools do exist — `repos_view`, `repos_list_heads`, `repos_fsck` — teaching only `repos` spellings. See `run402 repos --help` for the full tiered surface (common: `create`/`view`/`list`; occasional: `snapshot`/`mirror`/`recover`/`handoff`/`resume`/`invite`/`join`; lifecycle: `rename`/`delete`; maintenance: `fsck`/`gc`/`access`/`policy`), and the CLI reference's `repos` section for the terminal-loss statement and the progressive backup warning.
|
|
75
|
+
A hosted git remote, encrypted before it leaves the machine — no deploy, no manifest, no app. `origin` is claimed additively — an existing `origin` is never touched, the run402 remote falls back to `run402` instead. A directory inside another repository is never scaffolded into that repository: the skip carries a `create_nested_repo` next action, `--nested` makes it its own nested repository (one line appended to the enclosing repository's local `.git/info/exclude`, nothing else touched), and `create` prints a `git push` next action only for a remote it actually added. Every mutating `repos` verb (`create`, `rename`, `delete`, `snapshot`, `policy`, `mirror`, `gc`, `handoff`, `resume`, `invite`, `join`) is CLI/OpenClaw-only by design — no MCP tool exists or will exist for them (one-shot recovery receipts, immutable generations, irreversible delete, single-use bearer keys). Three READ-ONLY tools do exist — `repos_view`, `repos_list_heads`, `repos_fsck` — teaching only `repos` spellings. See `run402 repos --help` for the full tiered surface (common: `create`/`view`/`list`; occasional: `snapshot`/`mirror`/`recover`/`handoff`/`resume`/`invite`/`join`; lifecycle: `rename`/`delete`; maintenance: `fsck`/`gc`/`access`/`policy`), and the CLI reference's `repos` section for the terminal-loss statement and the progressive backup warning.
|
|
72
76
|
|
|
73
77
|
**Handoff / resume.** `run402 repos handoff [--note-file <path>]` captures the actual working tree (staged/unstaged/untracked, like `git stash push -u`) into the vault and mints a single-use bearer key (`kgh1_…`, printed to stdout exactly once); `run402 repos resume <kgh1_…>` claims it on another machine, clones fresh, and reapplies the exact dirty state with `git stash apply --index`. No shared keystore, no shared allowance — the key is the whole handoff. On a wallet with no active tier, `resume` first folds the same cold-start chain `create` does (allowance → faucet → one x402 prototype payment); `--no-init` opts out, and the claim itself never waits on it. Sensitive untracked files (`.env`, `*.pem`, SSH/AWS/GPG dirs, …) are excluded from capture by default (`--include-sensitive <glob>` re-admits one). See the reference `run402.com/llms-cli.txt` for the full note-schema and flag reference.
|
|
74
78
|
|
|
@@ -169,7 +173,7 @@ Promote success means the origin pointer is active; mutable public URLs may stil
|
|
|
169
173
|
`deploy-dir` hashes each file client-side and only uploads bytes the gateway doesn't already have. Re-deploying an unchanged tree returns immediately with `bytes_uploaded: 0`. Progress events stream to stderr.
|
|
170
174
|
Release inspection commands print `{ release: ... }` or `{ diff: ... }` (raw payload, no envelope — see the "Output Contract" section in [llms-cli.txt](llms-cli.txt)); use them after deploys to compare release inventory without starting another mutation. `deploy verify` prints the canonical edge-coherence report and exits 2 when the report is valid but not yet coherent. Inventories include `release_generation`, `static_manifest_sha256`, and nullable `static_manifest_metadata`; diffs include `static_assets` counters such as unchanged/changed/added/removed and CAS byte reuse. `deploy diagnose` / `deploy resolve --url` print URL-first diagnostics with `would_serve`, `diagnostic_status`, `match`, warnings, `edge_propagation`, and next steps; host misses are successful diagnostic calls with `would_serve: false`. Stable-host resolve fields can include `authorization_result`, `cas_object`, `response_variant`, `allow`, `route_pattern`, `target_type`, `target_name`, `target_file`, and `edge_propagation` (`settled`, `propagating`, or `sync_pending`).
|
|
171
175
|
|
|
172
|
-
For database-bearing changes, use `run402 apply --manifest app.json --rehearse --json` before commit. It creates a contained branch, applies the candidate plan there, runs checks, and exits nonzero on a failed rehearsal. Manual restore points live under `run402 snapshots create|list|get|restore|delete`; temporary data branches live under `run402 branches create|list|renew|delete`.
|
|
176
|
+
For database-bearing changes, use `run402 apply --manifest app.json --rehearse --json` before commit. It creates a contained branch, applies the candidate plan there, runs checks, and exits nonzero on a failed rehearsal. The automatic rehearsal inside `up` / `apply` reports `rehearsal: { status: "skipped", reason }` when there is nothing to protect: `no_live_release` on a first deploy, `no_migrations`, or `migrations_unchanged` when every migration is already applied with an identical checksum (a page-only redeploy that still carries its migrations ships in seconds). `PUBLIC_ROUTED_FUNCTION` is informational (`requires_confirmation: false`) and needs no `--allow-warning`; only warnings with `requires_confirmation: true` do. Manual restore points live under `run402 snapshots create|list|get|restore|delete`; temporary data branches live under `run402 branches create|list|renew|delete`.
|
|
173
177
|
|
|
174
178
|
### GitHub Actions OIDC deploys
|
|
175
179
|
|
|
@@ -220,12 +224,17 @@ run402 functions deploy <id> my-fn --file fn.ts \
|
|
|
220
224
|
--schedule "*/15 * * * *" \
|
|
221
225
|
--deps "stripe,zod@^3"
|
|
222
226
|
run402 functions logs <id> my-fn --tail 100 --request-id req_abc123 --follow
|
|
227
|
+
run402 functions logs --request-id req_abc123 --project <id> # <name> optional with --request-id: every function is searched
|
|
228
|
+
run402 functions logs my-fn --project <id> --all # include the Lambda INIT_START / REPORT lines
|
|
229
|
+
run402 logs --request-id req_abc123 --project <id> # the x-run402-request-id response header, no function name needed
|
|
223
230
|
run402 functions invoke my-fn --project <id> --body-file request.json
|
|
224
231
|
run402 functions invoke paid-fn --project <id> --body-file request.json --idempotency-key paid:call:123 --wait
|
|
225
232
|
run402 functions rebuild <id> my-fn # refresh ONE function onto the current runtime
|
|
226
233
|
run402 functions rebuild <id> --all # refresh every function in the project
|
|
227
234
|
```
|
|
228
235
|
|
|
236
|
+
Log reads are app-first: every entry carries `origin: "app" | "platform"`, the Lambda runtime lines (INIT_START, START/END/REPORT RequestId, billed duration) are hidden by default (`--app`), `--platform` shows only them and `--all` the raw stream; `hidden` counts what was dropped and a `hint` explains an empty result. `--request-id` accepts `req_`, `fnrun_`, and `fnatt_` ids, `--since` takes an ISO timestamp or epoch ms, and `run402 logs` rejects unknown flags.
|
|
237
|
+
|
|
229
238
|
Inline `--body <json>` is validated before any request. Prefer `--body-file <path>` for agents and Windows `cmd.exe`; the file path avoids shell quoting entirely. Invalid or empty JSON fails locally instead of invoking the function with corrupted input.
|
|
230
239
|
|
|
231
240
|
`functions rebuild` is opt-in and never changes your source: it re-bundles the stored source against the platform's current runtime/entry-wrapper (deps pinned to the exact versions recorded at deploy), so a gateway-side wrapper fix (e.g. an SSR `auth.*` fix) reaches an already-deployed function — a plain redeploy with unchanged source does not. The source `code_hash` is unchanged and no new release is created. Functions deployed before dependency locking return `CANNOT_REBUILD_UNLOCKED_DEPS`; redeploy those from source instead. `run402 doctor` flags functions on a stale runtime.
|
|
@@ -303,6 +312,15 @@ run402 tier set hobby # $5 / 30 days
|
|
|
303
312
|
run402 billing checkout <org_id> --product tier --tier hobby # Stripe alternative
|
|
304
313
|
```
|
|
305
314
|
|
|
315
|
+
### Doctor
|
|
316
|
+
|
|
317
|
+
```bash
|
|
318
|
+
run402 doctor # { ok, blocking[], warnings[], checks[] }
|
|
319
|
+
run402 doctor --refresh # live npm check for a newer run402
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
`ok` is structural: `true` exactly when `blocking[]` is empty. Every check carries `severity: "blocking" | "advisory" | "info"`; advisory findings (an unbound operator passkey, a degraded recovery posture on another org, a stale CLI, gitvault gaps) land in `warnings[]` without changing `ok` or the exit code. The `tier` check's `status` is a fixed vocabulary (`ok | inactive | frozen | past_due | dormant | purged | missing | unknown | error`, never a tier name; see `value.tier` / `value.lifecycle`), and a wallet whose own org holds no tier but can reach another org's projects reports `missing` as advisory (`TIER_MISSING_ON_OWN_ORG`).
|
|
323
|
+
|
|
306
324
|
## State
|
|
307
325
|
|
|
308
326
|
Local state lives at:
|
|
@@ -357,7 +375,7 @@ The canonical, comprehensive CLI reference — every flag, every subcommand, edg
|
|
|
357
375
|
|
|
358
376
|
**<https://docs.run402.com/llms-cli.txt>**
|
|
359
377
|
|
|
360
|
-
Same content also at [`cli/llms-cli.txt`](./llms-cli.txt) in the repo. Treat
|
|
378
|
+
Same content also at [`cli/llms-cli.txt`](./llms-cli.txt) (index), [`cli/llms-cli-<slice>.txt`](./) (topic slices) and [`cli/llms-cli-full.txt`](./llms-cli-full.txt) (everything) in the repo. Treat those files as authoritative; this README is a quick-orientation landing page.
|
|
361
379
|
|
|
362
380
|
## Other interfaces
|
|
363
381
|
|
package/gitvault-surface.json
CHANGED
package/lib/command-manifest.mjs
CHANGED
|
@@ -485,7 +485,10 @@ export const COMMAND_MANIFEST = [
|
|
|
485
485
|
// without rejecting it," which this satisfies.
|
|
486
486
|
{ path: ["doctor"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["--no-scan"], runStyle: "merged" },
|
|
487
487
|
{ path: ["webhook-secret", "rotate"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [] },
|
|
488
|
-
|
|
488
|
+
// `run402 logs [<function>] --request-id <id>`: the optional positional is
|
|
489
|
+
// the function name (`run402 errors` samples print it that way); it is
|
|
490
|
+
// never a project id.
|
|
491
|
+
{ path: ["logs"], positionals: [p("function", { required: false })], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["--request-id", "req_gate123"], runStyle: "merged" },
|
|
489
492
|
];
|
|
490
493
|
|
|
491
494
|
// Families deliberately absent from the manifest, consumed by the gate's
|
package/lib/deploy-v2.mjs
CHANGED
|
@@ -26,6 +26,7 @@ import { existsSync, fstatSync, readFileSync } from "node:fs";
|
|
|
26
26
|
import { resolve, dirname, extname, isAbsolute } from "node:path";
|
|
27
27
|
import {
|
|
28
28
|
applyWithGitvault,
|
|
29
|
+
assertLocalFileReferencesExist,
|
|
29
30
|
buildDeployResolveSummary,
|
|
30
31
|
githubActionsCredentials,
|
|
31
32
|
loadDeployManifest,
|
|
@@ -1182,6 +1183,18 @@ async function applyCmd(args) {
|
|
|
1182
1183
|
const releaseSpec = normalizedManifest.spec;
|
|
1183
1184
|
const idempotencyKey = normalizedManifest.idempotencyKey;
|
|
1184
1185
|
|
|
1186
|
+
// Filesystem-reference check: every function source, site file / dir and
|
|
1187
|
+
// asset path the normalized spec still points at must exist. One `stat`
|
|
1188
|
+
// per reference, so it runs in every mode — `--check` / `--print-spec`
|
|
1189
|
+
// included — before any gateway call (MANIFEST_FILE_MISSING).
|
|
1190
|
+
try {
|
|
1191
|
+
await assertLocalFileReferencesExist(releaseSpec, {
|
|
1192
|
+
...(manifestPath ? { manifestPath } : {}),
|
|
1193
|
+
});
|
|
1194
|
+
} catch (err) {
|
|
1195
|
+
reportSdkError(err);
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1185
1198
|
if (opts.mode === "check") {
|
|
1186
1199
|
console.log(JSON.stringify({
|
|
1187
1200
|
ok: true,
|
|
@@ -1531,12 +1544,15 @@ function enhanceDeployWarningError(err) {
|
|
|
1531
1544
|
|
|
1532
1545
|
const ROUTE_WARNING_GUIDANCE = {
|
|
1533
1546
|
PUBLIC_ROUTED_FUNCTION: {
|
|
1534
|
-
|
|
1547
|
+
// Informational on the gateway (requires_confirmation: false): it never
|
|
1548
|
+
// blocks a deploy by itself, so the guidance must not teach an
|
|
1549
|
+
// acknowledgement flag the warning does not need.
|
|
1550
|
+
hint: "A deploy route makes a function public same-origin browser ingress; direct /functions/v1/:name remains API-key protected. This warning is informational and needs no --allow-warning; another warning with requires_confirmation: true is what blocked this plan.",
|
|
1535
1551
|
next_actions: [
|
|
1536
1552
|
nextAction("edit_request", { why: "Review application auth and authorization in the routed function." }),
|
|
1537
1553
|
nextAction("edit_request", { why: "Add CSRF protection for cookie-authenticated POST/PUT/PATCH/DELETE routes." }),
|
|
1538
1554
|
nextAction("edit_request", { why: "Implement CORS and OPTIONS explicitly when cross-origin callers are intended." }),
|
|
1539
|
-
retryAction("run402 deploy apply --allow-
|
|
1555
|
+
retryAction("run402 deploy apply --allow-warning <code>", "Acknowledge only the warning that has requires_confirmation: true, after review."),
|
|
1540
1556
|
],
|
|
1541
1557
|
},
|
|
1542
1558
|
ROUTE_TARGET_CARRIED_FORWARD: {
|
package/lib/doctor.mjs
CHANGED
|
@@ -79,7 +79,25 @@ Usage:
|
|
|
79
79
|
run402 --wallet <profile> doctor --buzz --buzz-agent <npub-or-hex>
|
|
80
80
|
|
|
81
81
|
Output:
|
|
82
|
-
Stdout is a JSON report
|
|
82
|
+
Stdout is a JSON report:
|
|
83
|
+
{ ok,
|
|
84
|
+
blocking: [{ check, status, message, hint? }],
|
|
85
|
+
warnings: [{ check, code?, message, hint? }],
|
|
86
|
+
checks: [{ name, status, severity, value?, hint?, message? }] }
|
|
87
|
+
\`ok\` answers ONE question — can this agent ship from here — and is true
|
|
88
|
+
exactly when \`blocking[]\` is empty. Every check carries a \`severity\`:
|
|
89
|
+
blocking would stop a deploy: config_dir / allowance missing or error,
|
|
90
|
+
api_reachable error, tier inactive / frozen / past_due /
|
|
91
|
+
dormant / missing / error, error-severity source_scan findings
|
|
92
|
+
advisory a warning that never stops a deploy: operator_health,
|
|
93
|
+
recovery_posture, gitvault, runtime_staleness, cli_update gaps
|
|
94
|
+
info ok / skipped / unknown
|
|
95
|
+
Agents: branch on \`ok\`; read \`warnings[]\` (one entry per gap) for the
|
|
96
|
+
non-blocking gaps and \`blocking[]\` for what to fix when \`ok\` is false.
|
|
97
|
+
\`--only\` and \`--refresh\` runs have the same shape. The tier check's
|
|
98
|
+
status is a fixed vocabulary — ok | inactive | frozen | past_due | dormant
|
|
99
|
+
| purged | missing | unknown | error — never a tier name; the tier name
|
|
100
|
+
and raw lifecycle ride in value.tier / value.lifecycle.
|
|
83
101
|
Buzz mode adds { mode: "buzz", contract_id, generated_at, mutation_state,
|
|
84
102
|
binding, telemetry } and uses check status ok|warning|blocked.
|
|
85
103
|
|
|
@@ -130,7 +148,10 @@ Checks performed:
|
|
|
130
148
|
- Allowance is configured and on a valid rail (x402 / mpp)
|
|
131
149
|
- Keystore has at least one wallet
|
|
132
150
|
- API_BASE is reachable (network check via /health)
|
|
133
|
-
- Active tier resolves and is not 'past_due' / 'frozen'
|
|
151
|
+
- Active tier resolves and is not 'past_due' / 'frozen' / 'dormant'. A wallet
|
|
152
|
+
whose OWN organization holds no tier but that can reach projects owned by
|
|
153
|
+
another organization (membership or grant) reports status 'missing' as an
|
|
154
|
+
advisory, not a blocker — deploys to those projects are unaffected.
|
|
134
155
|
- Function runtime staleness: deployed functions running an older platform
|
|
135
156
|
runtime than the current gateway build (refresh with 'run402 functions
|
|
136
157
|
rebuild --all'; re-bundles from your stored source, no source change)
|
|
@@ -157,10 +178,89 @@ Buzz mode checks (in order):
|
|
|
157
178
|
operator, runtime-staleness, and source-tree checks.
|
|
158
179
|
|
|
159
180
|
Exit codes:
|
|
160
|
-
0 —
|
|
161
|
-
1 — one or more checks
|
|
181
|
+
0 — ok: true (advisory warnings never change the exit code)
|
|
182
|
+
1 — ok: false (one or more blocking checks; see blocking[])
|
|
162
183
|
`;
|
|
163
184
|
|
|
185
|
+
/**
|
|
186
|
+
* Check statuses that carry no finding at all. Everything else is either the
|
|
187
|
+
* one advisory status (`warning`) or a blocking status — so a NEW status
|
|
188
|
+
* string that slips into a check (a lifecycle name, a tier name, a typo)
|
|
189
|
+
* fails closed as blocking instead of silently passing.
|
|
190
|
+
*/
|
|
191
|
+
const INFO_STATUSES = new Set(["ok", "skipped", "unknown"]);
|
|
192
|
+
|
|
193
|
+
/** Non-active organization lifecycle states the gateway can report (`organization-lifecycle` spec); each is its own blocking tier status. */
|
|
194
|
+
const TIER_LIFECYCLE_STATUSES = new Set(["past_due", "frozen", "dormant", "purged"]);
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* The severity contract, in one place: `blocking` = would stop this agent
|
|
198
|
+
* from shipping; `advisory` = a gap worth surfacing that never stops a
|
|
199
|
+
* deploy; `info` = nothing to act on. A check may pin its own `severity`
|
|
200
|
+
* explicitly (the tier check does, for the missing-but-can-reach-projects
|
|
201
|
+
* shape); otherwise it is derived from `status`.
|
|
202
|
+
*/
|
|
203
|
+
function severityOf(check) {
|
|
204
|
+
if (check.severity === "blocking" || check.severity === "advisory" || check.severity === "info") return check.severity;
|
|
205
|
+
if (INFO_STATUSES.has(check.status)) return "info";
|
|
206
|
+
if (check.status === "warning") return "advisory";
|
|
207
|
+
return "blocking";
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Stable codes for advisory checks that report ONE finding (no `value.gaps`), keyed by check name. */
|
|
211
|
+
const ADVISORY_CODES = {
|
|
212
|
+
cli_update: "CLI_UPDATE_AVAILABLE",
|
|
213
|
+
runtime_staleness: "FUNCTION_RUNTIME_STALE",
|
|
214
|
+
source_scan: "SOURCE_SCAN_WARNINGS",
|
|
215
|
+
tier: "TIER_MISSING_ON_OWN_ORG",
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Fold the per-check report into the agent-facing envelope:
|
|
220
|
+
* `{ ok, blocking[], warnings[], checks[] }`. `warnings[]` carries one entry
|
|
221
|
+
* per gap string of every advisory check (plus one for an advisory check
|
|
222
|
+
* with no gaps); `blocking[]` carries one entry per blocking check and is
|
|
223
|
+
* empty exactly when `ok` is true. Pure — the same fold serves a full run,
|
|
224
|
+
* `--only`, and `--refresh`.
|
|
225
|
+
*/
|
|
226
|
+
export function buildDoctorReport(rawChecks) {
|
|
227
|
+
const checks = rawChecks.map((check) => {
|
|
228
|
+
const { name, status, severity: _pinned, ...rest } = check;
|
|
229
|
+
return { name, status, severity: severityOf(check), ...rest };
|
|
230
|
+
});
|
|
231
|
+
const blocking = [];
|
|
232
|
+
const warnings = [];
|
|
233
|
+
for (const check of checks) {
|
|
234
|
+
if (check.severity === "blocking") {
|
|
235
|
+
blocking.push({
|
|
236
|
+
check: check.name,
|
|
237
|
+
status: check.status,
|
|
238
|
+
message: check.message ?? `${check.name}: ${check.status}`,
|
|
239
|
+
...(check.hint && { hint: check.hint }),
|
|
240
|
+
});
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (check.severity !== "advisory") continue;
|
|
244
|
+
const gaps = Array.isArray(check.value?.gaps) ? check.value.gaps.filter((g) => typeof g === "string" && g.length > 0) : [];
|
|
245
|
+
if (gaps.length > 0) {
|
|
246
|
+
for (const gap of gaps) {
|
|
247
|
+
warnings.push({ check: check.name, message: gap, ...(check.hint && { hint: check.hint }) });
|
|
248
|
+
}
|
|
249
|
+
} else {
|
|
250
|
+
const code = check.code ?? ADVISORY_CODES[check.name];
|
|
251
|
+
const message = check.message ?? check.hint ?? `${check.name}: ${check.status}`;
|
|
252
|
+
warnings.push({
|
|
253
|
+
check: check.name,
|
|
254
|
+
...(code && { code }),
|
|
255
|
+
message,
|
|
256
|
+
// Only carry `hint` when it adds something the message does not.
|
|
257
|
+
...(check.hint && check.hint !== message && { hint: check.hint }),
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return { ok: blocking.length === 0, blocking, warnings, checks };
|
|
262
|
+
}
|
|
263
|
+
|
|
164
264
|
function redactAllowanceForDiagnostics(allowance) {
|
|
165
265
|
if (!allowance || typeof allowance !== "object") return allowance;
|
|
166
266
|
const safe = { ...allowance };
|
|
@@ -368,36 +468,71 @@ export async function run(sub, args = []) {
|
|
|
368
468
|
}
|
|
369
469
|
|
|
370
470
|
// 5. Active tier.
|
|
471
|
+
//
|
|
472
|
+
// `status` is a FIXED vocabulary — ok | inactive | frozen | past_due |
|
|
473
|
+
// dormant | purged | missing | unknown | error — never the tier name and
|
|
474
|
+
// never a raw gateway string: the old derivation fell through to
|
|
475
|
+
// `tierName ?? "missing"`, which could put a tier NAME ("team") in the
|
|
476
|
+
// status slot, and any status outside the ok-allowlist silently flipped
|
|
477
|
+
// the whole report to ok:false while looking benign. The tier name and the
|
|
478
|
+
// raw lifecycle ride in value.tier / value.lifecycle instead.
|
|
371
479
|
if (wanted("tier")) try {
|
|
372
480
|
const sdk = getSdk();
|
|
373
481
|
const tier = await sdk.tier.status();
|
|
374
|
-
const tierName = tier?.tier
|
|
375
|
-
const lifecycle = tier?.organization_lifecycle_state
|
|
482
|
+
const tierName = typeof tier?.tier === "string" && tier.tier.length > 0 ? tier.tier : null;
|
|
483
|
+
const lifecycle = typeof tier?.organization_lifecycle_state === "string" && tier.organization_lifecycle_state.length > 0
|
|
484
|
+
? tier.organization_lifecycle_state
|
|
485
|
+
: null;
|
|
376
486
|
const active = tier?.active === true;
|
|
377
|
-
|
|
487
|
+
// Projects this wallet can reach through org membership or a grant —
|
|
488
|
+
// the tier read already carries them. A wallet whose OWN org-of-one
|
|
489
|
+
// never bought a tier can still ship into another org's projects, so
|
|
490
|
+
// "missing" is only a blocker when there is nowhere to ship.
|
|
491
|
+
const reachableProjects = Array.isArray(tier?.projects) ? tier.projects.length : 0;
|
|
492
|
+
let status;
|
|
493
|
+
if (lifecycle !== null && lifecycle !== "active") {
|
|
494
|
+
status = TIER_LIFECYCLE_STATUSES.has(lifecycle) ? lifecycle : "inactive";
|
|
495
|
+
} else if (tierName === null) {
|
|
496
|
+
status = "missing";
|
|
497
|
+
} else if (!active) {
|
|
498
|
+
status = "inactive";
|
|
499
|
+
} else if (lifecycle === null) {
|
|
500
|
+
status = "unknown";
|
|
501
|
+
} else {
|
|
502
|
+
status = "ok";
|
|
503
|
+
}
|
|
504
|
+
const value = {
|
|
505
|
+
tier: tierName,
|
|
506
|
+
lifecycle,
|
|
507
|
+
active,
|
|
508
|
+
organization_lifecycle_state: lifecycle,
|
|
509
|
+
lease_expires_at: tier?.lease_expires_at ?? null,
|
|
510
|
+
reachable_projects: reachableProjects,
|
|
511
|
+
};
|
|
512
|
+
if (status === "ok") {
|
|
513
|
+
checks.push({ name: "tier", status, value });
|
|
514
|
+
} else if (status === "missing" && reachableProjects > 0) {
|
|
378
515
|
checks.push({
|
|
379
516
|
name: "tier",
|
|
380
|
-
status
|
|
381
|
-
|
|
517
|
+
status,
|
|
518
|
+
severity: "advisory",
|
|
519
|
+
value,
|
|
520
|
+
message: `this wallet's own organization holds no tier, but it can reach ${reachableProjects} project(s) owned by another organization (membership or grant) — deploys to those are unaffected`,
|
|
521
|
+
hint: "Run 'run402 tier set prototype' only if you want to provision projects under this wallet's own organization.",
|
|
382
522
|
});
|
|
383
523
|
} else {
|
|
384
|
-
const status = lifecycle && lifecycle !== "active"
|
|
385
|
-
? lifecycle
|
|
386
|
-
: tierName && !active
|
|
387
|
-
? "inactive"
|
|
388
|
-
: tierName && lifecycle === null
|
|
389
|
-
? "unknown"
|
|
390
|
-
: tierName ?? "missing";
|
|
391
524
|
checks.push({
|
|
392
525
|
name: "tier",
|
|
393
526
|
status,
|
|
394
|
-
value
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
527
|
+
value,
|
|
528
|
+
message: status === "unknown"
|
|
529
|
+
? "tier resolved, but the organization lifecycle could not be determined"
|
|
530
|
+
: status === "missing"
|
|
531
|
+
? "no tier on this wallet's organization and no reachable project — nothing can be provisioned or deployed from here"
|
|
532
|
+
: status === "inactive"
|
|
533
|
+
? `tier '${tierName ?? "(none)"}' is not active${lifecycle && lifecycle !== "active" ? ` (organization lifecycle '${lifecycle}')` : ""}`
|
|
534
|
+
: `organization lifecycle is '${lifecycle}' — the control plane is gated until the tier is reactivated`,
|
|
535
|
+
hint: status === "unknown"
|
|
401
536
|
? "Tier resolved, but organization lifecycle could not be determined. Check `run402 tier status` before assuming the account is healthy."
|
|
402
537
|
: "Run 'run402 tier set prototype' to subscribe, renew, or reactivate the tier.",
|
|
403
538
|
});
|
|
@@ -838,11 +973,10 @@ export async function run(sub, args = []) {
|
|
|
838
973
|
}
|
|
839
974
|
}
|
|
840
975
|
|
|
841
|
-
//
|
|
842
|
-
//
|
|
843
|
-
//
|
|
844
|
-
const
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
process.exit(allOk ? 0 : 1);
|
|
976
|
+
// `ok` is structural — "no check is blocking" — never an allowlist of
|
|
977
|
+
// status strings. Advisory warnings ride in `warnings[]` and never change
|
|
978
|
+
// the exit code; `blocking[]` is what to fix when `ok` is false.
|
|
979
|
+
const report = buildDoctorReport(checks);
|
|
980
|
+
console.log(JSON.stringify(report, null, 2));
|
|
981
|
+
process.exit(report.ok ? 0 : 1);
|
|
848
982
|
}
|
package/lib/errors.mjs
CHANGED
|
@@ -108,9 +108,11 @@ Exit codes (the promote gate — only when --fail-on-new is set):
|
|
|
108
108
|
from 1. Without --fail-on-new, failures are the usual exit 1.
|
|
109
109
|
|
|
110
110
|
Auth:
|
|
111
|
-
The addressed project's own
|
|
112
|
-
|
|
113
|
-
|
|
111
|
+
The addressed project's own service_key when it is cached locally; otherwise
|
|
112
|
+
your wallet / session / delegate, which the gateway authorizes with
|
|
113
|
+
project.read — an org member or teammate agent needs no project key. A key
|
|
114
|
+
for project A requesting project B's errors gets 403 (never a 404 that
|
|
115
|
+
leaks existence). Read-only; never lifecycle-gated.
|
|
114
116
|
|
|
115
117
|
The golden path — gate a promote:
|
|
116
118
|
run402 deploy promote --project <id> --release <rel>
|