bulletin-deploy 0.7.26 → 0.7.27-rc.2
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/bin/bulletin-deploy +5 -3
- package/dist/bug-report.js +4 -4
- package/dist/{chunk-QUQYXRTC.js → chunk-3AJST77I.js} +1 -1
- package/dist/{chunk-5EWKYMDC.js → chunk-EJUBFDXU.js} +2 -2
- package/dist/{chunk-NEYQ2JMY.js → chunk-JHD3OP3E.js} +30 -7
- package/dist/{chunk-6MDQDYVL.js → chunk-KLXIR7NQ.js} +1 -1
- package/dist/{chunk-X6YUEHEH.js → chunk-MRRSSWRM.js} +55 -7
- package/dist/{chunk-MR6AV2UF.js → chunk-P7XM4NJG.js} +49 -5
- package/dist/{chunk-5WKHSZT7.js → chunk-Y5EWXXFE.js} +1 -1
- package/dist/chunk-probe.js +3 -3
- package/dist/deploy.d.ts +16 -2
- package/dist/deploy.js +11 -7
- package/dist/dotns.d.ts +11 -1
- package/dist/dotns.js +3 -3
- package/dist/index.js +7 -7
- package/dist/memory-report.js +2 -2
- package/dist/merkle.js +7 -7
- package/dist/personhood/bootstrap.js +3 -3
- package/dist/personhood/people-client.js +3 -3
- package/dist/run-state.js +1 -1
- package/dist/telemetry.d.ts +9 -2
- package/dist/telemetry.js +4 -2
- package/dist/version-check.js +3 -3
- package/package.json +1 -1
- package/tools/release-retry-wrapper.mjs +25 -10
package/bin/bulletin-deploy
CHANGED
|
@@ -207,9 +207,11 @@ if (!flags.help && !flags.version) {
|
|
|
207
207
|
};
|
|
208
208
|
process.on("uncaughtException", (e) => handleUnhandled(e, "uncaught"));
|
|
209
209
|
process.on("unhandledRejection", (e) => handleUnhandled(e, "unhandled"));
|
|
210
|
-
// POSIX exit codes: 128 + signal number.
|
|
211
|
-
//
|
|
212
|
-
// retryable.
|
|
210
|
+
// POSIX exit codes: 128 + signal number. Signal exits (130/SIGINT,
|
|
211
|
+
// 143/SIGTERM, 129/SIGHUP) indicate cancellation or environment teardown
|
|
212
|
+
// and are retryable from the consumer's perspective. Contrast with
|
|
213
|
+
// NonRetryableError exits (code 78, POSIX EX_CONFIG) which callers must
|
|
214
|
+
// NOT retry — see EXIT_CODE_NO_RETRY in src/errors.ts.
|
|
213
215
|
process.on("SIGINT", () => finalize("SIGINT", 130));
|
|
214
216
|
process.on("SIGTERM", () => finalize("SIGTERM", 143));
|
|
215
217
|
process.on("SIGHUP", () => finalize("SIGHUP", 129));
|
package/dist/bug-report.js
CHANGED
|
@@ -9,10 +9,10 @@ import {
|
|
|
9
9
|
offerBugReport,
|
|
10
10
|
scrubSecrets,
|
|
11
11
|
setDeployContext
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import "./chunk-
|
|
14
|
-
import "./chunk-
|
|
15
|
-
import "./chunk-
|
|
12
|
+
} from "./chunk-EJUBFDXU.js";
|
|
13
|
+
import "./chunk-3AJST77I.js";
|
|
14
|
+
import "./chunk-P7XM4NJG.js";
|
|
15
|
+
import "./chunk-Y5EWXXFE.js";
|
|
16
16
|
export {
|
|
17
17
|
buildCliFlagsSummary,
|
|
18
18
|
buildLabels,
|
|
@@ -2,11 +2,11 @@ import {
|
|
|
2
2
|
classifyErrorArea,
|
|
3
3
|
isInteractive,
|
|
4
4
|
promptYesNo
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-3AJST77I.js";
|
|
6
6
|
import {
|
|
7
7
|
VERSION,
|
|
8
8
|
getCurrentSentryTraceId
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-P7XM4NJG.js";
|
|
10
10
|
|
|
11
11
|
// src/bug-report.ts
|
|
12
12
|
import { execSync, execFileSync } from "child_process";
|
|
@@ -20,10 +20,10 @@ import {
|
|
|
20
20
|
} from "./chunk-S7EM5VMW.js";
|
|
21
21
|
import {
|
|
22
22
|
setDeployContext
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-EJUBFDXU.js";
|
|
24
24
|
import {
|
|
25
25
|
probeChunks
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-KLXIR7NQ.js";
|
|
27
27
|
import {
|
|
28
28
|
packSection
|
|
29
29
|
} from "./chunk-C2TS5MER.js";
|
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
parseDomainName,
|
|
35
35
|
popStatusName,
|
|
36
36
|
verifyNonceAdvanced
|
|
37
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-MRRSSWRM.js";
|
|
38
38
|
import {
|
|
39
39
|
derivePoolAccounts,
|
|
40
40
|
detectTestnet,
|
|
@@ -56,7 +56,7 @@ import {
|
|
|
56
56
|
truncateAddress,
|
|
57
57
|
withDeploySpan,
|
|
58
58
|
withSpan
|
|
59
|
-
} from "./chunk-
|
|
59
|
+
} from "./chunk-P7XM4NJG.js";
|
|
60
60
|
import {
|
|
61
61
|
DEFAULT_ENV_ID,
|
|
62
62
|
getPopSelfServeConfig,
|
|
@@ -137,7 +137,10 @@ var CHUNK_SIZE = 2 * 1024 * 1024;
|
|
|
137
137
|
var MAX_FILE_SIZE = 8 * 1024 * 1024;
|
|
138
138
|
var MAX_RECONNECTIONS = parseInt(process.env.BULLETIN_MAX_RECONNECTIONS ?? "3", 10);
|
|
139
139
|
var CHUNK_TIMEOUT_MS = parseInt(process.env.BULLETIN_CHUNK_TIMEOUT_MS ?? "180000", 10);
|
|
140
|
-
var CHUNK_MORTALITY_PERIOD =
|
|
140
|
+
var CHUNK_MORTALITY_PERIOD = (() => {
|
|
141
|
+
const v = parseInt(process.env.BULLETIN_CHUNK_MORTALITY_PERIOD ?? "", 10);
|
|
142
|
+
return Number.isFinite(v) && v > 0 ? v : 16;
|
|
143
|
+
})();
|
|
141
144
|
var RETRY_BASE_DELAY_MS = 2e3;
|
|
142
145
|
var RETRY_MAX_DELAY_MS = 15e3;
|
|
143
146
|
var WS_HEARTBEAT_TIMEOUT_MS = 3e5;
|
|
@@ -663,6 +666,10 @@ async function storeChunkedContent(chunks, { client: existingClient, unsafeApi:
|
|
|
663
666
|
continue;
|
|
664
667
|
}
|
|
665
668
|
captureWarning("Chunk upload failed, retrying", { chunkIndex: fail.index + 1, maxRetries: MAX_CHUNK_RETRIES, error: fail.error?.message?.slice(0, 200) });
|
|
669
|
+
const isExpiryFailure = fail.error?.message?.includes("isValid:false");
|
|
670
|
+
if (isExpiryFailure) {
|
|
671
|
+
console.log(` Chunk ${fail.index + 1}: tx rejected (isValid:false), likely mortal era expiry \u2014 reissuing with fresh nonce`);
|
|
672
|
+
}
|
|
666
673
|
let retried = false;
|
|
667
674
|
for (let attempt = 1; attempt <= MAX_CHUNK_RETRIES; attempt++) {
|
|
668
675
|
recordRecoveryAndCheckBudget("chunk_retry");
|
|
@@ -1050,6 +1057,11 @@ function preWarmGateway(chunkCids, gateways) {
|
|
|
1050
1057
|
}
|
|
1051
1058
|
}
|
|
1052
1059
|
}
|
|
1060
|
+
function applyManifestFetchAttributes(fetched) {
|
|
1061
|
+
setDeployAttribute("deploy.manifest.fetch_source", fetched.source);
|
|
1062
|
+
setDeployAttribute("deploy.manifest.fetch_attempts", String(fetched.attempts ?? 0));
|
|
1063
|
+
setDeployAttribute("deploy.manifest.bytes_downloaded", String(fetched.bytesDownloaded ?? 0));
|
|
1064
|
+
}
|
|
1053
1065
|
async function storeDirectoryV2(directoryPath, opts = {}) {
|
|
1054
1066
|
if (opts.password) return storeDirectory(directoryPath, opts);
|
|
1055
1067
|
const provider = opts.provider ?? {};
|
|
@@ -1063,6 +1075,7 @@ async function storeDirectoryV2(directoryPath, opts = {}) {
|
|
|
1063
1075
|
});
|
|
1064
1076
|
const prevManifest = fetched.source === "embedded" ? fetched.manifest : null;
|
|
1065
1077
|
console.log(` Manifest fetch: ${fetched.source}${fetched.source !== "none" ? ` (${fetched.attempts} attempt${fetched.attempts === 1 ? "" : "s"})` : ""}`);
|
|
1078
|
+
applyManifestFetchAttributes(fetched);
|
|
1066
1079
|
const deployedAt = opts.reproducibleSource ? resolveReproducibleTimestamp(opts.reproducibleSource) : (/* @__PURE__ */ new Date()).toISOString();
|
|
1067
1080
|
writeEmbeddedManifestPlaceholder(directoryPath, {
|
|
1068
1081
|
version: MANIFEST_VERSION,
|
|
@@ -1462,6 +1475,13 @@ async function estimateUploadBytes(content) {
|
|
|
1462
1475
|
return null;
|
|
1463
1476
|
}
|
|
1464
1477
|
}
|
|
1478
|
+
function assertSubdomainOwnerMatchesSigner(result, signerEvmAddress, sublabel, parentLabel) {
|
|
1479
|
+
if (result.owned && result.owner?.toLowerCase() !== signerEvmAddress?.toLowerCase()) {
|
|
1480
|
+
throw new NonRetryableError(
|
|
1481
|
+
`Subdomain ${sublabel}.${parentLabel}.dot is already owned by ${result.owner} (signer is ${signerEvmAddress}). Use a fresh subdomain label, or release the existing registration.`
|
|
1482
|
+
);
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1465
1485
|
async function deploy(content, domainName = null, options = {}) {
|
|
1466
1486
|
const envId = options.env ?? DEFAULT_ENV_ID;
|
|
1467
1487
|
let envBulletin = [DEFAULT_BULLETIN_RPC];
|
|
@@ -1545,8 +1565,9 @@ async function deploy(content, domainName = null, options = {}) {
|
|
|
1545
1565
|
await preflight.connect(resolveDotnsConnectOptions(options, envAssetHub, envAutoAccountMapping, envContracts, envNativeToEthRatio, envId, envPopSelfServe, envRegisterStorageDeposit));
|
|
1546
1566
|
if (parsed?.isSubdomain) {
|
|
1547
1567
|
try {
|
|
1548
|
-
const
|
|
1549
|
-
|
|
1568
|
+
const subResult = await preflight.checkSubdomainOwnership(parsed.sublabel, parsed.parentLabel);
|
|
1569
|
+
assertSubdomainOwnerMatchesSigner(subResult, preflight.evmAddress, parsed.sublabel, parsed.parentLabel);
|
|
1570
|
+
if (!subResult.owned) {
|
|
1550
1571
|
const { owned: parentOwned, owner: parentOwner } = await preflight.checkOwnership(parsed.parentLabel);
|
|
1551
1572
|
if (!parentOwned) {
|
|
1552
1573
|
throw new NonRetryableError(
|
|
@@ -2280,8 +2301,10 @@ export {
|
|
|
2280
2301
|
detectFramework,
|
|
2281
2302
|
checkDeploySize,
|
|
2282
2303
|
resolveReproducibleTimestamp,
|
|
2304
|
+
applyManifestFetchAttributes,
|
|
2283
2305
|
storeDirectoryV2,
|
|
2284
2306
|
resolveDotnsConnectOptions,
|
|
2285
2307
|
estimateUploadBytes,
|
|
2308
|
+
assertSubdomainOwnerMatchesSigner,
|
|
2286
2309
|
deploy
|
|
2287
2310
|
};
|
|
@@ -7,10 +7,13 @@ import {
|
|
|
7
7
|
setDeploySentryTag,
|
|
8
8
|
truncateAddress,
|
|
9
9
|
withSpan
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-P7XM4NJG.js";
|
|
11
11
|
import {
|
|
12
12
|
validateContractAddresses
|
|
13
13
|
} from "./chunk-MGU5I7H5.js";
|
|
14
|
+
import {
|
|
15
|
+
NonRetryableError
|
|
16
|
+
} from "./chunk-ZOC4GITL.js";
|
|
14
17
|
|
|
15
18
|
// src/dotns.ts
|
|
16
19
|
import crypto from "crypto";
|
|
@@ -357,7 +360,7 @@ function sanitizeDomainLabel(label) {
|
|
|
357
360
|
}
|
|
358
361
|
return label;
|
|
359
362
|
}
|
|
360
|
-
function validateDomainLabel(label) {
|
|
363
|
+
function validateDomainLabel(label, opts = {}) {
|
|
361
364
|
if (!/^[a-z0-9-]{3,}$/.test(label)) throw new Error("Invalid domain label: must contain only lowercase letters, digits, and hyphens, min 3 chars");
|
|
362
365
|
if (label.startsWith("-") || label.endsWith("-")) throw new Error("Invalid domain label: cannot start or end with hyphen");
|
|
363
366
|
const sanitized = sanitizeDomainLabel(label);
|
|
@@ -369,6 +372,15 @@ function validateDomainLabel(label) {
|
|
|
369
372
|
`Invalid domain label: "${sanitized}" \u2014 dotns base-name extraction leaves a trailing hyphen ("${baseWithHyphen}"), which the registry rejects with PopError("Name must be lowercase ASCII DNS label"). Drop the hyphen before the digits (e.g. "${dropHyphen}") or add a non-digit segment between (e.g. "${insertSegment}").`
|
|
370
373
|
);
|
|
371
374
|
}
|
|
375
|
+
if (opts.checkReserved !== false) {
|
|
376
|
+
const classification = classifyDotnsLabel(sanitized);
|
|
377
|
+
if (classification.status === ProofOfPersonhoodStatus.Reserved) {
|
|
378
|
+
const sanitizeTrail = label !== sanitized ? `Input "${label}" was sanitized to "${sanitized}" (excess trailing digits trimmed). ` : "";
|
|
379
|
+
throw new NonRetryableError(
|
|
380
|
+
`${sanitizeTrail}Invalid domain label "${sanitized}": ${classification.message}`
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
372
384
|
return sanitized;
|
|
373
385
|
}
|
|
374
386
|
function isCommitmentMature(chainNowSeconds, commitTimestampSeconds, minimumAgeSeconds) {
|
|
@@ -411,7 +423,7 @@ function canRegister(requiredStatus, userStatus, trailingDigits) {
|
|
|
411
423
|
return trailingDigits !== 0 && userStatus !== ProofOfPersonhoodStatus.ProofOfPersonhoodLite;
|
|
412
424
|
}
|
|
413
425
|
function exampleNoStatusLabel(label) {
|
|
414
|
-
const base = stripTrailingDigits(validateDomainLabel(label)).replace(/[^a-z0-9-]/g, "x");
|
|
426
|
+
const base = stripTrailingDigits(validateDomainLabel(label, { checkReserved: false })).replace(/[^a-z0-9-]/g, "x");
|
|
415
427
|
return `${base.padEnd(9, "x").slice(0, 9)}00.dot`;
|
|
416
428
|
}
|
|
417
429
|
function parseDomainName(input) {
|
|
@@ -422,7 +434,7 @@ function parseDomainName(input) {
|
|
|
422
434
|
return { isSubdomain: false, label: sanitized, sublabel: null, parentLabel: null, fullName: `${sanitized}.dot` };
|
|
423
435
|
}
|
|
424
436
|
if (parts.length === 2) {
|
|
425
|
-
const sanitizedSub = validateDomainLabel(parts[0]);
|
|
437
|
+
const sanitizedSub = validateDomainLabel(parts[0], { checkReserved: false });
|
|
426
438
|
const sanitizedParent = validateDomainLabel(parts[1]);
|
|
427
439
|
const fullLabel = `${sanitizedSub}.${sanitizedParent}`;
|
|
428
440
|
return { isSubdomain: true, label: fullLabel, sublabel: sanitizedSub, parentLabel: sanitizedParent, fullName: `${fullLabel}.dot` };
|
|
@@ -1137,6 +1149,41 @@ var DotNS = class {
|
|
|
1137
1149
|
}
|
|
1138
1150
|
return decodeFunctionResult({ abi: contractAbi, functionName, data: callResult.result.value.data });
|
|
1139
1151
|
}
|
|
1152
|
+
/**
|
|
1153
|
+
* Like contractCall, but returns null when the chain replies with empty data
|
|
1154
|
+
* ("0x"). Use this for view functions where an unset storage slot is a
|
|
1155
|
+
* meaningful answer (e.g. resolver(node) for a name with no resolver,
|
|
1156
|
+
* text records, optional ownership lookups). Use the strict contractCall
|
|
1157
|
+
* for read paths that must always return a value.
|
|
1158
|
+
*/
|
|
1159
|
+
async contractCallNullable(contractAddress, contractAbi, functionName, args = []) {
|
|
1160
|
+
this.ensureConnected();
|
|
1161
|
+
if (!this.clientWrapper) throw new Error("contractCallNullable: polkadot-api client not available");
|
|
1162
|
+
const encodedCallData = encodeFunctionData({ abi: contractAbi, functionName, args });
|
|
1163
|
+
const callResult = await this.clientWrapper.performDryRunCall(this.substrateAddress, contractAddress, 0n, encodedCallData);
|
|
1164
|
+
if (!callResult.result.isOk) {
|
|
1165
|
+
const errorData = callResult.result.value;
|
|
1166
|
+
throw new Error(formatContractDryRunFailure({
|
|
1167
|
+
revertData: errorData?.data ?? "0x",
|
|
1168
|
+
revertFlags: errorData?.flags ?? 0n,
|
|
1169
|
+
gasConsumed: callResult.gasConsumed,
|
|
1170
|
+
gasRequired: callResult.gasRequired,
|
|
1171
|
+
storageDeposit: callResult.storageDeposit?.value
|
|
1172
|
+
}, {
|
|
1173
|
+
contractAddress,
|
|
1174
|
+
functionName,
|
|
1175
|
+
signerSubstrateAddress: this.substrateAddress,
|
|
1176
|
+
signerEvmAddress: this.evmAddress ?? void 0,
|
|
1177
|
+
value: 0n,
|
|
1178
|
+
encodedData: encodedCallData,
|
|
1179
|
+
args,
|
|
1180
|
+
contracts: this._contracts
|
|
1181
|
+
}));
|
|
1182
|
+
}
|
|
1183
|
+
const rawData = callResult.result.value.data ?? "0x";
|
|
1184
|
+
if (rawData === "0x" || rawData === "" || rawData.length <= 2) return null;
|
|
1185
|
+
return decodeFunctionResult({ abi: contractAbi, functionName, data: rawData });
|
|
1186
|
+
}
|
|
1140
1187
|
async contractTransaction(contractAddress, value, contractAbi, functionName, args = [], statusCallback = () => {
|
|
1141
1188
|
}, { useNoncePolling, verifyEffect } = {}) {
|
|
1142
1189
|
this.ensureConnected();
|
|
@@ -1154,7 +1201,8 @@ var DotNS = class {
|
|
|
1154
1201
|
const checkAddress = (ownerAddress || this.evmAddress).toLowerCase();
|
|
1155
1202
|
const tokenId = computeDomainTokenId(label);
|
|
1156
1203
|
try {
|
|
1157
|
-
const owner = await withTimeout(this.
|
|
1204
|
+
const owner = await withTimeout(this.contractCallNullable(this._contracts.DOTNS_REGISTRAR, DOTNS_REGISTRAR_ABI, "ownerOf", [tokenId]), 3e4, "ownerOf");
|
|
1205
|
+
if (owner === null) return { owned: false, owner: null };
|
|
1158
1206
|
const owned = owner.toLowerCase() === checkAddress;
|
|
1159
1207
|
return { owned, owner };
|
|
1160
1208
|
} catch {
|
|
@@ -1187,7 +1235,7 @@ var DotNS = class {
|
|
|
1187
1235
|
if (!this.clientWrapper) return { owned: false, owner: null };
|
|
1188
1236
|
const node = namehash(`${sublabel}.${parentLabel}.dot`);
|
|
1189
1237
|
try {
|
|
1190
|
-
const owner = await withTimeout(this.
|
|
1238
|
+
const owner = await withTimeout(this.contractCallNullable(this._contracts.DOTNS_REGISTRY, DOTNS_REGISTRY_ABI, "owner", [node]), 3e4, "owner");
|
|
1191
1239
|
if (!owner || owner === zeroAddress) return { owned: false, owner: null };
|
|
1192
1240
|
const owned = owner.toLowerCase() === this.evmAddress.toLowerCase();
|
|
1193
1241
|
return { owned, owner };
|
|
@@ -1310,7 +1358,7 @@ var DotNS = class {
|
|
|
1310
1358
|
let onChainValue = "";
|
|
1311
1359
|
let lastPrintedElapsed = -1;
|
|
1312
1360
|
while (true) {
|
|
1313
|
-
const onChain = await withTimeout(this.
|
|
1361
|
+
const onChain = await withTimeout(this.contractCallNullable(this._contracts.DOTNS_RESOLVER, DOTNS_TEXT_RESOLVER_ABI, "text", [node, key]), 3e4, "text");
|
|
1314
1362
|
onChainValue = onChain ?? "";
|
|
1315
1363
|
if (onChainValue === value) break;
|
|
1316
1364
|
const nowChainMs = Number(await this.clientWrapper.client.query.Timestamp.Now.getValue());
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
package_default,
|
|
3
3
|
writeRunState
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-Y5EWXXFE.js";
|
|
5
5
|
|
|
6
6
|
// src/memory-report.ts
|
|
7
7
|
import * as fs2 from "fs";
|
|
@@ -238,6 +238,14 @@ function getDeployAttributes(domain) {
|
|
|
238
238
|
"deploy.pool.nonce_collision_reupload_count": 0,
|
|
239
239
|
// Manifest-aware Phase A trust: count of section-1 CIDs trusted from prev manifest.
|
|
240
240
|
"deploy.phase_a.chunks_trusted": 0,
|
|
241
|
+
// Manifest fetch outcome. Seeded so every span carries the attributes even when
|
|
242
|
+
// fetchPreviousManifest is never reached (first deploy, early error, non-incremental
|
|
243
|
+
// path). "none" + "0" form the denominator for ratio queries:
|
|
244
|
+
// count_if(deploy.manifest.fetch_source, "heuristic_fallback") / count().
|
|
245
|
+
// Both string-valued per @sentry/node EAP numeric-attribute caveat.
|
|
246
|
+
"deploy.manifest.fetch_source": "none",
|
|
247
|
+
"deploy.manifest.fetch_attempts": "0",
|
|
248
|
+
"deploy.manifest.bytes_downloaded": "0",
|
|
241
249
|
// Bulletin storage upload chain receipt (root-node tx, or last chunk when root skipped).
|
|
242
250
|
// Empty-string default so every span carries the attribute for filter queries.
|
|
243
251
|
"bulletin.upload.tx_hash": "",
|
|
@@ -288,16 +296,49 @@ function computeDeployOutcome(errorCategory, isSad, sadReason) {
|
|
|
288
296
|
if (isSad) return `sad_${sadReason}`;
|
|
289
297
|
return "clean";
|
|
290
298
|
}
|
|
299
|
+
var ERROR_KIND_RULES = [
|
|
300
|
+
[/Contract reverted|Contract execution would revert|revert(?:ed|ing)?\s*\(flags=[0-9]+\)/i, "contract-revert"],
|
|
301
|
+
[/timed out after \d+s waiting for block|Transaction not included after \d+s|Transaction did not settle within/i, "chain-timeout"],
|
|
302
|
+
[/\bstale\b.*nonce|nonce.*\bstale\b|"type"\s*:\s*"Future"|Invalid::Future|tx rejected by pool/i, "nonce-stale"],
|
|
303
|
+
[/heartbeat timeout|WS halt|Unable to connect|ChainHead disjointed|websocket.*closed|socket closed|disconnect/i, "connection"],
|
|
304
|
+
[/requires ProofOfPersonhoodFull,\s*but this signer is NoStatus/i, "naming.pop_required"],
|
|
305
|
+
[/Domain\s+\S+\.dot\s+is already owned by\s+0x[a-fA-F0-9]+/i, "naming.already_owned"],
|
|
306
|
+
[/Cannot deploy\s+[\w.-]+\.dot:\s*parent\s+[\w.-]+\.dot\s+is owned by/i, "naming.subdomain_orphan"],
|
|
307
|
+
[/Post-deploy verification failed for .+: on-chain contenthash is /i, "verify.contenthash_mismatch"],
|
|
308
|
+
[/Deploy verification failed:\s*DAG-PB root.+not finalised/i, "verify.dagpb_not_finalised"],
|
|
309
|
+
[/Retry budget exhausted:.*recovery attempts/i, "network.recovery_exhausted"],
|
|
310
|
+
[/ReviveApi\.\w+ timed out after \d+ms/i, "chain.api_timeout"],
|
|
311
|
+
[/^INVARIANT FAILED:/i, "tool.invariant"]
|
|
312
|
+
];
|
|
291
313
|
function classifyErrorKind(msg) {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
if (/heartbeat timeout|WS halt|Unable to connect|ChainHead disjointed|websocket.*closed|socket closed|disconnect/i.test(msg)) return "connection";
|
|
314
|
+
for (const [re, kind] of ERROR_KIND_RULES) {
|
|
315
|
+
if (re.test(msg)) return kind;
|
|
316
|
+
}
|
|
296
317
|
return "unknown";
|
|
297
318
|
}
|
|
298
319
|
function sanitizeErrorMessage(msg) {
|
|
299
320
|
return scrubPaths(msg.slice(0, 500));
|
|
300
321
|
}
|
|
322
|
+
function analyseErrorPattern(msg) {
|
|
323
|
+
const tags = [];
|
|
324
|
+
const len = msg.length;
|
|
325
|
+
if (len < 50) tags.push("len:lt50");
|
|
326
|
+
else if (len < 100) tags.push("len:50-99");
|
|
327
|
+
else if (len < 200) tags.push("len:100-199");
|
|
328
|
+
else if (len < 500) tags.push("len:200-499");
|
|
329
|
+
else tags.push("len:gte500");
|
|
330
|
+
if (/[a-z]+:\/\/[^\s:/?#]+:[^\s@/?#]+@/i.test(msg)) tags.push("url-userinfo");
|
|
331
|
+
const longHexRuns = (msg.match(/[0-9a-fA-F]{40,}/g) ?? []).filter((h) => !h.toLowerCase().startsWith("e30") && h.length !== 40);
|
|
332
|
+
if (longHexRuns.length > 0) tags.push(`long-hex:${Math.min(longHexRuns.length, 9)}`);
|
|
333
|
+
const b64ish = msg.match(/[A-Za-z0-9+/=]{30,}/g) ?? [];
|
|
334
|
+
if (b64ish.some((s) => /[A-Z]/.test(s) && /[a-z]/.test(s) && /[0-9]/.test(s))) tags.push("base64ish");
|
|
335
|
+
if (/\b[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}\b/.test(msg)) tags.push("jwt-shape");
|
|
336
|
+
const evmCount = (msg.match(/0x[a-fA-F0-9]{40}\b/g) ?? []).length;
|
|
337
|
+
if (evmCount > 0) tags.push(`evm:${Math.min(evmCount, 9)}`);
|
|
338
|
+
if (/\b[1-9A-HJ-NP-Za-km-z]{46,49}\b/.test(msg)) tags.push("ss58-shape");
|
|
339
|
+
if (/(?:\b[a-z]{3,8}\s){11,23}\b[a-z]{3,8}\b/.test(msg)) tags.push("mnemonic-shape");
|
|
340
|
+
return tags.join(",");
|
|
341
|
+
}
|
|
301
342
|
async function withSpan(op, description, attributes, fn) {
|
|
302
343
|
if (!Sentry) return fn();
|
|
303
344
|
return Sentry.startSpan({ op, name: description, attributes }, async (span) => {
|
|
@@ -308,6 +349,7 @@ async function withSpan(op, description, attributes, fn) {
|
|
|
308
349
|
span.setAttribute("error.message", msg);
|
|
309
350
|
span.setAttribute("deploy.error_kind", classifyErrorKind(msg));
|
|
310
351
|
span.setAttribute("deploy.error_message", sanitizeErrorMessage(msg));
|
|
352
|
+
span.setAttribute("deploy.error_pattern_signature", analyseErrorPattern(msg));
|
|
311
353
|
span.setStatus({ code: 2, message: "internal_error" });
|
|
312
354
|
throw error;
|
|
313
355
|
}
|
|
@@ -407,6 +449,7 @@ async function withDeploySpan(domain, fn) {
|
|
|
407
449
|
span.setAttribute("deploy.error_category", errorCategory);
|
|
408
450
|
span.setAttribute("deploy.error_kind", classifyErrorKind(msg));
|
|
409
451
|
span.setAttribute("deploy.error_message", sanitizeErrorMessage(msg));
|
|
452
|
+
span.setAttribute("deploy.error_pattern_signature", analyseErrorPattern(msg));
|
|
410
453
|
currentErrorCategory = errorCategory;
|
|
411
454
|
const isExpected = isExpectedError(msg);
|
|
412
455
|
span.setAttribute("deploy.expected", isExpected ? "true" : "false");
|
|
@@ -667,6 +710,7 @@ export {
|
|
|
667
710
|
computeDeployOutcome,
|
|
668
711
|
classifyErrorKind,
|
|
669
712
|
sanitizeErrorMessage,
|
|
713
|
+
analyseErrorPattern,
|
|
670
714
|
withSpan,
|
|
671
715
|
sampleMemory,
|
|
672
716
|
withDeploySpan,
|
package/dist/chunk-probe.js
CHANGED
|
@@ -5,9 +5,9 @@ import {
|
|
|
5
5
|
_decodeStorageValue,
|
|
6
6
|
_resetProbeSession,
|
|
7
7
|
probeChunks
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-
|
|
10
|
-
import "./chunk-
|
|
8
|
+
} from "./chunk-KLXIR7NQ.js";
|
|
9
|
+
import "./chunk-P7XM4NJG.js";
|
|
10
|
+
import "./chunk-Y5EWXXFE.js";
|
|
11
11
|
export {
|
|
12
12
|
ChainProbeCrossValidationError,
|
|
13
13
|
ChainProbeMetadataError,
|
package/dist/deploy.d.ts
CHANGED
|
@@ -60,7 +60,7 @@ interface StoredChunk {
|
|
|
60
60
|
declare const DEFAULT_BULLETIN_RPC = "wss://paseo-bulletin-rpc.polkadot.io";
|
|
61
61
|
declare const DEFAULT_POOL_SIZE = 10;
|
|
62
62
|
declare function setWsHaltCallback(cb: (() => void) | null): void;
|
|
63
|
-
declare const CHUNK_MORTALITY_PERIOD
|
|
63
|
+
declare const CHUNK_MORTALITY_PERIOD: number;
|
|
64
64
|
declare function retryBudgetExhausted(history: number[], maxEvents: number, windowMs: number, now?: number): boolean;
|
|
65
65
|
declare function isConnectionError(error: any): boolean;
|
|
66
66
|
declare function deriveRootSigner(mnemonic: string, path?: string): {
|
|
@@ -170,6 +170,11 @@ declare function checkDeploySize(carBytes: number, opts: {
|
|
|
170
170
|
allowLargeDeploy?: boolean;
|
|
171
171
|
}): SizeDecision;
|
|
172
172
|
declare function resolveReproducibleTimestamp(source: string): string;
|
|
173
|
+
declare function applyManifestFetchAttributes(fetched: {
|
|
174
|
+
source: string;
|
|
175
|
+
attempts?: number;
|
|
176
|
+
bytesDownloaded?: number;
|
|
177
|
+
}): void;
|
|
173
178
|
declare function storeDirectoryV2(directoryPath: string, opts?: StoreDirectoryOptions): Promise<{
|
|
174
179
|
storageCid: string;
|
|
175
180
|
ipfsCid: string;
|
|
@@ -262,6 +267,15 @@ declare function resolveDotnsConnectOptions(options: Pick<DeployOptions, "mnemon
|
|
|
262
267
|
registerStorageDeposit?: bigint;
|
|
263
268
|
};
|
|
264
269
|
declare function estimateUploadBytes(content: DeployContent): Promise<number | null>;
|
|
270
|
+
/**
|
|
271
|
+
* Throws NonRetryableError if a subdomain is owned by a different address
|
|
272
|
+
* than the current signer. Called in the preflight branch before chunk upload.
|
|
273
|
+
* Issue #562: preflight was only checking `owned`, not comparing `owner`.
|
|
274
|
+
*/
|
|
275
|
+
declare function assertSubdomainOwnerMatchesSigner(result: {
|
|
276
|
+
owned: boolean;
|
|
277
|
+
owner: string | null | undefined;
|
|
278
|
+
}, signerEvmAddress: string | null | undefined, sublabel: string, parentLabel: string): void;
|
|
265
279
|
declare function deploy(content: DeployContent, domainName?: string | null, options?: DeployOptions): Promise<DeployResult>;
|
|
266
280
|
|
|
267
|
-
export { CHUNK_MORTALITY_PERIOD, DEFAULT_BULLETIN_RPC, DEFAULT_POOL_SIZE, type DeployContent, type DeployOptions, type DeployResult, ENCRYPT_KEY_LEN, ENCRYPT_MAGIC, ENCRYPT_NONCE_LEN, ENCRYPT_PBKDF2_ITERATIONS, ENCRYPT_SALT_LEN, ENCRYPT_TAG_LEN, type SizeDecision, type StoreDirectoryOptions, __assignDenseNoncesForTest, buildFilesMap, checkDeploySize, chunk, computeStorageCid, createCID, deploy, deriveRootSigner, detectFramework, encodeContenthash, encryptContent, estimateUploadBytes, friendlyChainError, hasIPFS, isConnectionError, merkleize, resolveDotnsConnectOptions, resolveReproducibleTimestamp, retryBudgetExhausted, setWsHaltCallback, storeChunkedContent, storeDirectory, storeDirectoryV2, storeFile };
|
|
281
|
+
export { CHUNK_MORTALITY_PERIOD, DEFAULT_BULLETIN_RPC, DEFAULT_POOL_SIZE, type DeployContent, type DeployOptions, type DeployResult, ENCRYPT_KEY_LEN, ENCRYPT_MAGIC, ENCRYPT_NONCE_LEN, ENCRYPT_PBKDF2_ITERATIONS, ENCRYPT_SALT_LEN, ENCRYPT_TAG_LEN, type SizeDecision, type StoreDirectoryOptions, __assignDenseNoncesForTest, applyManifestFetchAttributes, assertSubdomainOwnerMatchesSigner, buildFilesMap, checkDeploySize, chunk, computeStorageCid, createCID, deploy, deriveRootSigner, detectFramework, encodeContenthash, encryptContent, estimateUploadBytes, friendlyChainError, hasIPFS, isConnectionError, merkleize, resolveDotnsConnectOptions, resolveReproducibleTimestamp, retryBudgetExhausted, setWsHaltCallback, storeChunkedContent, storeDirectory, storeDirectoryV2, storeFile };
|
package/dist/deploy.js
CHANGED
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
ENCRYPT_SALT_LEN,
|
|
10
10
|
ENCRYPT_TAG_LEN,
|
|
11
11
|
__assignDenseNoncesForTest,
|
|
12
|
+
applyManifestFetchAttributes,
|
|
13
|
+
assertSubdomainOwnerMatchesSigner,
|
|
12
14
|
buildFilesMap,
|
|
13
15
|
checkDeploySize,
|
|
14
16
|
chunk,
|
|
@@ -32,19 +34,19 @@ import {
|
|
|
32
34
|
storeDirectory,
|
|
33
35
|
storeDirectoryV2,
|
|
34
36
|
storeFile
|
|
35
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-JHD3OP3E.js";
|
|
36
38
|
import "./chunk-KHVTYIIX.js";
|
|
37
39
|
import "./chunk-KOSF5FDO.js";
|
|
38
40
|
import "./chunk-FZWJV5AD.js";
|
|
39
41
|
import "./chunk-S7EM5VMW.js";
|
|
40
|
-
import "./chunk-
|
|
41
|
-
import "./chunk-
|
|
42
|
-
import "./chunk-
|
|
42
|
+
import "./chunk-EJUBFDXU.js";
|
|
43
|
+
import "./chunk-3AJST77I.js";
|
|
44
|
+
import "./chunk-KLXIR7NQ.js";
|
|
43
45
|
import "./chunk-C2TS5MER.js";
|
|
44
|
-
import "./chunk-
|
|
46
|
+
import "./chunk-MRRSSWRM.js";
|
|
45
47
|
import "./chunk-QMYW3D6E.js";
|
|
46
|
-
import "./chunk-
|
|
47
|
-
import "./chunk-
|
|
48
|
+
import "./chunk-P7XM4NJG.js";
|
|
49
|
+
import "./chunk-Y5EWXXFE.js";
|
|
48
50
|
import "./chunk-MGU5I7H5.js";
|
|
49
51
|
import {
|
|
50
52
|
EXIT_CODE_NO_RETRY,
|
|
@@ -64,6 +66,8 @@ export {
|
|
|
64
66
|
EXIT_CODE_NO_RETRY,
|
|
65
67
|
NonRetryableError,
|
|
66
68
|
__assignDenseNoncesForTest,
|
|
69
|
+
applyManifestFetchAttributes,
|
|
70
|
+
assertSubdomainOwnerMatchesSigner,
|
|
67
71
|
buildFilesMap,
|
|
68
72
|
checkDeploySize,
|
|
69
73
|
chunk,
|
package/dist/dotns.d.ts
CHANGED
|
@@ -159,7 +159,9 @@ declare function computeDomainTokenId(label: string): bigint;
|
|
|
159
159
|
declare function countTrailingDigits(label: string): number;
|
|
160
160
|
declare function stripTrailingDigits(label: string): string;
|
|
161
161
|
declare function sanitizeDomainLabel(label: string): string;
|
|
162
|
-
declare function validateDomainLabel(label: string
|
|
162
|
+
declare function validateDomainLabel(label: string, opts?: {
|
|
163
|
+
checkReserved?: boolean;
|
|
164
|
+
}): string;
|
|
163
165
|
declare function isCommitmentMature(chainNowSeconds: number, commitTimestampSeconds: number, minimumAgeSeconds: number): boolean;
|
|
164
166
|
declare function isCommitmentTimingBarerevert(msg: string): boolean;
|
|
165
167
|
declare function classifyDotnsLabel(label: string): {
|
|
@@ -301,6 +303,14 @@ declare class DotNS {
|
|
|
301
303
|
} | null>;
|
|
302
304
|
private submitTransfer;
|
|
303
305
|
contractCall(contractAddress: string, contractAbi: readonly any[], functionName: string, args?: any[]): Promise<any>;
|
|
306
|
+
/**
|
|
307
|
+
* Like contractCall, but returns null when the chain replies with empty data
|
|
308
|
+
* ("0x"). Use this for view functions where an unset storage slot is a
|
|
309
|
+
* meaningful answer (e.g. resolver(node) for a name with no resolver,
|
|
310
|
+
* text records, optional ownership lookups). Use the strict contractCall
|
|
311
|
+
* for read paths that must always return a value.
|
|
312
|
+
*/
|
|
313
|
+
contractCallNullable(contractAddress: string, contractAbi: readonly any[], functionName: string, args?: any[]): Promise<any | null>;
|
|
304
314
|
contractTransaction(contractAddress: string, value: bigint, contractAbi: readonly any[], functionName: string, args?: any[], statusCallback?: (status: string) => void, { useNoncePolling, verifyEffect }?: {
|
|
305
315
|
useNoncePolling?: boolean;
|
|
306
316
|
verifyEffect?: () => Promise<boolean>;
|
package/dist/dotns.js
CHANGED
|
@@ -41,10 +41,10 @@ import {
|
|
|
41
41
|
stripTrailingDigits,
|
|
42
42
|
validateDomainLabel,
|
|
43
43
|
verifyNonceAdvanced
|
|
44
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-MRRSSWRM.js";
|
|
45
45
|
import "./chunk-QMYW3D6E.js";
|
|
46
|
-
import "./chunk-
|
|
47
|
-
import "./chunk-
|
|
46
|
+
import "./chunk-P7XM4NJG.js";
|
|
47
|
+
import "./chunk-Y5EWXXFE.js";
|
|
48
48
|
import "./chunk-MGU5I7H5.js";
|
|
49
49
|
import "./chunk-ZOC4GITL.js";
|
|
50
50
|
export {
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
deploy,
|
|
3
3
|
merkleizeJS,
|
|
4
4
|
merkleizeWithStableOrder
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-JHD3OP3E.js";
|
|
6
6
|
import {
|
|
7
7
|
computeStats,
|
|
8
8
|
renderSummary,
|
|
@@ -24,18 +24,18 @@ import {
|
|
|
24
24
|
isVolatilePath,
|
|
25
25
|
parseManifest
|
|
26
26
|
} from "./chunk-S7EM5VMW.js";
|
|
27
|
-
import "./chunk-
|
|
28
|
-
import "./chunk-
|
|
27
|
+
import "./chunk-EJUBFDXU.js";
|
|
28
|
+
import "./chunk-3AJST77I.js";
|
|
29
29
|
import {
|
|
30
30
|
probeChunks
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-KLXIR7NQ.js";
|
|
32
32
|
import "./chunk-C2TS5MER.js";
|
|
33
33
|
import {
|
|
34
34
|
DEFAULT_MNEMONIC,
|
|
35
35
|
DotNS,
|
|
36
36
|
parseDomainName,
|
|
37
37
|
sanitizeDomainLabel
|
|
38
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-MRRSSWRM.js";
|
|
39
39
|
import {
|
|
40
40
|
bootstrapPool,
|
|
41
41
|
derivePoolAccounts,
|
|
@@ -43,7 +43,7 @@ import {
|
|
|
43
43
|
fetchPoolAuthorizations,
|
|
44
44
|
selectAccount
|
|
45
45
|
} from "./chunk-QMYW3D6E.js";
|
|
46
|
-
import "./chunk-
|
|
46
|
+
import "./chunk-P7XM4NJG.js";
|
|
47
47
|
import {
|
|
48
48
|
VERSION,
|
|
49
49
|
loadRunState,
|
|
@@ -53,7 +53,7 @@ import {
|
|
|
53
53
|
shouldSkipStaleWarning,
|
|
54
54
|
stateFilePath,
|
|
55
55
|
writeRunState
|
|
56
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-Y5EWXXFE.js";
|
|
57
57
|
import {
|
|
58
58
|
DEFAULT_ENV_ID,
|
|
59
59
|
defaultBundledPath,
|
package/dist/memory-report.js
CHANGED
package/dist/merkle.js
CHANGED
|
@@ -6,19 +6,19 @@ import {
|
|
|
6
6
|
merkleizeKuboBackend,
|
|
7
7
|
merkleizeWithStableOrder,
|
|
8
8
|
rebuildOrderedCarFromBytes
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-JHD3OP3E.js";
|
|
10
10
|
import "./chunk-KHVTYIIX.js";
|
|
11
11
|
import "./chunk-KOSF5FDO.js";
|
|
12
12
|
import "./chunk-FZWJV5AD.js";
|
|
13
13
|
import "./chunk-S7EM5VMW.js";
|
|
14
|
-
import "./chunk-
|
|
15
|
-
import "./chunk-
|
|
16
|
-
import "./chunk-
|
|
14
|
+
import "./chunk-EJUBFDXU.js";
|
|
15
|
+
import "./chunk-3AJST77I.js";
|
|
16
|
+
import "./chunk-KLXIR7NQ.js";
|
|
17
17
|
import "./chunk-C2TS5MER.js";
|
|
18
|
-
import "./chunk-
|
|
18
|
+
import "./chunk-MRRSSWRM.js";
|
|
19
19
|
import "./chunk-QMYW3D6E.js";
|
|
20
|
-
import "./chunk-
|
|
21
|
-
import "./chunk-
|
|
20
|
+
import "./chunk-P7XM4NJG.js";
|
|
21
|
+
import "./chunk-Y5EWXXFE.js";
|
|
22
22
|
import "./chunk-MGU5I7H5.js";
|
|
23
23
|
import "./chunk-ZOC4GITL.js";
|
|
24
24
|
import "./chunk-HOTQDYHD.js";
|
|
@@ -21,10 +21,10 @@ import {
|
|
|
21
21
|
} from "../chunk-T7EEVWNU.js";
|
|
22
22
|
import {
|
|
23
23
|
WS_HEARTBEAT_TIMEOUT_MS
|
|
24
|
-
} from "../chunk-
|
|
24
|
+
} from "../chunk-MRRSSWRM.js";
|
|
25
25
|
import "../chunk-QMYW3D6E.js";
|
|
26
|
-
import "../chunk-
|
|
27
|
-
import "../chunk-
|
|
26
|
+
import "../chunk-P7XM4NJG.js";
|
|
27
|
+
import "../chunk-Y5EWXXFE.js";
|
|
28
28
|
import {
|
|
29
29
|
loadEnvironments
|
|
30
30
|
} from "../chunk-MGU5I7H5.js";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
WS_HEARTBEAT_TIMEOUT_MS
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-MRRSSWRM.js";
|
|
4
4
|
import "../chunk-QMYW3D6E.js";
|
|
5
|
-
import "../chunk-
|
|
6
|
-
import "../chunk-
|
|
5
|
+
import "../chunk-P7XM4NJG.js";
|
|
6
|
+
import "../chunk-Y5EWXXFE.js";
|
|
7
7
|
import {
|
|
8
8
|
loadEnvironments
|
|
9
9
|
} from "../chunk-MGU5I7H5.js";
|
package/dist/run-state.js
CHANGED
package/dist/telemetry.d.ts
CHANGED
|
@@ -29,9 +29,16 @@ type DeployErrorCategory = 'user' | 'environment' | 'internal' | 'unknown';
|
|
|
29
29
|
declare function classifyDeployError(msg: string): DeployErrorCategory;
|
|
30
30
|
declare function classifySadReason(message: string): string;
|
|
31
31
|
declare function computeDeployOutcome(errorCategory: DeployErrorCategory | null, isSad: boolean, sadReason: string): string;
|
|
32
|
-
type DeployErrorKind = 'contract-revert' | 'chain-timeout' | 'nonce-stale' | 'connection' | 'unknown';
|
|
32
|
+
type DeployErrorKind = 'contract-revert' | 'chain-timeout' | 'nonce-stale' | 'connection' | 'naming.pop_required' | 'naming.already_owned' | 'naming.subdomain_orphan' | 'verify.contenthash_mismatch' | 'verify.dagpb_not_finalised' | 'network.recovery_exhausted' | 'chain.api_timeout' | 'tool.invariant' | 'unknown';
|
|
33
33
|
declare function classifyErrorKind(msg: string): DeployErrorKind;
|
|
34
34
|
declare function sanitizeErrorMessage(msg: string): string;
|
|
35
|
+
/**
|
|
36
|
+
* Classify an error message's *shape* (length, presence of certain patterns)
|
|
37
|
+
* without excerpting content. The result is a comma-joined list of tags;
|
|
38
|
+
* future analysis of scrubbed events will use this to identify which content
|
|
39
|
+
* shape triggered Sentry's PII scrubber, so we can build a real sanitiser.
|
|
40
|
+
*/
|
|
41
|
+
declare function analyseErrorPattern(msg: string): string;
|
|
35
42
|
declare function withSpan<T>(op: string, description: string, attributes: Record<string, string | number | boolean | undefined>, fn: () => T | Promise<T>): Promise<T>;
|
|
36
43
|
declare function sampleMemory(stage: string): void;
|
|
37
44
|
declare function withDeploySpan<T>(domain: string, fn: () => T | Promise<T>): Promise<T>;
|
|
@@ -46,4 +53,4 @@ declare function setDeploySentryTag(key: string, value: string): void;
|
|
|
46
53
|
declare function captureWarning(message: string, context?: Record<string, unknown>): void;
|
|
47
54
|
declare function flush(): Promise<void>;
|
|
48
55
|
|
|
49
|
-
export { type DeployErrorCategory, type DeployErrorKind, type InternalContextSignals, VERSION, __setDeployRootSpanForTest, __setSentryForTest, captureWarning, classifyDeployError, classifyErrorKind, classifySadReason, closeTelemetry, computeDeployOutcome, flush, getCurrentSentryTraceId, getDeployAttributes, initTelemetry, isExpectedError, isInternalContext, isInternalContextFromSignals, markRelaunchOomHintShown, resolveRepo, resolveRunner, resolveRunnerType, sampleMemory, sanitizeBranch, sanitizeErrorMessage, sanitizeRepo, scrubPaths, setDeployAttribute, setDeployReportContext, setDeploySentryTag, setRunStateActive, truncateAddress, withDeploySpan, withSpan };
|
|
56
|
+
export { type DeployErrorCategory, type DeployErrorKind, type InternalContextSignals, VERSION, __setDeployRootSpanForTest, __setSentryForTest, analyseErrorPattern, captureWarning, classifyDeployError, classifyErrorKind, classifySadReason, closeTelemetry, computeDeployOutcome, flush, getCurrentSentryTraceId, getDeployAttributes, initTelemetry, isExpectedError, isInternalContext, isInternalContextFromSignals, markRelaunchOomHintShown, resolveRepo, resolveRunner, resolveRunnerType, sampleMemory, sanitizeBranch, sanitizeErrorMessage, sanitizeRepo, scrubPaths, setDeployAttribute, setDeployReportContext, setDeploySentryTag, setRunStateActive, truncateAddress, withDeploySpan, withSpan };
|
package/dist/telemetry.js
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
VERSION,
|
|
3
3
|
__setDeployRootSpanForTest,
|
|
4
4
|
__setSentryForTest,
|
|
5
|
+
analyseErrorPattern,
|
|
5
6
|
captureWarning,
|
|
6
7
|
classifyDeployError,
|
|
7
8
|
classifyErrorKind,
|
|
@@ -31,12 +32,13 @@ import {
|
|
|
31
32
|
truncateAddress,
|
|
32
33
|
withDeploySpan,
|
|
33
34
|
withSpan
|
|
34
|
-
} from "./chunk-
|
|
35
|
-
import "./chunk-
|
|
35
|
+
} from "./chunk-P7XM4NJG.js";
|
|
36
|
+
import "./chunk-Y5EWXXFE.js";
|
|
36
37
|
export {
|
|
37
38
|
VERSION,
|
|
38
39
|
__setDeployRootSpanForTest,
|
|
39
40
|
__setSentryForTest,
|
|
41
|
+
analyseErrorPattern,
|
|
40
42
|
captureWarning,
|
|
41
43
|
classifyDeployError,
|
|
42
44
|
classifyErrorKind,
|
package/dist/version-check.js
CHANGED
|
@@ -11,9 +11,9 @@ import {
|
|
|
11
11
|
isPreReleaseVersion,
|
|
12
12
|
preReleaseWarning,
|
|
13
13
|
promptYesNo
|
|
14
|
-
} from "./chunk-
|
|
15
|
-
import "./chunk-
|
|
16
|
-
import "./chunk-
|
|
14
|
+
} from "./chunk-3AJST77I.js";
|
|
15
|
+
import "./chunk-P7XM4NJG.js";
|
|
16
|
+
import "./chunk-Y5EWXXFE.js";
|
|
17
17
|
export {
|
|
18
18
|
assessVersion,
|
|
19
19
|
checkNodeVersion,
|
package/package.json
CHANGED
|
@@ -2,8 +2,15 @@
|
|
|
2
2
|
// Selective retry wrapper for release E2E.
|
|
3
3
|
//
|
|
4
4
|
// Spawns a child process (the deploy CLI / test runner invocation), captures
|
|
5
|
-
// stderr, classifies the failure mode, and exits with 75 on
|
|
6
|
-
// matches (retry-eligible) or the child's own exit code otherwise.
|
|
5
|
+
// stdout AND stderr, classifies the failure mode, and exits with 75 on
|
|
6
|
+
// flake-class matches (retry-eligible) or the child's own exit code otherwise.
|
|
7
|
+
//
|
|
8
|
+
// Both streams are passed through to the parent's stdout/stderr so the GH
|
|
9
|
+
// Actions job log still shows everything live.
|
|
10
|
+
//
|
|
11
|
+
// node --test captures each test-file subprocess's output and re-emits it as
|
|
12
|
+
// TAP YAML on its own stdout — so deploy CLI errors (e.g. "ChainHead
|
|
13
|
+
// disjointed") appear on stdout, not stderr. Capturing both is required.
|
|
7
14
|
//
|
|
8
15
|
// Configure nick-fields/retry@v3 with retry_on_exit_code: 75 so retries only
|
|
9
16
|
// fire for the named transient classes. See
|
|
@@ -20,12 +27,16 @@ const FLAKE_PATTERNS = [
|
|
|
20
27
|
"ChainHead disjointed", // RPC reorg / WS flake
|
|
21
28
|
"Connection lost", // WS hard drop
|
|
22
29
|
"Account mapping did not take effect", // Revive mapping race
|
|
30
|
+
"requires Node.js >=22", // parity-default runner downgrade (Node v18) — infra flake
|
|
31
|
+
"received a shutdown signal", // runner process killed mid-job — CI infra flake
|
|
23
32
|
];
|
|
24
33
|
|
|
25
|
-
|
|
34
|
+
// output: combined stdout+stderr text from the child. Any flake pattern
|
|
35
|
+
// appearing anywhere in the child's output makes the run retry-eligible.
|
|
36
|
+
export function classifyForRetry(output, childExitCode = 1) {
|
|
26
37
|
if (childExitCode === 0) return 0;
|
|
27
38
|
for (const pat of FLAKE_PATTERNS) {
|
|
28
|
-
if (
|
|
39
|
+
if (output.includes(pat)) return 75;
|
|
29
40
|
}
|
|
30
41
|
return childExitCode || 1;
|
|
31
42
|
}
|
|
@@ -37,20 +48,24 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
37
48
|
console.error("usage: release-retry-wrapper.mjs <command> [args...]");
|
|
38
49
|
process.exit(2);
|
|
39
50
|
}
|
|
40
|
-
const child = spawn(cmd, args, { stdio: ["inherit", "
|
|
41
|
-
let
|
|
51
|
+
const child = spawn(cmd, args, { stdio: ["inherit", "pipe", "pipe"] });
|
|
52
|
+
let outputBuf = "";
|
|
53
|
+
child.stdout.on("data", (chunk) => {
|
|
54
|
+
process.stdout.write(chunk); // pass through stdout to job log
|
|
55
|
+
outputBuf += chunk.toString();
|
|
56
|
+
});
|
|
42
57
|
child.stderr.on("data", (chunk) => {
|
|
43
|
-
process.stderr.write(chunk); // pass through to job log
|
|
44
|
-
|
|
58
|
+
process.stderr.write(chunk); // pass through stderr to job log
|
|
59
|
+
outputBuf += chunk.toString();
|
|
45
60
|
});
|
|
46
61
|
child.on("error", (err) => {
|
|
47
62
|
process.stderr.write(`[release-retry-wrapper] failed to spawn: ${err.message}\n`);
|
|
48
63
|
process.exit(1);
|
|
49
64
|
});
|
|
50
|
-
// Use `close` (not `exit`) so
|
|
65
|
+
// Use `close` (not `exit`) so both pipes are fully drained before we
|
|
51
66
|
// classify — `exit` can fire before the last `data` chunk lands.
|
|
52
67
|
child.on("close", (code) => {
|
|
53
|
-
const cls = classifyForRetry(
|
|
68
|
+
const cls = classifyForRetry(outputBuf, code ?? 1);
|
|
54
69
|
if (cls === 75) {
|
|
55
70
|
console.error("[release-retry-wrapper] flake-class match — exiting 75 to signal retry");
|
|
56
71
|
}
|