run402 4.0.3 → 4.1.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 +3 -2
- package/cli.mjs +1 -1
- package/lib/allowance.mjs +12 -2
- package/lib/deploy-v2.mjs +123 -1
- package/lib/deploy.mjs +2 -0
- package/lib/doctor.mjs +31 -8
- package/lib/up.mjs +79 -1
- package/package.json +1 -1
- package/sdk/dist/namespaces/deploy.d.ts +15 -1
- package/sdk/dist/namespaces/deploy.d.ts.map +1 -1
- package/sdk/dist/namespaces/deploy.js +53 -0
- package/sdk/dist/namespaces/deploy.js.map +1 -1
- package/sdk/dist/namespaces/deploy.types.d.ts +77 -0
- package/sdk/dist/namespaces/deploy.types.d.ts.map +1 -1
- package/sdk/dist/scoped.d.ts +7 -1
- package/sdk/dist/scoped.d.ts.map +1 -1
- package/sdk/dist/scoped.js +7 -0
- package/sdk/dist/scoped.js.map +1 -1
package/README.md
CHANGED
|
@@ -75,6 +75,7 @@ run402 projects schema <id> # introspect tables + R
|
|
|
75
75
|
run402 sites deploy-dir ./dist # incremental upload (plan/commit transport)
|
|
76
76
|
run402 deploy apply --manifest app.json # one-call full stack deploy
|
|
77
77
|
run402 apply --manifest app.json --rehearse --json
|
|
78
|
+
run402 deploy verify op_... --project prj_... --wait
|
|
78
79
|
run402 deploy release active # inspect current-live release inventory
|
|
79
80
|
run402 deploy release diff --from empty --to active
|
|
80
81
|
run402 deploy diagnose --project prj_123 https://example.com/events --method GET
|
|
@@ -83,7 +84,7 @@ run402 subdomains claim my-app # → my-app.run402.com (auto-reas
|
|
|
83
84
|
```
|
|
84
85
|
|
|
85
86
|
`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.
|
|
86
|
-
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. 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`).
|
|
87
|
+
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`).
|
|
87
88
|
|
|
88
89
|
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`.
|
|
89
90
|
|
|
@@ -153,7 +154,7 @@ import { db, adminDb, auth, email, ai } from "@run402/functions";
|
|
|
153
154
|
|
|
154
155
|
### Same-origin web routes
|
|
155
156
|
|
|
156
|
-
`run402 deploy apply` accepts `site.public_paths` for clean static browser URLs and `routes.replace` for function ingress or exact method-aware static aliases. Release asset paths such as `events.html` are distinct from public paths such as `/events`: prefer `{ "site": { "replace": { "events.html": { "data": "<h1>Events</h1>" } }, "public_paths": { "mode": "explicit", "replace": { "/events": { "asset": "events.html" } } } } }` for ordinary clean static URLs. In explicit mode `/events.html` is not public unless separately declared; `mode: "implicit"` restores filename-derived reachability and can widen access. `routes.replace` is an array of route entries, not a path-keyed map. Use exact `/admin` plus final-wildcard `/admin/*` for a routed section root, narrow `/api/*` methods such as `["GET","POST","OPTIONS"]`, POST-only `/login` function routes, and exact static route targets like `{ "pattern": "/events", "methods": ["GET","HEAD"], "target": { "type": "static", "file": "events.html" } }` only for route-table alias behavior. Static route `file` is a release asset path, not a public path, URL, CAS hash, rewrite, or redirect. Routed functions use Node 22 Fetch Request -> Response; `req.url` is the full public URL on managed subdomains, deployment hosts, and verified custom domains. Direct `/functions/v1/:name` remains API-key protected. Known resolve literals include `host_missing`, `manifest_missing`, `active_release_missing`, `unsupported_manifest_version`, `path_error`, `none`, `static_exact`, `static_index`, `spa_fallback`, `spa_fallback_missing`, `route_function`, `route_static_alias`, and `route_method_miss`; known `authorization_result` values include `authorized`, `not_public`, `not_applicable`, `manifest_missing`, `target_missing`, `active_release_missing`, `unsupported_manifest_version`, `path_error`, `missing_cas_object`, `unfinalized_or_deleting_cas_object`, `size_mismatch`, and `unauthorized_cas_object`. Known `fallback_state` values include `active_release_missing`, `unsupported_manifest_version`, and `negative_cache_hit`; preserve unknown future strings. Release inventories may include `static_public_paths` with `public_path`, `asset_path`, `reachability_authority`, and `direct`; resolve may return the same reachability fields plus `authorization_result`, `cas_object`, `response_variant`, and route/static fields like `allow`, `route_pattern`, `target_type`, `target_name`, and `target_file`. Static route target warnings include `STATIC_ALIAS_SHADOWS_STATIC_PATH`, `STATIC_ALIAS_RELATIVE_ASSET_RISK`, `STATIC_ALIAS_DUPLICATE_CANONICAL_URL`, `STATIC_ALIAS_EXTENSIONLESS_NON_HTML`, and `STATIC_ALIAS_TABLE_NEAR_LIMIT`; inspect active routes, `static_public_paths`, and the backing `asset_path`. Runtime route failure codes to branch on: `ROUTE_MANIFEST_LOAD_FAILED`, `ROUTED_INVOKE_WORKER_SECRET_MISSING`, `ROUTED_INVOKE_AUTH_FAILED`, `ROUTED_ROUTE_STALE`, `ROUTE_METHOD_NOT_ALLOWED`, and `ROUTED_RESPONSE_TOO_LARGE`.
|
|
157
|
+
`run402 deploy apply` accepts `site.public_paths` for clean static browser URLs and `routes.replace` for function ingress or exact method-aware static aliases. Release asset paths such as `events.html` are distinct from public paths such as `/events`: prefer `{ "site": { "replace": { "events.html": { "data": "<h1>Events</h1>" } }, "public_paths": { "mode": "explicit", "replace": { "/events": { "asset": "events.html" } } } } }` for ordinary clean static URLs. In explicit mode `/events.html` is not public unless separately declared; `mode: "implicit"` restores filename-derived reachability and can widen access. `routes.replace` is an array of route entries, not a path-keyed map. Use exact `/admin` plus final-wildcard `/admin/*` for a routed section root, narrow `/api/*` methods such as `["GET","POST","OPTIONS"]`, POST-only `/login` function routes, and exact static route targets like `{ "pattern": "/events", "methods": ["GET","HEAD"], "target": { "type": "static", "file": "events.html" } }` only for route-table alias behavior. Static route `file` is a release asset path, not a public path, URL, CAS hash, rewrite, or redirect. Recipe — static home page + SPA shell: a root alias `{ "pattern": "/", "target": { "type": "static", "file": "home.html" } }` (with `home.html` at the site root) serves real static bytes at `GET /` (`route_static_alias`) while unmatched app routes keep the `index.html` shell (`spa_fallback`); expect non-blocking `STATIC_ALIAS_SHADOWS_STATIC_PATH` (warn) / `STATIC_ALIAS_DUPLICATE_CANONICAL_URL` (info) plan lints and verify with `run402 deploy resolve --url https://<your-site>/ --method GET`. Routed functions use Node 22 Fetch Request -> Response; `req.url` is the full public URL on managed subdomains, deployment hosts, and verified custom domains. Direct `/functions/v1/:name` remains API-key protected. Known resolve literals include `host_missing`, `manifest_missing`, `active_release_missing`, `unsupported_manifest_version`, `path_error`, `none`, `static_exact`, `static_index`, `spa_fallback`, `spa_fallback_missing`, `route_function`, `route_static_alias`, and `route_method_miss`; known `authorization_result` values include `authorized`, `not_public`, `not_applicable`, `manifest_missing`, `target_missing`, `active_release_missing`, `unsupported_manifest_version`, `path_error`, `missing_cas_object`, `unfinalized_or_deleting_cas_object`, `size_mismatch`, and `unauthorized_cas_object`. Known `fallback_state` values include `active_release_missing`, `unsupported_manifest_version`, and `negative_cache_hit`; preserve unknown future strings. Release inventories may include `static_public_paths` with `public_path`, `asset_path`, `reachability_authority`, and `direct`; resolve may return the same reachability fields plus `authorization_result`, `cas_object`, `response_variant`, and route/static fields like `allow`, `route_pattern`, `target_type`, `target_name`, and `target_file`. Static route target warnings include `STATIC_ALIAS_SHADOWS_STATIC_PATH`, `STATIC_ALIAS_RELATIVE_ASSET_RISK`, `STATIC_ALIAS_DUPLICATE_CANONICAL_URL`, `STATIC_ALIAS_EXTENSIONLESS_NON_HTML`, and `STATIC_ALIAS_TABLE_NEAR_LIMIT`; inspect active routes, `static_public_paths`, and the backing `asset_path`. Runtime route failure codes to branch on: `ROUTE_MANIFEST_LOAD_FAILED`, `ROUTED_INVOKE_WORKER_SECRET_MISSING`, `ROUTED_INVOKE_AUTH_FAILED`, `ROUTED_ROUTE_STALE`, `ROUTE_METHOD_NOT_ALLOWED`, and `ROUTED_RESPONSE_TOO_LARGE`.
|
|
157
158
|
|
|
158
159
|
### Secrets
|
|
159
160
|
|
package/cli.mjs
CHANGED
|
@@ -27,7 +27,7 @@ Commands:
|
|
|
27
27
|
credentials Manage local credential material (project-keys)
|
|
28
28
|
allowance Manage your agent allowance (create, fund, balance, status)
|
|
29
29
|
tier Manage tier subscription (status, set)
|
|
30
|
-
projects Manage projects (provision, list,
|
|
30
|
+
projects Manage projects (provision, list, get, sql, delete)
|
|
31
31
|
apply Alias for deploy apply; supports --rehearse for migration rehearsal
|
|
32
32
|
snapshots Create/list/restore/delete project data snapshots
|
|
33
33
|
branches Create/list/renew/delete contained project branches
|
package/lib/allowance.mjs
CHANGED
|
@@ -242,7 +242,17 @@ async function balance() {
|
|
|
242
242
|
}, null, 2));
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
-
async function exportAddr() {
|
|
245
|
+
async function exportAddr(args = []) {
|
|
246
|
+
const parsedArgs = normalizeArgv(args);
|
|
247
|
+
assertKnownFlags(parsedArgs, [], []);
|
|
248
|
+
const bare = positionalArgs(parsedArgs, []);
|
|
249
|
+
if (bare.length > 0) {
|
|
250
|
+
fail({
|
|
251
|
+
code: "BAD_USAGE",
|
|
252
|
+
message: `Unexpected argument for allowance export: ${bare[0]}`,
|
|
253
|
+
details: { argument: bare[0] },
|
|
254
|
+
});
|
|
255
|
+
}
|
|
246
256
|
try {
|
|
247
257
|
const address = await getSdk().allowance.export();
|
|
248
258
|
console.log(JSON.stringify({ address }));
|
|
@@ -333,7 +343,7 @@ export async function run(sub, args) {
|
|
|
333
343
|
case "create": await create(); break;
|
|
334
344
|
case "fund": await fund(); break;
|
|
335
345
|
case "balance": await balance(); break;
|
|
336
|
-
case "export": await exportAddr(); break;
|
|
346
|
+
case "export": await exportAddr(args); break;
|
|
337
347
|
case "checkout": await checkout(args); break;
|
|
338
348
|
case "history": await history(args); break;
|
|
339
349
|
default:
|
package/lib/deploy-v2.mjs
CHANGED
|
@@ -190,6 +190,33 @@ Output:
|
|
|
190
190
|
stdout: { "events": [...] }
|
|
191
191
|
`;
|
|
192
192
|
|
|
193
|
+
const VERIFY_HELP = `run402 deploy verify — Verify gateway and edge coherence
|
|
194
|
+
|
|
195
|
+
Usage:
|
|
196
|
+
run402 deploy verify <operation_id> [--project <id>] [--wait] [--timeout <seconds>] [--json]
|
|
197
|
+
run402 deploy verify --operation <operation_id> [--project <id>] [--wait] [--timeout <seconds>] [--json]
|
|
198
|
+
|
|
199
|
+
Checks that the gateway's live release and the edge pointers observed by
|
|
200
|
+
Run402 agree for the deployment operation. Use --wait to poll until the report
|
|
201
|
+
is coherent or the timeout elapses.
|
|
202
|
+
|
|
203
|
+
Options:
|
|
204
|
+
--operation <id> Operation id to verify. Equivalent to the positional id.
|
|
205
|
+
--project <id> Project ID that owns the operation (default: active project)
|
|
206
|
+
--wait Poll until coherent or timeout
|
|
207
|
+
--timeout <seconds> Maximum wait time with --wait (default 60)
|
|
208
|
+
--json No-op compatibility flag; output is always JSON
|
|
209
|
+
|
|
210
|
+
Exit codes:
|
|
211
|
+
0 coherent
|
|
212
|
+
2 valid report, but not coherent before timeout
|
|
213
|
+
1 usage, auth, network, or gateway error
|
|
214
|
+
|
|
215
|
+
Output:
|
|
216
|
+
stdout: { "status": "coherent"|"not_coherent", "coherent": boolean, "report": {...} }
|
|
217
|
+
stderr: one JSON poll progress line per attempt when --wait is set
|
|
218
|
+
`;
|
|
219
|
+
|
|
193
220
|
const REHEARSE_HELP = `run402 deploy rehearse — Run a persisted plan on a contained branch
|
|
194
221
|
|
|
195
222
|
Usage:
|
|
@@ -301,6 +328,7 @@ export async function runDeployV2(sub, args) {
|
|
|
301
328
|
if (sub === "resume") return await resumeCmd(args);
|
|
302
329
|
if (sub === "list") return await listCmd(args);
|
|
303
330
|
if (sub === "events") return await eventsCmd(args);
|
|
331
|
+
if (sub === "verify") return await verifyCmd(args);
|
|
304
332
|
if (sub === "release") return await releaseCmd(args);
|
|
305
333
|
if (sub === "diagnose") return await diagnoseCmd(args);
|
|
306
334
|
if (sub === "resolve") return await resolveCmd(args);
|
|
@@ -1594,6 +1622,100 @@ async function eventsCmd(args) {
|
|
|
1594
1622
|
}
|
|
1595
1623
|
}
|
|
1596
1624
|
|
|
1625
|
+
async function verifyCmd(args) {
|
|
1626
|
+
const parsed = parseDeploySubcommandArgs(args, {
|
|
1627
|
+
command: "deploy verify",
|
|
1628
|
+
help: VERIFY_HELP,
|
|
1629
|
+
valueFlags: ["--project", "--operation", "--timeout"],
|
|
1630
|
+
booleanFlags: ["--wait", "--json"],
|
|
1631
|
+
});
|
|
1632
|
+
const positionals = expectPositionals(parsed.positionals, {
|
|
1633
|
+
command: "run402 deploy verify <operation_id> [--project <id>] [--wait]",
|
|
1634
|
+
max: 1,
|
|
1635
|
+
});
|
|
1636
|
+
const positionalOperationId = positionals[0] ?? null;
|
|
1637
|
+
const flaggedOperationId = parsed.flags["--operation"] ?? null;
|
|
1638
|
+
if (positionalOperationId && flaggedOperationId && positionalOperationId !== flaggedOperationId) {
|
|
1639
|
+
fail({
|
|
1640
|
+
code: "BAD_USAGE",
|
|
1641
|
+
message: "Pass the operation id either positionally or with --operation, not both.",
|
|
1642
|
+
details: { positional: positionalOperationId, operation: flaggedOperationId },
|
|
1643
|
+
});
|
|
1644
|
+
}
|
|
1645
|
+
const operationId = flaggedOperationId ?? positionalOperationId;
|
|
1646
|
+
if (!operationId) {
|
|
1647
|
+
fail({
|
|
1648
|
+
code: "BAD_USAGE",
|
|
1649
|
+
message: "Missing <operation_id>.",
|
|
1650
|
+
hint: "run402 deploy verify <operation_id> [--project <id>] [--wait]",
|
|
1651
|
+
});
|
|
1652
|
+
}
|
|
1653
|
+
const project = resolveProjectId(parsed.flags["--project"] ?? null);
|
|
1654
|
+
const wait = Boolean(parsed.flags["--wait"]);
|
|
1655
|
+
const timeoutSeconds = parsed.flags["--timeout"] === undefined
|
|
1656
|
+
? 60
|
|
1657
|
+
: parsePositiveInt(parsed.flags["--timeout"], "--timeout");
|
|
1658
|
+
|
|
1659
|
+
allowanceAuthHeaders("/apply/v1/operations");
|
|
1660
|
+
|
|
1661
|
+
try {
|
|
1662
|
+
let result;
|
|
1663
|
+
if (wait) {
|
|
1664
|
+
result = await getSdk()._applyEngine.waitEdgeCoherent(operationId, {
|
|
1665
|
+
project,
|
|
1666
|
+
timeoutMs: timeoutSeconds * 1000,
|
|
1667
|
+
onPoll: (event) => {
|
|
1668
|
+
console.error(JSON.stringify({
|
|
1669
|
+
type: "deploy.verify.poll",
|
|
1670
|
+
coherent: event.report.coherent,
|
|
1671
|
+
attempts: event.attempts,
|
|
1672
|
+
elapsed_ms: event.elapsedMs,
|
|
1673
|
+
pending_count: event.report.pending_count,
|
|
1674
|
+
path_count: event.report.path_count,
|
|
1675
|
+
total_path_count: event.report.total_path_count,
|
|
1676
|
+
paths_truncated: event.report.paths_truncated,
|
|
1677
|
+
paths: summarizeEdgeCoherencePaths(event.report.paths),
|
|
1678
|
+
}));
|
|
1679
|
+
},
|
|
1680
|
+
});
|
|
1681
|
+
} else {
|
|
1682
|
+
const report = await getSdk()._applyEngine.edgeCoherence(operationId, { project });
|
|
1683
|
+
result = { coherent: report.coherent, attempts: 1, elapsedMs: 0, report };
|
|
1684
|
+
}
|
|
1685
|
+
const output = {
|
|
1686
|
+
status: result.coherent ? "coherent" : "not_coherent",
|
|
1687
|
+
coherent: result.coherent,
|
|
1688
|
+
operation_id: result.report.operation_id,
|
|
1689
|
+
project_id: result.report.project_id,
|
|
1690
|
+
attempts: result.attempts,
|
|
1691
|
+
elapsed_ms: result.elapsedMs,
|
|
1692
|
+
report: result.report,
|
|
1693
|
+
};
|
|
1694
|
+
console.log(JSON.stringify(output, null, 2));
|
|
1695
|
+
if (!result.coherent) process.exitCode = 2;
|
|
1696
|
+
} catch (err) {
|
|
1697
|
+
reportSdkError(err);
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
function summarizeEdgeCoherencePaths(paths) {
|
|
1702
|
+
if (!Array.isArray(paths)) return [];
|
|
1703
|
+
return paths.map((path) => ({
|
|
1704
|
+
path: path.path,
|
|
1705
|
+
host: path.host,
|
|
1706
|
+
state: path.state,
|
|
1707
|
+
observed_confidence: path.observed_confidence,
|
|
1708
|
+
expected_release_id: path.expected_release_id,
|
|
1709
|
+
observed_release_id: path.observed_release_id,
|
|
1710
|
+
expected_release_generation: path.expected_release_generation,
|
|
1711
|
+
observed_release_generation: path.observed_release_generation,
|
|
1712
|
+
status: path.status,
|
|
1713
|
+
x_cache: path.x_cache,
|
|
1714
|
+
age_seconds: path.age_seconds,
|
|
1715
|
+
error: path.error,
|
|
1716
|
+
}));
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1597
1719
|
async function releaseCmd(args) {
|
|
1598
1720
|
const action = args[0];
|
|
1599
1721
|
if (!action || action === "--help" || action === "-h") {
|
|
@@ -1843,7 +1965,7 @@ function parseDeploySubcommandArgs(rawArgs, { command, help, valueFlags = [], bo
|
|
|
1843
1965
|
const args = normalizeArgv(rawArgs);
|
|
1844
1966
|
const valueFlagSet = new Set(valueFlags);
|
|
1845
1967
|
const booleanFlagSet = new Set(booleanFlags);
|
|
1846
|
-
const numericFlagSet = new Set(["--limit", "--site-limit"]);
|
|
1968
|
+
const numericFlagSet = new Set(["--limit", "--site-limit", "--timeout"]);
|
|
1847
1969
|
const allowedFlags = new Set([...valueFlags, ...booleanFlags, "--help", "-h"]);
|
|
1848
1970
|
const flags = {};
|
|
1849
1971
|
const positionals = [];
|
package/lib/deploy.mjs
CHANGED
|
@@ -11,6 +11,7 @@ Subcommands:
|
|
|
11
11
|
resume <operation_id> Resume a stuck operation
|
|
12
12
|
list [--project <id>] List recent deploy operations
|
|
13
13
|
events <operation_id> Fetch event stream for an operation
|
|
14
|
+
verify <operation_id> Verify gateway and edge coherence
|
|
14
15
|
diagnose <url> Diagnose public URL routing
|
|
15
16
|
resolve --url <url> Low-level resolve diagnostics
|
|
16
17
|
release ... Inspect release inventory and diffs
|
|
@@ -55,6 +56,7 @@ export async function run(args) {
|
|
|
55
56
|
case "resume":
|
|
56
57
|
case "list":
|
|
57
58
|
case "events":
|
|
59
|
+
case "verify":
|
|
58
60
|
case "diagnose":
|
|
59
61
|
case "resolve":
|
|
60
62
|
case "release": {
|
package/lib/doctor.mjs
CHANGED
|
@@ -56,6 +56,13 @@ Exit codes:
|
|
|
56
56
|
1 — one or more checks failed (details in output)
|
|
57
57
|
`;
|
|
58
58
|
|
|
59
|
+
function redactAllowanceForDiagnostics(allowance) {
|
|
60
|
+
if (!allowance || typeof allowance !== "object") return allowance;
|
|
61
|
+
const safe = { ...allowance };
|
|
62
|
+
delete safe.privateKey;
|
|
63
|
+
return safe;
|
|
64
|
+
}
|
|
65
|
+
|
|
59
66
|
export async function run(sub, args = []) {
|
|
60
67
|
const all = [sub, ...args].filter(Boolean);
|
|
61
68
|
if (all.includes("--help") || all.includes("-h")) {
|
|
@@ -113,8 +120,9 @@ export async function run(sub, args = []) {
|
|
|
113
120
|
value: {
|
|
114
121
|
rail: allowance.rail,
|
|
115
122
|
// Don't surface amounts or addresses unless --verbose; agents
|
|
116
|
-
// checking for config presence don't need wallet details.
|
|
117
|
-
|
|
123
|
+
// checking for config presence don't need wallet details. Never
|
|
124
|
+
// include keystore secrets in diagnostics, even in verbose mode.
|
|
125
|
+
...(verbose && { details: redactAllowanceForDiagnostics(allowance) }),
|
|
118
126
|
},
|
|
119
127
|
});
|
|
120
128
|
} else {
|
|
@@ -182,19 +190,34 @@ export async function run(sub, args = []) {
|
|
|
182
190
|
const sdk = getSdk();
|
|
183
191
|
const tier = await sdk.tier.status();
|
|
184
192
|
const tierName = tier?.tier ?? null;
|
|
185
|
-
|
|
193
|
+
const lifecycle = tier?.organization_lifecycle_state ?? null;
|
|
194
|
+
const active = tier?.active === true;
|
|
195
|
+
if (tierName && active && lifecycle === "active") {
|
|
186
196
|
checks.push({
|
|
187
197
|
name: "tier",
|
|
188
198
|
status: "ok",
|
|
189
|
-
value: { tier: tierName },
|
|
199
|
+
value: { tier: tierName, active, organization_lifecycle_state: lifecycle },
|
|
190
200
|
});
|
|
191
201
|
} else {
|
|
202
|
+
const status = lifecycle && lifecycle !== "active"
|
|
203
|
+
? lifecycle
|
|
204
|
+
: tierName && !active
|
|
205
|
+
? "inactive"
|
|
206
|
+
: tierName && lifecycle === null
|
|
207
|
+
? "unknown"
|
|
208
|
+
: tierName ?? "missing";
|
|
192
209
|
checks.push({
|
|
193
210
|
name: "tier",
|
|
194
|
-
status
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
211
|
+
status,
|
|
212
|
+
value: {
|
|
213
|
+
tier: tierName,
|
|
214
|
+
active,
|
|
215
|
+
organization_lifecycle_state: lifecycle,
|
|
216
|
+
lease_expires_at: tier?.lease_expires_at ?? null,
|
|
217
|
+
},
|
|
218
|
+
hint: lifecycle === null && tierName
|
|
219
|
+
? "Tier resolved, but organization lifecycle could not be determined. Check `run402 tier status` before assuming the account is healthy."
|
|
220
|
+
: "Run 'run402 tier set prototype' to subscribe, renew, or reactivate the tier.",
|
|
198
221
|
});
|
|
199
222
|
}
|
|
200
223
|
} catch (err) {
|
package/lib/up.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import { createUpdateCheckScheduler, emitUpdateNotice } from "./update-check.mjs
|
|
|
8
8
|
const HELP = `run402 up — Provision/link/deploy the current app
|
|
9
9
|
|
|
10
10
|
Usage:
|
|
11
|
-
run402 up [repo-or-path] [--name <name>] [--project <id>] [--manifest <path>] [--dir <path>] [--tier <tier>] [-y|--yes] [--check|--print-spec|--plan|--require-plan <id>] [--human|--json-stream] [--quiet]
|
|
11
|
+
run402 up [repo-or-path] [--name <name>] [--project <id>] [--manifest <path>] [--dir <path>] [--tier <tier>] [-y|--yes] [--check|--print-spec|--plan|--require-plan <id>] [--verify] [--human|--json-stream] [--quiet]
|
|
12
12
|
run402 up verify [repo-or-path] [--project <id>] [--manifest <path>] [--dir <path>] [--human|--json-stream]
|
|
13
13
|
|
|
14
14
|
Options:
|
|
@@ -44,6 +44,8 @@ Options:
|
|
|
44
44
|
--propagation-budget-s <n>
|
|
45
45
|
Maximum wall-clock seconds to wait for fresh edge
|
|
46
46
|
propagation during app HTTP verification (default 120).
|
|
47
|
+
--verify After a deploy apply, wait for gateway/edge release
|
|
48
|
+
coherence and attach the report to the final result.
|
|
47
49
|
--no-propagation-wait
|
|
48
50
|
Return propagation_pending immediately when the edge is
|
|
49
51
|
still settling.
|
|
@@ -121,6 +123,7 @@ export async function run(args = []) {
|
|
|
121
123
|
"--allow-warnings",
|
|
122
124
|
"--allow-prune",
|
|
123
125
|
"--allow-shell-build",
|
|
126
|
+
"--verify",
|
|
124
127
|
"--no-propagation-wait",
|
|
125
128
|
"--json",
|
|
126
129
|
"--human",
|
|
@@ -206,6 +209,7 @@ export async function run(args = []) {
|
|
|
206
209
|
const quiet = parsed.includes("--quiet") || parsed.includes("--final-only") || jsonStream;
|
|
207
210
|
const mode = parseExecutionMode(parsed);
|
|
208
211
|
const dryRun = parsed.includes("--dry-run");
|
|
212
|
+
const verifyEdge = parsed.includes("--verify");
|
|
209
213
|
if (human && (parsed.includes("--json") || jsonStream)) {
|
|
210
214
|
fail({
|
|
211
215
|
code: "BAD_USAGE",
|
|
@@ -220,6 +224,13 @@ export async function run(args = []) {
|
|
|
220
224
|
details: { flag: "--dry-run" },
|
|
221
225
|
});
|
|
222
226
|
}
|
|
227
|
+
if (verifyEdge && (dryRun || isNonApplyingMode(mode))) {
|
|
228
|
+
fail({
|
|
229
|
+
code: "BAD_USAGE",
|
|
230
|
+
message: "--verify can only be used when run402 up applies a deploy.",
|
|
231
|
+
details: { flag: "--verify", mode: dryRun ? "dry-run" : mode },
|
|
232
|
+
});
|
|
233
|
+
}
|
|
223
234
|
if (isApplyReviewedMode(mode) && (parsed.includes("--allow-warnings") || parsed.includes("--allow-warning"))) {
|
|
224
235
|
fail({
|
|
225
236
|
code: "BAD_USAGE",
|
|
@@ -263,6 +274,14 @@ export async function run(args = []) {
|
|
|
263
274
|
console.error(JSON.stringify(event));
|
|
264
275
|
},
|
|
265
276
|
});
|
|
277
|
+
const edgeWait = verifyEdge
|
|
278
|
+
? await attachEdgeVerification(result, {
|
|
279
|
+
sdk,
|
|
280
|
+
timeoutSeconds: propagationBudgetSeconds ?? 120,
|
|
281
|
+
jsonStream,
|
|
282
|
+
quiet,
|
|
283
|
+
})
|
|
284
|
+
: null;
|
|
266
285
|
if (jsonStream) {
|
|
267
286
|
console.log(JSON.stringify({ type: "run402.up.result", result }));
|
|
268
287
|
} else if (mode === "printSpec") {
|
|
@@ -277,6 +296,9 @@ export async function run(args = []) {
|
|
|
277
296
|
if (shouldExitNonZeroForUpResult(result)) {
|
|
278
297
|
process.exitCode = 1;
|
|
279
298
|
}
|
|
299
|
+
if (edgeWait && !edgeWait.coherent && process.exitCode !== 1) {
|
|
300
|
+
process.exitCode = 2;
|
|
301
|
+
}
|
|
280
302
|
} catch (err) {
|
|
281
303
|
reportSdkError(err);
|
|
282
304
|
}
|
|
@@ -415,6 +437,62 @@ function isApplyReviewedMode(mode) {
|
|
|
415
437
|
return mode && typeof mode === "object" && mode.kind === "applyReviewed";
|
|
416
438
|
}
|
|
417
439
|
|
|
440
|
+
function isNonApplyingMode(mode) {
|
|
441
|
+
return mode === "check" || mode === "printSpec" || mode === "plan";
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
async function attachEdgeVerification(result, { sdk, timeoutSeconds, jsonStream, quiet }) {
|
|
445
|
+
const deploy = result?.result?.deploy;
|
|
446
|
+
const operationId = deploy?.operation_id;
|
|
447
|
+
const projectId = result?.result?.project_id;
|
|
448
|
+
if (!operationId || !projectId || String(operationId).startsWith("core:")) {
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
const scoped = await sdk.project(projectId);
|
|
452
|
+
const wait = await scoped.apply.waitEdgeCoherent(operationId, {
|
|
453
|
+
timeoutMs: timeoutSeconds * 1000,
|
|
454
|
+
onPoll: (event) => {
|
|
455
|
+
const line = {
|
|
456
|
+
type: "deploy.verify.poll",
|
|
457
|
+
coherent: event.report.coherent,
|
|
458
|
+
attempts: event.attempts,
|
|
459
|
+
elapsed_ms: event.elapsedMs,
|
|
460
|
+
pending_count: event.report.pending_count,
|
|
461
|
+
path_count: event.report.path_count,
|
|
462
|
+
total_path_count: event.report.total_path_count,
|
|
463
|
+
paths_truncated: event.report.paths_truncated,
|
|
464
|
+
paths: summarizeEdgeCoherencePaths(event.report.paths),
|
|
465
|
+
};
|
|
466
|
+
if (jsonStream) console.log(JSON.stringify({ type: "action.event", event: line }));
|
|
467
|
+
else if (!quiet) console.error(JSON.stringify(line));
|
|
468
|
+
},
|
|
469
|
+
});
|
|
470
|
+
result.result.edge_coherence = wait;
|
|
471
|
+
result.result.deploy = {
|
|
472
|
+
...deploy,
|
|
473
|
+
edge_coherence: wait.report,
|
|
474
|
+
};
|
|
475
|
+
return wait;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function summarizeEdgeCoherencePaths(paths) {
|
|
479
|
+
if (!Array.isArray(paths)) return [];
|
|
480
|
+
return paths.map((path) => ({
|
|
481
|
+
path: path.path,
|
|
482
|
+
host: path.host,
|
|
483
|
+
state: path.state,
|
|
484
|
+
observed_confidence: path.observed_confidence,
|
|
485
|
+
expected_release_id: path.expected_release_id,
|
|
486
|
+
observed_release_id: path.observed_release_id,
|
|
487
|
+
expected_release_generation: path.expected_release_generation,
|
|
488
|
+
observed_release_generation: path.observed_release_generation,
|
|
489
|
+
status: path.status,
|
|
490
|
+
x_cache: path.x_cache,
|
|
491
|
+
age_seconds: path.age_seconds,
|
|
492
|
+
error: path.error,
|
|
493
|
+
}));
|
|
494
|
+
}
|
|
495
|
+
|
|
418
496
|
function collectRepeatedValues(args, flag) {
|
|
419
497
|
const values = [];
|
|
420
498
|
for (let i = 0; i < args.length; i++) {
|
package/package.json
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* behavior; this file is the implementation.
|
|
20
20
|
*/
|
|
21
21
|
import type { Client } from "../kernel.js";
|
|
22
|
-
import type { ApplyOptions, ActiveReleaseInventory, DeployEvent, DeployEventsResponse, DeployListOptions, DeployListResponse, DeployOperation, DeployResult, DeployResolveOptions, DeployResolveResponse, OperationSnapshot, PlanResponse, PromoteOptions, PromoteResult, ReleaseDiffOptions, ReleaseInventory, ReleaseInventoryByIdOptions, ReleaseInventoryOptions, ReleaseSpec, ReleaseToReleaseDiff, RehearsePlanOptions, RehearsePlanResult, StartOptions } from "./deploy.types.js";
|
|
22
|
+
import type { ApplyOptions, ActiveReleaseInventory, DeployEvent, DeployEventsResponse, DeployListOptions, DeployListResponse, DeployOperation, DeployResult, DeployResolveOptions, DeployResolveResponse, EdgeCoherenceReport, EdgeCoherenceWaitOptions, EdgeCoherenceWaitResult, OperationSnapshot, PlanResponse, PromoteOptions, PromoteResult, ReleaseDiffOptions, ReleaseInventory, ReleaseInventoryByIdOptions, ReleaseInventoryOptions, ReleaseSpec, ReleaseToReleaseDiff, RehearsePlanOptions, RehearsePlanResult, StartOptions } from "./deploy.types.js";
|
|
23
23
|
export declare class Deploy {
|
|
24
24
|
private readonly client;
|
|
25
25
|
constructor(client: Client);
|
|
@@ -149,6 +149,20 @@ export declare class Deploy {
|
|
|
149
149
|
events(operationId: string, opts: {
|
|
150
150
|
project: string;
|
|
151
151
|
}): Promise<DeployEventsResponse>;
|
|
152
|
+
/**
|
|
153
|
+
* Probe whether the gateway and edge pointers agree on the active release
|
|
154
|
+
* for an operation. The endpoint requires `apikey` auth, so `project` is
|
|
155
|
+
* required. Use `waitEdgeCoherent` when you want bounded polling.
|
|
156
|
+
*/
|
|
157
|
+
edgeCoherence(operationId: string, opts: {
|
|
158
|
+
project: string;
|
|
159
|
+
}): Promise<EdgeCoherenceReport>;
|
|
160
|
+
/**
|
|
161
|
+
* Poll `edgeCoherence` until the report is coherent or the timeout elapses.
|
|
162
|
+
* Returns the last report either way so automation can explain stale paths
|
|
163
|
+
* without issuing a second request.
|
|
164
|
+
*/
|
|
165
|
+
waitEdgeCoherent(operationId: string, opts: EdgeCoherenceWaitOptions): Promise<EdgeCoherenceWaitResult>;
|
|
152
166
|
/**
|
|
153
167
|
* Fetch a release inventory by id. The endpoint requires `apikey` auth, so
|
|
154
168
|
* pass the owning project id. `siteLimit` controls how many site paths the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/namespaces/deploy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAkB3C,OAAO,KAAK,EACV,YAAY,EACZ,sBAAsB,EAQtB,WAAW,EACX,oBAAoB,EAEpB,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,EACf,YAAY,EACZ,oBAAoB,EACpB,qBAAqB,
|
|
1
|
+
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/namespaces/deploy.ts"],"names":[],"mappings":"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"}
|
|
@@ -376,6 +376,57 @@ export class Deploy {
|
|
|
376
376
|
throw translateDeployError(err, "events", null, operationId);
|
|
377
377
|
}
|
|
378
378
|
}
|
|
379
|
+
/**
|
|
380
|
+
* Probe whether the gateway and edge pointers agree on the active release
|
|
381
|
+
* for an operation. The endpoint requires `apikey` auth, so `project` is
|
|
382
|
+
* required. Use `waitEdgeCoherent` when you want bounded polling.
|
|
383
|
+
*/
|
|
384
|
+
async edgeCoherence(operationId, opts) {
|
|
385
|
+
if (!operationId || !operationId.startsWith("op_")) {
|
|
386
|
+
throw new Run402DeployError(`Invalid operation id: "${operationId}"`, {
|
|
387
|
+
code: "OPERATION_NOT_FOUND",
|
|
388
|
+
retryable: false,
|
|
389
|
+
context: "verifying deploy edge coherence",
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
if (!opts?.project) {
|
|
393
|
+
throw new LocalError("apply.edgeCoherence requires a project id ({ project: 'prj_...' })", "verifying deploy edge coherence");
|
|
394
|
+
}
|
|
395
|
+
const headers = await apikeyHeaders(this.client, opts.project);
|
|
396
|
+
try {
|
|
397
|
+
return await this.client.request(`/apply/v1/operations/${encodeURIComponent(operationId)}/edge-coherence`, { headers, context: "verifying deploy edge coherence" });
|
|
398
|
+
}
|
|
399
|
+
catch (err) {
|
|
400
|
+
throw translateDeployError(err, "edge-coherence", null, operationId);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Poll `edgeCoherence` until the report is coherent or the timeout elapses.
|
|
405
|
+
* Returns the last report either way so automation can explain stale paths
|
|
406
|
+
* without issuing a second request.
|
|
407
|
+
*/
|
|
408
|
+
async waitEdgeCoherent(operationId, opts) {
|
|
409
|
+
const timeoutMs = normalizePositiveSafeIntegerQueryOption(opts?.timeoutMs ?? 60_000, "apply.waitEdgeCoherent timeoutMs", "waiting for deploy edge coherence") ?? 60_000;
|
|
410
|
+
const intervalMs = normalizePositiveSafeIntegerQueryOption(opts?.intervalMs ?? 1_000, "apply.waitEdgeCoherent intervalMs", "waiting for deploy edge coherence") ?? 1_000;
|
|
411
|
+
if (!opts?.project) {
|
|
412
|
+
throw new LocalError("apply.waitEdgeCoherent requires a project id ({ project: 'prj_...' })", "waiting for deploy edge coherence");
|
|
413
|
+
}
|
|
414
|
+
const start = Date.now();
|
|
415
|
+
let attempts = 0;
|
|
416
|
+
while (true) {
|
|
417
|
+
attempts += 1;
|
|
418
|
+
const report = await this.edgeCoherence(operationId, { project: opts.project });
|
|
419
|
+
const elapsedMs = Date.now() - start;
|
|
420
|
+
opts.onPoll?.({ attempts, elapsedMs, report });
|
|
421
|
+
if (report.coherent) {
|
|
422
|
+
return { coherent: true, attempts, elapsedMs, report };
|
|
423
|
+
}
|
|
424
|
+
if (elapsedMs >= timeoutMs) {
|
|
425
|
+
return { coherent: false, attempts, elapsedMs, report };
|
|
426
|
+
}
|
|
427
|
+
await sleep(Math.min(intervalMs, Math.max(timeoutMs - elapsedMs, 0)));
|
|
428
|
+
}
|
|
429
|
+
}
|
|
379
430
|
/**
|
|
380
431
|
* Fetch a release inventory by id. The endpoint requires `apikey` auth, so
|
|
381
432
|
* pass the owning project id. `siteLimit` controls how many site paths the
|
|
@@ -1645,6 +1696,7 @@ async function pollUntilReady(client, commit, diff, warnings, emit, projectId, s
|
|
|
1645
1696
|
diff,
|
|
1646
1697
|
warnings,
|
|
1647
1698
|
...(commit.subdomain_bindings ? { subdomain_bindings: commit.subdomain_bindings } : {}),
|
|
1699
|
+
...(commit.edge ? { edge: commit.edge } : {}),
|
|
1648
1700
|
};
|
|
1649
1701
|
}
|
|
1650
1702
|
const opHeaders = projectId ? await apikeyHeaders(client, projectId) : {};
|
|
@@ -1744,6 +1796,7 @@ async function pollSnapshotUntilReady(client, initial, diff, warnings, emit, pro
|
|
|
1744
1796
|
diff,
|
|
1745
1797
|
warnings,
|
|
1746
1798
|
...(snapshot.subdomain_bindings ? { subdomain_bindings: snapshot.subdomain_bindings } : {}),
|
|
1799
|
+
...(snapshot.edge ? { edge: snapshot.edge } : {}),
|
|
1747
1800
|
};
|
|
1748
1801
|
}
|
|
1749
1802
|
if (TERMINAL_STATUSES.includes(snapshot.status)) {
|