gencow 0.1.223 → 0.1.225
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/app-command.mjs +17 -47
- package/lib/app-delete-operation.mjs +3 -57
- package/lib/deploy-package-runtime.mjs +1 -30
- package/lib/deployment-admission-retry.mjs +31 -0
- package/lib/deployment-operation-poll.mjs +22 -3
- package/lib/platform-client.mjs +2 -3
- package/lib/static-deploy-command.mjs +19 -4
- package/package.json +3 -3
- package/runtime/server.mjs +3766 -2568
- package/server/index.js.map +7 -0
- package/lib/request-deadline.mjs +0 -22
package/lib/app-command.mjs
CHANGED
|
@@ -17,7 +17,6 @@ import {
|
|
|
17
17
|
import { pollAppDeleteOperation } from "./app-delete-operation.mjs";
|
|
18
18
|
import { appResponseError } from "./app-response-error.mjs";
|
|
19
19
|
import { readJsonObjectResponse, readJsonValueResponse } from "./http-response-json.mjs";
|
|
20
|
-
import { runWithAbortDeadline } from "./request-deadline.mjs";
|
|
21
20
|
import { BOLD, CYAN, DIM, GREEN, RED, RESET, error, info, log, success, warn } from "./output.mjs";
|
|
22
21
|
import { loadCreds, rpcMutation, rpcQuery, saveCreds, requireCreds } from "./platform-client.mjs";
|
|
23
22
|
import { resolveProjectMetadataPath, resolveProjectSelection } from "./project-context.mjs";
|
|
@@ -28,7 +27,6 @@ import { updateEnvLocalUrl } from "./cli-project-runtime.mjs";
|
|
|
28
27
|
const DEFAULT_DELETE_POLL_ATTEMPTS = 120;
|
|
29
28
|
const DELETE_POLL_INTERVAL_MS = 1_000;
|
|
30
29
|
const DEFAULT_APP_CREATE_TIMEOUT_MS = 60_000;
|
|
31
|
-
const DEFAULT_APP_DELETE_REQUEST_TIMEOUT_MS = 20_000;
|
|
32
30
|
|
|
33
31
|
function resolveAppCreateTimeoutMs(value) {
|
|
34
32
|
const parsed = Number(value);
|
|
@@ -37,13 +35,6 @@ function resolveAppCreateTimeoutMs(value) {
|
|
|
37
35
|
: DEFAULT_APP_CREATE_TIMEOUT_MS;
|
|
38
36
|
}
|
|
39
37
|
|
|
40
|
-
function resolveAppDeleteRequestTimeoutMs(value) {
|
|
41
|
-
const parsed = Number(value);
|
|
42
|
-
return Number.isSafeInteger(parsed) && parsed >= 100 && parsed <= 600_000
|
|
43
|
-
? parsed
|
|
44
|
-
: DEFAULT_APP_DELETE_REQUEST_TIMEOUT_MS;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
38
|
function appResponseStatus(response) {
|
|
48
39
|
return Number.isSafeInteger(response?.status) ? `HTTP ${response.status}` : "unknown HTTP status";
|
|
49
40
|
}
|
|
@@ -146,9 +137,6 @@ async function confirmDelete(name) {
|
|
|
146
137
|
|
|
147
138
|
export function createAppCommand({
|
|
148
139
|
appCreateTimeoutMs = resolveAppCreateTimeoutMs(process.env.GENCOW_APP_CREATE_TIMEOUT_MS),
|
|
149
|
-
appDeleteRequestTimeoutMs = resolveAppDeleteRequestTimeoutMs(
|
|
150
|
-
process.env.GENCOW_APP_DELETE_REQUEST_TIMEOUT_MS,
|
|
151
|
-
),
|
|
152
140
|
clearTimeoutImpl = clearTimeout,
|
|
153
141
|
confirmDeleteImpl = confirmDelete,
|
|
154
142
|
createAbortControllerImpl = () => new AbortController(),
|
|
@@ -171,38 +159,28 @@ export function createAppCommand({
|
|
|
171
159
|
warnImpl = warn,
|
|
172
160
|
}) {
|
|
173
161
|
const boundedAppCreateTimeoutMs = resolveAppCreateTimeoutMs(appCreateTimeoutMs);
|
|
174
|
-
const boundedAppDeleteRequestTimeoutMs = resolveAppDeleteRequestTimeoutMs(appDeleteRequestTimeoutMs);
|
|
175
162
|
|
|
176
163
|
async function requestAppCreate(creds, name) {
|
|
164
|
+
const controller = createAbortControllerImpl();
|
|
177
165
|
const timeoutError = new Error(
|
|
178
166
|
`App creation request timed out after ${boundedAppCreateTimeoutMs}ms. Please try again.`,
|
|
179
167
|
);
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
},
|
|
187
|
-
setTimeoutImpl,
|
|
188
|
-
timeoutError,
|
|
189
|
-
timeoutMs: boundedAppCreateTimeoutMs,
|
|
168
|
+
let timeoutHandle;
|
|
169
|
+
const timeoutPromise = new Promise((_resolve, reject) => {
|
|
170
|
+
timeoutHandle = setTimeoutImpl(() => {
|
|
171
|
+
controller.abort(timeoutError);
|
|
172
|
+
reject(timeoutError);
|
|
173
|
+
}, boundedAppCreateTimeoutMs);
|
|
190
174
|
});
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const timeoutError = new Error("App deletion request timed out");
|
|
195
|
-
return runWithAbortDeadline({
|
|
196
|
-
clearTimeoutImpl,
|
|
197
|
-
createAbortControllerImpl,
|
|
198
|
-
request: async (signal) => {
|
|
199
|
-
const response = await rpcMutationImpl(creds, "apps.delete", { name, requestId }, { signal });
|
|
175
|
+
try {
|
|
176
|
+
const request = (async () => {
|
|
177
|
+
const response = await rpcMutationImpl(creds, "apps.create", { name }, { signal: controller.signal });
|
|
200
178
|
return { response, data: await readJsonObjectResponse(response) };
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
179
|
+
})();
|
|
180
|
+
return await Promise.race([request, timeoutPromise]);
|
|
181
|
+
} finally {
|
|
182
|
+
clearTimeoutImpl(timeoutHandle);
|
|
183
|
+
}
|
|
206
184
|
}
|
|
207
185
|
|
|
208
186
|
return async function app(subcmd, ...rest) {
|
|
@@ -364,16 +342,8 @@ ${dashboardLine}
|
|
|
364
342
|
|
|
365
343
|
infoImpl(`Deleting app "${name}"...`);
|
|
366
344
|
const requestId = createDeleteRequestIdImpl();
|
|
367
|
-
|
|
368
|
-
let delData;
|
|
369
|
-
try {
|
|
370
|
-
({ response: delRes, data: delData } = await requestAppDelete(creds, name, requestId));
|
|
371
|
-
} catch {
|
|
372
|
-
errorImpl(
|
|
373
|
-
"App deletion request did not complete. Its operation outcome is unknown; retry the same app delete to inspect it.\nCode: APP_DELETE_REQUEST_UNAVAILABLE",
|
|
374
|
-
);
|
|
375
|
-
return 1;
|
|
376
|
-
}
|
|
345
|
+
const delRes = await rpcMutationImpl(creds, "apps.delete", { name, requestId });
|
|
346
|
+
let delData = await readJsonObjectResponse(delRes);
|
|
377
347
|
if (!isExactAppDeleteResponse(delData, name)) {
|
|
378
348
|
const operationResult = await pollAppDeleteOperation({
|
|
379
349
|
creds,
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { isExactAppDeleteResponse } from "./app-response-contract.mjs";
|
|
2
2
|
import { APP_DELETE_OPERATION_PATTERN } from "./app-response-error.mjs";
|
|
3
3
|
import { readJsonObjectResponse } from "./http-response-json.mjs";
|
|
4
|
-
import { runWithAbortDeadline } from "./request-deadline.mjs";
|
|
5
4
|
|
|
6
5
|
const APP_DELETE_ACTIVE_STATES = new Set([
|
|
7
6
|
"accepted",
|
|
@@ -27,38 +26,6 @@ const APP_DELETE_RESPONSE_STATES = new Set([
|
|
|
27
26
|
const CORRELATION_ID_PATTERN = /^[A-Za-z0-9_-]{8,128}$/u;
|
|
28
27
|
const DIAGNOSTIC_CODE_PATTERN = /^[A-Za-z][A-Za-z0-9_]{2,127}$/u;
|
|
29
28
|
const APP_ID_PATTERN = /^(?=.{3,63}$)[a-z][a-z0-9]*(?:-[a-z0-9]+){2,7}$/u;
|
|
30
|
-
const DEFAULT_DELETE_OPERATION_TIMEOUT_MS = 120_000;
|
|
31
|
-
const DEFAULT_DELETE_STATUS_REQUEST_TIMEOUT_MS = 10_000;
|
|
32
|
-
|
|
33
|
-
function resolveTimeoutMs(value, fallback) {
|
|
34
|
-
const parsed = Number(value);
|
|
35
|
-
return Number.isSafeInteger(parsed) && parsed >= 1 && parsed <= 600_000 ? parsed : fallback;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
async function requestDeleteStatus({
|
|
39
|
-
clearTimeoutImpl,
|
|
40
|
-
createAbortControllerImpl,
|
|
41
|
-
creds,
|
|
42
|
-
name,
|
|
43
|
-
operationId,
|
|
44
|
-
requestTimeoutMs,
|
|
45
|
-
rpcQueryImpl,
|
|
46
|
-
setTimeoutImpl,
|
|
47
|
-
}) {
|
|
48
|
-
const timeoutError = new Error("App deletion status request timed out");
|
|
49
|
-
timeoutError.name = "AbortError";
|
|
50
|
-
return runWithAbortDeadline({
|
|
51
|
-
clearTimeoutImpl,
|
|
52
|
-
createAbortControllerImpl,
|
|
53
|
-
request: async (signal) => {
|
|
54
|
-
const statusRes = await rpcQueryImpl(creds, "apps.deleteStatus", { name, operationId }, { signal });
|
|
55
|
-
return { statusRes, statusData: await readJsonObjectResponse(statusRes) };
|
|
56
|
-
},
|
|
57
|
-
setTimeoutImpl,
|
|
58
|
-
timeoutError,
|
|
59
|
-
timeoutMs: requestTimeoutMs,
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
29
|
|
|
63
30
|
function isAllowedStatusPath(value, operationId, expectedAppId) {
|
|
64
31
|
if (value === undefined) return true;
|
|
@@ -137,18 +104,12 @@ function isMatchingTerminalFailureResponse(value, accepted, expectedAppId) {
|
|
|
137
104
|
}
|
|
138
105
|
|
|
139
106
|
export async function pollAppDeleteOperation({
|
|
140
|
-
clearTimeoutImpl = clearTimeout,
|
|
141
|
-
createAbortControllerImpl = () => new AbortController(),
|
|
142
107
|
creds,
|
|
143
108
|
initialBody,
|
|
144
109
|
name,
|
|
145
|
-
nowImpl = Date.now,
|
|
146
|
-
operationTimeoutMs = DEFAULT_DELETE_OPERATION_TIMEOUT_MS,
|
|
147
110
|
pollAttempts,
|
|
148
111
|
pollIntervalMs,
|
|
149
|
-
requestTimeoutMs = DEFAULT_DELETE_STATUS_REQUEST_TIMEOUT_MS,
|
|
150
112
|
rpcQueryImpl,
|
|
151
|
-
setTimeoutImpl = setTimeout,
|
|
152
113
|
sleepImpl,
|
|
153
114
|
}) {
|
|
154
115
|
const accepted = parseAppDeleteOperationEnvelope(initialBody, name);
|
|
@@ -160,25 +121,13 @@ export async function pollAppDeleteOperation({
|
|
|
160
121
|
let latest = accepted;
|
|
161
122
|
let successfulPollResponses = 0;
|
|
162
123
|
let transportFailures = 0;
|
|
163
|
-
const deadlineAt = nowImpl() + resolveTimeoutMs(operationTimeoutMs, DEFAULT_DELETE_OPERATION_TIMEOUT_MS);
|
|
164
|
-
const boundedRequestTimeoutMs = resolveTimeoutMs(
|
|
165
|
-
requestTimeoutMs,
|
|
166
|
-
DEFAULT_DELETE_STATUS_REQUEST_TIMEOUT_MS,
|
|
167
|
-
);
|
|
168
124
|
for (let attempt = 0; attempt < pollAttempts; attempt += 1) {
|
|
169
|
-
const remainingMs = deadlineAt - nowImpl();
|
|
170
|
-
if (remainingMs <= 0) break;
|
|
171
125
|
try {
|
|
172
|
-
const
|
|
173
|
-
clearTimeoutImpl,
|
|
174
|
-
createAbortControllerImpl,
|
|
175
|
-
creds,
|
|
126
|
+
const statusRes = await rpcQueryImpl(creds, "apps.deleteStatus", {
|
|
176
127
|
name,
|
|
177
128
|
operationId: accepted.operationId,
|
|
178
|
-
requestTimeoutMs: Math.min(boundedRequestTimeoutMs, remainingMs),
|
|
179
|
-
rpcQueryImpl,
|
|
180
|
-
setTimeoutImpl,
|
|
181
129
|
});
|
|
130
|
+
const statusData = await readJsonObjectResponse(statusRes);
|
|
182
131
|
// A lifecycle operation is durable. A transient 5xx while the status
|
|
183
132
|
// query is being served must not turn that durable receipt into a client
|
|
184
133
|
// contract failure or abandon the operation's correlation identity.
|
|
@@ -208,10 +157,7 @@ export async function pollAppDeleteOperation({
|
|
|
208
157
|
transportFailures += 1;
|
|
209
158
|
// The durable receipt remains authoritative across transient polling failures.
|
|
210
159
|
}
|
|
211
|
-
if (attempt + 1 < pollAttempts)
|
|
212
|
-
const remainingAfterRequestMs = deadlineAt - nowImpl();
|
|
213
|
-
if (remainingAfterRequestMs > 0) await sleepImpl(Math.min(pollIntervalMs, remainingAfterRequestMs));
|
|
214
|
-
}
|
|
160
|
+
if (attempt + 1 < pollAttempts) await sleepImpl(pollIntervalMs);
|
|
215
161
|
}
|
|
216
162
|
|
|
217
163
|
const pollingUnavailable = successfulPollResponses === 0 && transportFailures > 0;
|
|
@@ -20,6 +20,7 @@ import { buildDeployClaimFromArchive } from "./deploy-package-claim.mjs";
|
|
|
20
20
|
import { renderDeployFailureDiagnosticLines } from "./deploy-failure-diagnostic.mjs";
|
|
21
21
|
import { readJsonObjectResponse } from "./http-response-json.mjs";
|
|
22
22
|
import { pollAcceptedDeploymentOperation } from "./deployment-operation-poll.mjs";
|
|
23
|
+
import { submitDeploymentWithIdempotentRetry } from "./deployment-admission-retry.mjs";
|
|
23
24
|
import { acquireDeployReceipt, releaseDeployReceipt } from "./deploy-receipt-store.mjs";
|
|
24
25
|
import { CYAN, DIM, RED, RESET, YELLOW, error, info, log, success, warn } from "./output.mjs";
|
|
25
26
|
import { platformFetch, rpcMutation } from "./platform-client.mjs";
|
|
@@ -42,36 +43,6 @@ import {
|
|
|
42
43
|
import { detectProjectConfigFile, warnProjectConfigSelectionOnce } from "./project-config-selection.mjs";
|
|
43
44
|
import { writeCanonicalDeployBundle } from "./deploy-bundle-staging.mjs";
|
|
44
45
|
|
|
45
|
-
const INITIAL_DEPLOYMENT_ADMISSION_ATTEMPTS = 3;
|
|
46
|
-
|
|
47
|
-
function isRetryableInitialDeploymentAdmission(response) {
|
|
48
|
-
return Number.isInteger(response?.status) && response.status >= 500 && response.status <= 599;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* A deploy claim is an idempotency key. Before the Platform returns the
|
|
53
|
-
* operation receipt, a transport/5xx outcome is intentionally ambiguous;
|
|
54
|
-
* resubmit only that same claim a bounded number of times rather than making
|
|
55
|
-
* the customer invent a second operation.
|
|
56
|
-
*/
|
|
57
|
-
export async function submitDeploymentWithIdempotentRetry({
|
|
58
|
-
submit,
|
|
59
|
-
sleepImpl = (ms) => new Promise((resolveDelay) => setTimeout(resolveDelay, ms)),
|
|
60
|
-
}) {
|
|
61
|
-
let response;
|
|
62
|
-
for (let attempt = 1; attempt <= INITIAL_DEPLOYMENT_ADMISSION_ATTEMPTS; attempt += 1) {
|
|
63
|
-
response = await submit();
|
|
64
|
-
if (
|
|
65
|
-
!isRetryableInitialDeploymentAdmission(response) ||
|
|
66
|
-
attempt === INITIAL_DEPLOYMENT_ADMISSION_ATTEMPTS
|
|
67
|
-
) {
|
|
68
|
-
return response;
|
|
69
|
-
}
|
|
70
|
-
await sleepImpl(250 * attempt);
|
|
71
|
-
}
|
|
72
|
-
return response;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
46
|
async function loadProjectConfigDefault(options) {
|
|
76
47
|
const { loadConfig } = await import("./cli-project-runtime.mjs");
|
|
77
48
|
return loadConfig(options);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const INITIAL_DEPLOYMENT_ADMISSION_ATTEMPTS = 3;
|
|
2
|
+
|
|
3
|
+
function isRetryableInitialDeploymentAdmission(response) {
|
|
4
|
+
return Number.isInteger(response?.status) && response.status >= 500 && response.status <= 599;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A deploy claim is an idempotency key. Before Platform returns an operation
|
|
9
|
+
* receipt, retry only the same claim after an ambiguous transport or 5xx
|
|
10
|
+
* outcome; do not create replacement work.
|
|
11
|
+
*/
|
|
12
|
+
export async function submitDeploymentWithIdempotentRetry({
|
|
13
|
+
submit,
|
|
14
|
+
sleepImpl = (ms) => new Promise((resolveDelay) => setTimeout(resolveDelay, ms)),
|
|
15
|
+
}) {
|
|
16
|
+
let response;
|
|
17
|
+
for (let attempt = 1; attempt <= INITIAL_DEPLOYMENT_ADMISSION_ATTEMPTS; attempt += 1) {
|
|
18
|
+
try {
|
|
19
|
+
response = await submit();
|
|
20
|
+
} catch (error) {
|
|
21
|
+
if (attempt === INITIAL_DEPLOYMENT_ADMISSION_ATTEMPTS) throw error;
|
|
22
|
+
await sleepImpl(250 * attempt);
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (!isRetryableInitialDeploymentAdmission(response) || attempt === INITIAL_DEPLOYMENT_ADMISSION_ATTEMPTS) {
|
|
26
|
+
return response;
|
|
27
|
+
}
|
|
28
|
+
await sleepImpl(250 * attempt);
|
|
29
|
+
}
|
|
30
|
+
return response;
|
|
31
|
+
}
|
|
@@ -2,6 +2,7 @@ import { readJsonObjectResponse } from "./http-response-json.mjs";
|
|
|
2
2
|
|
|
3
3
|
const PENDING_STATES = new Set(["accepted", "queued", "running"]);
|
|
4
4
|
const TERMINAL_STATES = new Set(["succeeded", "failed", "blocked", "cancelled"]);
|
|
5
|
+
const TERMINAL_FAILURE_STATES = new Set(["failed", "blocked"]);
|
|
5
6
|
const OPERATION_ID = /^[1-9][0-9]*$/u;
|
|
6
7
|
const CORRELATION_ID = /^[A-Za-z0-9_-]{8,128}$/u;
|
|
7
8
|
const APP_NAME = /^[a-z0-9][a-z0-9-]{0,62}$/u;
|
|
@@ -63,12 +64,24 @@ function isNonterminalResponse(response, body, identity) {
|
|
|
63
64
|
);
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
function
|
|
67
|
+
function isMatchingTerminalPinnedFailure(response, identity, expected) {
|
|
67
68
|
return (
|
|
68
69
|
expected.operationId !== null &&
|
|
69
70
|
Number.isInteger(response?.status) &&
|
|
70
71
|
response.status >= 500 &&
|
|
71
|
-
response.status <= 599
|
|
72
|
+
response.status <= 599 &&
|
|
73
|
+
TERMINAL_FAILURE_STATES.has(identity.state) &&
|
|
74
|
+
identityIsValid(identity, expected, false)
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function isTransientPinnedOperationRead(response, identity, expected) {
|
|
79
|
+
return (
|
|
80
|
+
expected.operationId !== null &&
|
|
81
|
+
Number.isInteger(response?.status) &&
|
|
82
|
+
response.status >= 500 &&
|
|
83
|
+
response.status <= 599 &&
|
|
84
|
+
!isMatchingTerminalPinnedFailure(response, identity, expected)
|
|
72
85
|
);
|
|
73
86
|
}
|
|
74
87
|
|
|
@@ -134,6 +147,12 @@ export async function pollAcceptedDeploymentOperation({
|
|
|
134
147
|
platformFetch(creds, expected.pollUrl, { method: "GET", signal: controller.signal }),
|
|
135
148
|
timedOut,
|
|
136
149
|
]);
|
|
150
|
+
} catch {
|
|
151
|
+
// The operation identity was already sealed by the accepted response.
|
|
152
|
+
// A transport failure while observing it is not that operation's
|
|
153
|
+
// terminal result. Present it as a transient pinned read so the loop
|
|
154
|
+
// keeps polling the same receipt until its declared client deadline.
|
|
155
|
+
return { ok: false, status: 503, statusText: "Service Unavailable" };
|
|
137
156
|
} finally {
|
|
138
157
|
if (timer !== undefined) deadlineClearTimeoutImpl(timer);
|
|
139
158
|
}
|
|
@@ -165,7 +184,7 @@ export async function pollAcceptedDeploymentOperation({
|
|
|
165
184
|
// Once the server has sealed the operation identity, a gateway/database
|
|
166
185
|
// read failure cannot safely be presented as that operation's terminal
|
|
167
186
|
// result. Keep polling the same receipt; never submit a second deploy.
|
|
168
|
-
if (isTransientPinnedOperationRead(response, expected)) {
|
|
187
|
+
if (isTransientPinnedOperationRead(response, identity, expected)) {
|
|
169
188
|
if (nowImpl() >= deadlineMs) return pendingResponse();
|
|
170
189
|
const retryAfter = Number(response.headers?.get?.("Retry-After"));
|
|
171
190
|
const retryDelayMs =
|
package/lib/platform-client.mjs
CHANGED
|
@@ -96,11 +96,10 @@ export async function platformFetch(creds, path, opts = {}) {
|
|
|
96
96
|
});
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
export async function rpcQuery(creds, queryName, args = {}
|
|
99
|
+
export async function rpcQuery(creds, queryName, args = {}) {
|
|
100
100
|
return platformFetch(creds, "/api/query", {
|
|
101
|
-
...requestOptions,
|
|
102
101
|
method: "POST",
|
|
103
|
-
headers: {
|
|
102
|
+
headers: { "Content-Type": "application/json" },
|
|
104
103
|
body: JSON.stringify({ name: queryName, args }),
|
|
105
104
|
});
|
|
106
105
|
}
|
|
@@ -47,7 +47,17 @@ export async function postStaticDeployWithTransientProxyRetry({
|
|
|
47
47
|
}) {
|
|
48
48
|
let response;
|
|
49
49
|
for (let attempt = 0; ; attempt += 1) {
|
|
50
|
-
|
|
50
|
+
try {
|
|
51
|
+
response = await send();
|
|
52
|
+
} catch (error) {
|
|
53
|
+
// `send` closes over one immutable deploy claim (and, when enabled, one
|
|
54
|
+
// release attempt). Replaying it is admission observation, not a new
|
|
55
|
+
// static deployment. Keep this bounded; the final transport error is
|
|
56
|
+
// still reported to the caller without inventing a terminal outcome.
|
|
57
|
+
if (attempt >= retryDelaysMs.length) throw error;
|
|
58
|
+
await delayImpl(retryDelaysMs[attempt]);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
51
61
|
if (!(await isBodylessTransientStaticDeployResponse(response)) || attempt >= retryDelaysMs.length) {
|
|
52
62
|
return response;
|
|
53
63
|
}
|
|
@@ -446,14 +456,19 @@ export function createStaticDeployRuntime({
|
|
|
446
456
|
setTimeoutImpl,
|
|
447
457
|
});
|
|
448
458
|
}
|
|
449
|
-
} catch
|
|
450
|
-
|
|
459
|
+
} catch {
|
|
460
|
+
// No response means the canonical deployment may already exist. Do not
|
|
461
|
+
// let this observer terminalize its release attempt; retain the same
|
|
462
|
+
// claim/attempt so a normal retry converges through the executor owner.
|
|
451
463
|
try {
|
|
452
464
|
unlinkSyncImpl(tmpBundle);
|
|
453
465
|
} catch {
|
|
454
466
|
/* ignore */
|
|
455
467
|
}
|
|
456
|
-
|
|
468
|
+
errorImpl("Static deployment admission is temporarily unavailable. Retry the same command.");
|
|
469
|
+
infoImpl("Code: PLATFORM_DEPLOYMENT_ADMISSION_UNAVAILABLE");
|
|
470
|
+
exitImpl(1);
|
|
471
|
+
return;
|
|
457
472
|
}
|
|
458
473
|
|
|
459
474
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gencow",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.225",
|
|
4
4
|
"description": "Gencow — AI Backend Engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -33,9 +33,9 @@
|
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@types/node": "^25.9.5",
|
|
35
35
|
"better-auth": "^1.6.23",
|
|
36
|
+
"@gencow/core": "0.1.43",
|
|
36
37
|
"@gencow/client": "0.2.7",
|
|
37
|
-
"@gencow/react": "0.2.7"
|
|
38
|
-
"@gencow/core": "0.1.43"
|
|
38
|
+
"@gencow/react": "0.2.7"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
41
|
"prebuild": "pnpm --filter @gencow/migration-contract run build && pnpm --filter @gencow/server run build",
|