run402 4.105.5 → 4.105.6
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/lib/up.mjs +2 -1
- package/package.json +1 -1
- package/sdk/dist/namespaces/deploy.d.ts.map +1 -1
- package/sdk/dist/namespaces/deploy.js +76 -13
- package/sdk/dist/namespaces/deploy.js.map +1 -1
- package/sdk/dist/namespaces/deploy.types.d.ts +10 -7
- package/sdk/dist/namespaces/deploy.types.d.ts.map +1 -1
- package/vault-surface.json +1 -1
package/lib/up.mjs
CHANGED
|
@@ -65,7 +65,8 @@ Options:
|
|
|
65
65
|
commits directly, and a plan whose migrations are all
|
|
66
66
|
already applied with an identical checksum is not
|
|
67
67
|
rehearsed either (result.deploy.rehearsal says which:
|
|
68
|
-
no_live_release / migrations_unchanged / no_migrations
|
|
68
|
+
no_live_release / migrations_unchanged / no_migrations /
|
|
69
|
+
ci_session_unsupported).
|
|
69
70
|
--repo-only Provision + scaffold the run402 remote + first push,
|
|
70
71
|
and stop there — no deploy. The vault-only track
|
|
71
72
|
(D8), composed through up instead of run402 repos
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "run402",
|
|
3
|
-
"version": "4.105.
|
|
3
|
+
"version": "4.105.6",
|
|
4
4
|
"description": "CLI for Run402 — full-stack backend infrastructure for AI agents: Postgres, auth, storage, serverless functions and atomic deploys. Paid with x402/MPP. Includes $0.03 image generation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/namespaces/deploy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/namespaces/deploy.ts"],"names":[],"mappings":"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"}
|
|
@@ -24,7 +24,7 @@ import { isGrantKeyCredentials } from "../grant-key-credentials.js";
|
|
|
24
24
|
import { isKnownBinaryContent, isUtf8StringSource } from "../validation.js";
|
|
25
25
|
import { assertCiDeployableSpec } from "./ci.js";
|
|
26
26
|
import { ROUTE_PRICING_NETWORKS, ROUTE_HTTP_METHODS, isModernPlanMigrationDiff, normalizeDeployResolveRequest, } from "./deploy.types.js";
|
|
27
|
-
import { ApiError, LocalError, NetworkError, PaymentRequired, Run402DeployError, Unauthorized, isTransferFreezeError, } from "../errors.js";
|
|
27
|
+
import { ApiError, LocalError, NetworkError, PaymentRequired, Run402DeployError, Unauthorized, isRun402Error, isTransferFreezeError, } from "../errors.js";
|
|
28
28
|
import { assertAssetMetadata, assertExifPolicy, } from "./assets-validation.js";
|
|
29
29
|
// ─── Constants ───────────────────────────────────────────────────────────────
|
|
30
30
|
const PLAN_BODY_LIMIT_BYTES = 5 * 1024 * 1024;
|
|
@@ -36,6 +36,10 @@ const APPLY_RETRY_DEFAULT_MAX_RETRIES = 2;
|
|
|
36
36
|
const APPLY_RETRY_BASE_DELAY_MS = 250;
|
|
37
37
|
const APPLY_RETRY_MAX_DELAY_MS = 2_000;
|
|
38
38
|
const APPLY_RETRY_JITTER_MS = 100;
|
|
39
|
+
const CONTENT_COMMIT_MAX_ATTEMPTS = 3;
|
|
40
|
+
const DEPLOY_COMMIT_MAX_ATTEMPTS = 3;
|
|
41
|
+
const DEPLOY_COMMIT_RETRY_BASE_DELAY_MS = 1_000;
|
|
42
|
+
const CONTENT_COMMIT_RETRY_BASE_DELAY_MS = 500;
|
|
39
43
|
const URL_REFRESH_AT_MS = 50 * 60 * 1000;
|
|
40
44
|
const SECRET_KEY_RE = /^[A-Z_][A-Z0-9_]{0,127}$/;
|
|
41
45
|
const APPLY_SAFE_RETRY_CODES = new Set([
|
|
@@ -568,11 +572,9 @@ async function rehearseBeforeCommit(client, plan, planId, project, opts, emit) {
|
|
|
568
572
|
const envelope = plan.rehearsal;
|
|
569
573
|
if (!envelope || !envelope.available) {
|
|
570
574
|
const reason = envelope?.reason ?? "no_migrations";
|
|
571
|
-
return skip(reason === "no_live_release"
|
|
572
|
-
?
|
|
573
|
-
:
|
|
574
|
-
? "migrations_unchanged"
|
|
575
|
-
: "no_migrations");
|
|
575
|
+
return skip(reason === "no_live_release" || reason === "migrations_unchanged" || reason === "ci_session_unsupported"
|
|
576
|
+
? reason
|
|
577
|
+
: "no_migrations");
|
|
576
578
|
}
|
|
577
579
|
// Named migrations that are all checksum-identical noops (already applied
|
|
578
580
|
// to the live project) would run NOTHING on the branch, so a rehearsal
|
|
@@ -731,7 +733,7 @@ async function applyOnce(client, spec, opts, emit) {
|
|
|
731
733
|
apply_plan_sha256: plan.vault?.apply_plan_sha256 ?? null,
|
|
732
734
|
})
|
|
733
735
|
: undefined;
|
|
734
|
-
const commit = requireCloudCommitResponse(await commitInternal(client, planId, opts.idempotencyKey, spec.project_id, requiredPlan, vaultCommit), "applying deploy");
|
|
736
|
+
const commit = requireCloudCommitResponse(await commitInternal(client, planId, opts.idempotencyKey, spec.project_id, requiredPlan, vaultCommit, operationId), "applying deploy");
|
|
735
737
|
const result = await pollUntilReady(client, commit, plan.diff, plan.warnings, emit, spec.project_id, sliceKinds);
|
|
736
738
|
result.rehearsal = rehearsal.block;
|
|
737
739
|
// v1.48 unified-apply: thread the plan response's `asset_entries[]` back
|
|
@@ -1591,7 +1593,7 @@ function expandCronNumberPart(part, min, max) {
|
|
|
1591
1593
|
}
|
|
1592
1594
|
return values;
|
|
1593
1595
|
}
|
|
1594
|
-
async function commitInternal(client, planId, idempotencyKey, project, requiredPlan, vault) {
|
|
1596
|
+
async function commitInternal(client, planId, idempotencyKey, project, requiredPlan, vault, operationId) {
|
|
1595
1597
|
try {
|
|
1596
1598
|
const body = {};
|
|
1597
1599
|
if (idempotencyKey)
|
|
@@ -1600,7 +1602,7 @@ async function commitInternal(client, planId, idempotencyKey, project, requiredP
|
|
|
1600
1602
|
body.required_plan = requiredPlanToWire(requiredPlan);
|
|
1601
1603
|
if (vault)
|
|
1602
1604
|
body.vault = vault;
|
|
1603
|
-
|
|
1605
|
+
const send = () => client.request(`/apply/v1/plans/${encodeURIComponent(planId)}/commit`, {
|
|
1604
1606
|
method: "POST",
|
|
1605
1607
|
body,
|
|
1606
1608
|
// Write-approval scope: committing a deploy is `project.deploy` on this project.
|
|
@@ -1615,11 +1617,43 @@ async function commitInternal(client, planId, idempotencyKey, project, requiredP
|
|
|
1615
1617
|
: {}),
|
|
1616
1618
|
context: "committing deploy",
|
|
1617
1619
|
});
|
|
1620
|
+
return await sendCommitRecoveringLostResponse(client, send, operationId, project);
|
|
1618
1621
|
}
|
|
1619
1622
|
catch (err) {
|
|
1620
1623
|
throw translateDeployError(err, "commit", planId, null);
|
|
1621
1624
|
}
|
|
1622
1625
|
}
|
|
1626
|
+
/**
|
|
1627
|
+
* Send a deploy commit, recovering when a 502/503/504 or a dropped connection
|
|
1628
|
+
* loses the response. The commit may still have reached the gateway (a long
|
|
1629
|
+
* commit can outlive the connection that carried it), and a second commit
|
|
1630
|
+
* while the first is running would drive the state machine twice. So read the
|
|
1631
|
+
* operation first: once it has left `planning`/`uploading` the commit landed,
|
|
1632
|
+
* and the caller follows the operation; otherwise the commit never arrived and
|
|
1633
|
+
* is sent again. Without an operation id to read, the failure surfaces as is.
|
|
1634
|
+
*/
|
|
1635
|
+
async function sendCommitRecoveringLostResponse(client, send, operationId, projectId) {
|
|
1636
|
+
for (let attempt = 1;; attempt++) {
|
|
1637
|
+
try {
|
|
1638
|
+
return await send();
|
|
1639
|
+
}
|
|
1640
|
+
catch (err) {
|
|
1641
|
+
if (!operationId || !projectId || !isTransientGatewayFailure(err) || attempt >= DEPLOY_COMMIT_MAX_ATTEMPTS)
|
|
1642
|
+
throw err;
|
|
1643
|
+
await sleep(DEPLOY_COMMIT_RETRY_BASE_DELAY_MS * 2 ** (attempt - 1));
|
|
1644
|
+
let snapshot;
|
|
1645
|
+
try {
|
|
1646
|
+
snapshot = await client.request(`/apply/v1/operations/${encodeURIComponent(operationId)}`, { headers: await apikeyHeaders(client, projectId), context: "fetching deploy operation" });
|
|
1647
|
+
}
|
|
1648
|
+
catch {
|
|
1649
|
+
throw err;
|
|
1650
|
+
}
|
|
1651
|
+
if (snapshot.status !== "planning" && snapshot.status !== "uploading") {
|
|
1652
|
+
return { operation_id: operationId, status: "running" };
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1623
1657
|
async function uploadMissing(client, projectId, presence, byteReaders, emit) {
|
|
1624
1658
|
// Surface CAS dedup hits so agents can distinguish "N files were already
|
|
1625
1659
|
// present" from "nothing happened". The gateway reports both present and
|
|
@@ -1692,7 +1726,7 @@ async function uploadMissing(client, projectId, presence, byteReaders, emit) {
|
|
|
1692
1726
|
// Plan-level finalize — marks the plan committed in the deploy_plans
|
|
1693
1727
|
// table. Per-session promotion to CAS already happened in the loop
|
|
1694
1728
|
// above; this call is the plan-level idempotency anchor.
|
|
1695
|
-
await client
|
|
1729
|
+
await commitContentPlanWithRetry(client, planRes.plan_id, headers);
|
|
1696
1730
|
}
|
|
1697
1731
|
async function uploadCoreContent(client, projectId, byteReaders, emit) {
|
|
1698
1732
|
const entries = [...byteReaders.entries()].sort(([a], [b]) => a.localeCompare(b));
|
|
@@ -1721,6 +1755,35 @@ async function uploadCoreContent(client, projectId, byteReaders, emit) {
|
|
|
1721
1755
|
});
|
|
1722
1756
|
}
|
|
1723
1757
|
}
|
|
1758
|
+
/**
|
|
1759
|
+
* Finalize a content plan, riding out a transient gateway failure. A 502/503/504
|
|
1760
|
+
* or a dropped connection means the load balancer or the network lost the
|
|
1761
|
+
* request, not that the gateway refused it, and the commit is safe to repeat: the
|
|
1762
|
+
* gateway re-promotes the same staged sessions. If a lost attempt had in fact
|
|
1763
|
+
* committed, the repeat answers 409 PLAN_ALREADY_COMMITTED, which is success.
|
|
1764
|
+
*/
|
|
1765
|
+
async function commitContentPlanWithRetry(client, planId, headers) {
|
|
1766
|
+
for (let attempt = 1;; attempt++) {
|
|
1767
|
+
try {
|
|
1768
|
+
await client.request(`/content/v1/plans/${encodeURIComponent(planId)}/commit`, { method: "POST", headers, body: {}, context: "committing content upload" });
|
|
1769
|
+
return;
|
|
1770
|
+
}
|
|
1771
|
+
catch (err) {
|
|
1772
|
+
if (attempt > 1 && isRun402Error(err) && err.status === 409 && err.code === "PLAN_ALREADY_COMMITTED")
|
|
1773
|
+
return;
|
|
1774
|
+
if (!isTransientGatewayFailure(err) || attempt >= CONTENT_COMMIT_MAX_ATTEMPTS)
|
|
1775
|
+
throw err;
|
|
1776
|
+
await sleep(CONTENT_COMMIT_RETRY_BASE_DELAY_MS * 2 ** (attempt - 1));
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
function isTransientGatewayFailure(err) {
|
|
1781
|
+
if (!isRun402Error(err))
|
|
1782
|
+
return false;
|
|
1783
|
+
if (err.kind === "network_error")
|
|
1784
|
+
return true;
|
|
1785
|
+
return err.status === 502 || err.status === 503 || err.status === 504;
|
|
1786
|
+
}
|
|
1724
1787
|
async function uploadOneWithRetry(fetchFn, session, bytes) {
|
|
1725
1788
|
const MAX_ATTEMPTS = 3;
|
|
1726
1789
|
for (let attempt = 1;; attempt++) {
|
|
@@ -2100,8 +2163,8 @@ async function startInternal(client, spec, opts) {
|
|
|
2100
2163
|
status: "started",
|
|
2101
2164
|
...(sliceKinds.length > 0 ? { slice_kinds: sliceKinds } : {}),
|
|
2102
2165
|
});
|
|
2103
|
-
const { planId } = requirePersistedPlan(plan, "starting deploy");
|
|
2104
|
-
const commit = requireCloudCommitResponse(await commitInternal(client, planId, opts.idempotencyKey, spec.project_id, opts.requiredPlan), "starting deploy");
|
|
2166
|
+
const { planId, operationId } = requirePersistedPlan(plan, "starting deploy");
|
|
2167
|
+
const commit = requireCloudCommitResponse(await commitInternal(client, planId, opts.idempotencyKey, spec.project_id, opts.requiredPlan, undefined, operationId), "starting deploy");
|
|
2105
2168
|
return await pollUntilReady(client, commit, plan.diff, plan.warnings, emit, spec.project_id, sliceKinds);
|
|
2106
2169
|
})();
|
|
2107
2170
|
// Avoid an unhandled-rejection at construction time. Consumers must call
|
|
@@ -4034,7 +4097,7 @@ async function uploadInlineCas(client, projectId, bytes, contentType) {
|
|
|
4034
4097
|
await uploadOne(client.fetch, session, bytes);
|
|
4035
4098
|
// v1.48 unified-apply: per-session /storage/v1/uploads/:id/complete is
|
|
4036
4099
|
// gone (404). The plan-level commit below promotes the session to CAS.
|
|
4037
|
-
await client
|
|
4100
|
+
await commitContentPlanWithRetry(client, planRes.plan_id, headers);
|
|
4038
4101
|
}
|
|
4039
4102
|
return { sha256, size: bytes.byteLength, contentType };
|
|
4040
4103
|
}
|