run402 4.0.3 → 4.1.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/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/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/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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "run402",
3
- "version": "4.0.3",
3
+ "version": "4.1.0",
4
4
  "description": "CLI for Run402 — provision Postgres databases, deploy static sites, generate images, and manage wallets via x402 and MPP micropayments.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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":"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"}
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)) {