bulletin-deploy 0.11.0 → 0.12.0-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.
Files changed (44) hide show
  1. package/bin/bulletin-bootstrap +23 -1
  2. package/dist/auth-config.js +3 -3
  3. package/dist/bug-report.js +4 -4
  4. package/dist/{chunk-BN4PKWRZ.js → chunk-2SAAHYXX.js} +3 -3
  5. package/dist/{chunk-4CVD6CNN.js → chunk-4ALP4YX6.js} +1 -1
  6. package/dist/{chunk-Y3WDLLDM.js → chunk-4JEFFZOU.js} +2 -2
  7. package/dist/{chunk-WFO7E5CI.js → chunk-CWADRKHP.js} +55 -12
  8. package/dist/{chunk-D4C2PBRO.js → chunk-E77QIUS4.js} +1 -1
  9. package/dist/{chunk-QVAZ3P7Q.js → chunk-EKG4PJDS.js} +38 -1
  10. package/dist/{chunk-MBZFAQF4.js → chunk-JUL7CJDD.js} +1 -1
  11. package/dist/{chunk-EAB5STKF.js → chunk-NHGLS7HU.js} +12 -2
  12. package/dist/{chunk-6C75PEHQ.js → chunk-ODF44V67.js} +1 -1
  13. package/dist/{chunk-ABX6QCX5.js → chunk-WIV5WKAF.js} +158 -61
  14. package/dist/{chunk-MRQPJLPS.js → chunk-XVQICFTB.js} +22 -1
  15. package/dist/{chunk-VFPXDXIV.js → chunk-XWGAPACU.js} +2 -2
  16. package/dist/chunk-probe.d.ts +43 -1
  17. package/dist/chunk-probe.js +11 -5
  18. package/dist/commands/login.js +11 -11
  19. package/dist/commands/logout.js +4 -4
  20. package/dist/commands/transfer.js +4 -4
  21. package/dist/commands/whoami.js +3 -3
  22. package/dist/deploy-actors.js +6 -6
  23. package/dist/deploy.d.ts +45 -1
  24. package/dist/deploy.js +17 -11
  25. package/dist/dotns.d.ts +33 -1
  26. package/dist/dotns.js +14 -4
  27. package/dist/index.d.ts +1 -1
  28. package/dist/index.js +18 -12
  29. package/dist/manifest/publish.js +12 -12
  30. package/dist/memory-report.js +2 -2
  31. package/dist/merkle.js +11 -11
  32. package/dist/personhood/bootstrap.js +4 -4
  33. package/dist/personhood/people-client.js +4 -4
  34. package/dist/personhood/reprove.js +19 -2
  35. package/dist/pool.d.ts +11 -1
  36. package/dist/pool.js +9 -1
  37. package/dist/run-state.js +1 -1
  38. package/dist/sss-allowance-cache.js +4 -4
  39. package/dist/storage-signer.d.ts +28 -3
  40. package/dist/storage-signer.js +13 -11
  41. package/dist/telemetry.js +2 -2
  42. package/dist/version-check.js +3 -3
  43. package/package.json +1 -1
  44. package/tools/release-retry-wrapper.mjs +6 -0
@@ -4,7 +4,7 @@ import * as fs from "node:fs/promises";
4
4
  import * as path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { DEFAULT_BULLETIN_RPC, DEFAULT_POOL_SIZE } from "../dist/deploy.js";
7
- import { bootstrapPool } from "../dist/pool.js";
7
+ import { bootstrapPool, BULLETIN_BLOCKS_PER_DAY, isAutoReauthorizeAllowed } from "../dist/pool.js";
8
8
  import { VERSION } from "../dist/telemetry.js";
9
9
 
10
10
  const args = process.argv.slice(2);
@@ -16,6 +16,7 @@ for (let i = 0; i < args.length; i++) {
16
16
  else if (args[i] === "--authorizer") { flags.authorizer = args[++i]; }
17
17
  else if (args[i] === "--rpc") { flags.rpc = args[++i]; }
18
18
  else if (args[i] === "--env") { flags.env = args[++i]; }
19
+ else if (args[i] === "--pre-check") { flags.preCheck = true; }
19
20
  else if (args[i] === "--version" || args[i] === "-V") { flags.version = true; }
20
21
  else if (args[i] === "--help" || args[i] === "-h") { flags.help = true; }
21
22
  else {
@@ -52,11 +53,22 @@ Options:
52
53
  --rpc wss://... Bulletin RPC endpoint (or set BULLETIN_RPC env var)
53
54
  --env <id> Load environment by id from environments.json
54
55
  --pool-size N Number of pool accounts to check/initialize (default: 10)
56
+ --pre-check E2E pre-flight mode (#1059): renew any account expiring
57
+ within 24h, not just already-expired ones. Requires
58
+ --env, and is hard-gated to environments where
59
+ environments.json has network != "mainnet" and
60
+ bulletinAutoAuthorize: true — refuses to write
61
+ otherwise, even with --authorizer supplied.
55
62
  --version Show version
56
63
  --help Show this help`);
57
64
  process.exit(0);
58
65
  }
59
66
 
67
+ if (flags.preCheck && !flags.env) {
68
+ console.error("Error: --pre-check requires --env <id>");
69
+ process.exit(1);
70
+ }
71
+
60
72
  let rpc = flags.rpc ?? process.env.BULLETIN_RPC ?? DEFAULT_BULLETIN_RPC;
61
73
  const poolSize = flags.poolSize ?? parseInt(process.env.BULLETIN_POOL_SIZE ?? String(DEFAULT_POOL_SIZE), 10);
62
74
  const mnemonic = flags.mnemonic ?? process.env.BULLETIN_POOL_MNEMONIC ?? process.env.MNEMONIC;
@@ -94,6 +106,16 @@ if (flags.env) {
94
106
  }
95
107
 
96
108
  bootstrapOpts.bulletinAuthorizeV2 = envEntry.bulletinAuthorizeV2 ?? false;
109
+
110
+ if (flags.preCheck) {
111
+ // #1059: widen the "needs authorization" threshold to 24h-before-expiry,
112
+ // and hard-gate the actual write to environments cleared for it. This is
113
+ // a config-driven (environments.json) gate, not a CLI flag override —
114
+ // --pre-check can never itself grant write access on an env that isn't
115
+ // eligible.
116
+ bootstrapOpts.reauthBufferBlocks = BULLETIN_BLOCKS_PER_DAY;
117
+ bootstrapOpts.allowAutoReauthorize = isAutoReauthorizeAllowed(envEntry);
118
+ }
97
119
  }
98
120
 
99
121
  await bootstrapPool(rpc, poolSize, mnemonic, bootstrapOpts);
@@ -9,10 +9,10 @@ import {
9
9
  getPeopleChainEndpoints,
10
10
  hasPersistedSession,
11
11
  resolveBulletinEndpoints
12
- } from "./chunk-D4C2PBRO.js";
12
+ } from "./chunk-E77QIUS4.js";
13
13
  import "./chunk-TSPERKUS.js";
14
- import "./chunk-EAB5STKF.js";
15
- import "./chunk-6C75PEHQ.js";
14
+ import "./chunk-NHGLS7HU.js";
15
+ import "./chunk-ODF44V67.js";
16
16
  import "./chunk-JSYQ3JQS.js";
17
17
  import "./chunk-ZOC4GITL.js";
18
18
  export {
@@ -10,10 +10,10 @@ import {
10
10
  offerBugReport,
11
11
  scrubSecrets,
12
12
  setDeployContext
13
- } from "./chunk-BN4PKWRZ.js";
14
- import "./chunk-MBZFAQF4.js";
15
- import "./chunk-EAB5STKF.js";
16
- import "./chunk-6C75PEHQ.js";
13
+ } from "./chunk-2SAAHYXX.js";
14
+ import "./chunk-JUL7CJDD.js";
15
+ import "./chunk-NHGLS7HU.js";
16
+ import "./chunk-ODF44V67.js";
17
17
  export {
18
18
  buildCliFlagsSummary,
19
19
  buildLabels,
@@ -2,15 +2,15 @@ import {
2
2
  classifyErrorArea,
3
3
  isInteractive,
4
4
  promptYesNo
5
- } from "./chunk-MBZFAQF4.js";
5
+ } from "./chunk-JUL7CJDD.js";
6
6
  import {
7
7
  VERSION,
8
8
  getCurrentSentryTraceId,
9
9
  resolveIssueRepoSlug
10
- } from "./chunk-EAB5STKF.js";
10
+ } from "./chunk-NHGLS7HU.js";
11
11
  import {
12
12
  package_default
13
- } from "./chunk-6C75PEHQ.js";
13
+ } from "./chunk-ODF44V67.js";
14
14
 
15
15
  // src/bug-report.ts
16
16
  import { execSync, execFileSync } from "child_process";
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-GRPLHUYC.js";
4
4
  import {
5
5
  DOT_DAPP_ID
6
- } from "./chunk-D4C2PBRO.js";
6
+ } from "./chunk-E77QIUS4.js";
7
7
 
8
8
  // src/sss-allowance-cache.ts
9
9
  import { mkdir, readFile, writeFile, unlink } from "fs/promises";
@@ -4,10 +4,10 @@ import {
4
4
  } from "./chunk-5FLTDWWP.js";
5
5
  import {
6
6
  DOT_PRODUCT_ID
7
- } from "./chunk-D4C2PBRO.js";
7
+ } from "./chunk-E77QIUS4.js";
8
8
  import {
9
9
  DEFAULT_MNEMONIC
10
- } from "./chunk-WFO7E5CI.js";
10
+ } from "./chunk-CWADRKHP.js";
11
11
 
12
12
  // src/deploy-actors.ts
13
13
  var DEFAULT_WORKER_SURI = DEFAULT_MNEMONIC;
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-SI2ZUOYD.js";
4
4
  import {
5
5
  isTestnetSpecName
6
- } from "./chunk-MRQPJLPS.js";
6
+ } from "./chunk-XVQICFTB.js";
7
7
  import {
8
8
  captureWarning,
9
9
  markCodePath,
@@ -11,7 +11,7 @@ import {
11
11
  setDeploySentryTag,
12
12
  truncateAddress,
13
13
  withSpan
14
- } from "./chunk-EAB5STKF.js";
14
+ } from "./chunk-NHGLS7HU.js";
15
15
  import {
16
16
  validateContractAddresses
17
17
  } from "./chunk-JSYQ3JQS.js";
@@ -50,6 +50,11 @@ var CODE_PATHS = {
50
50
  // true → automated mapping via EVM key derivation
51
51
  // false → standard check (already-mapped or manually register)
52
52
  DOTNS_AUTO_MAPPING: "dotns.auto-mapping",
53
+ // NOTE (issue #706): marked "exempt" in tools/check-code-path-coverage.py's
54
+ // CODE_PATHS mirror — it's a real fallback for external DotNS library
55
+ // callers, but Parity-internal telemetry never sees that population, so
56
+ // it always reads 0 hits here regardless of real-world usage. Confirmed
57
+ // non-dead in PR #756 and again for #706; keep the branch and the ID.
53
58
  DOTNS_MANUAL_MAPPING: "dotns.manual-mapping"
54
59
  };
55
60
 
@@ -126,7 +131,23 @@ var TX_WALL_CLOCK_CEILING_MS = 24e4;
126
131
  var TX_NO_PROGRESS_MS = 9e4;
127
132
  var WS_HEARTBEAT_TIMEOUT_MS = 3e5;
128
133
  var DOTNS_TX_MAX_ATTEMPTS = 3;
134
+ var WatcherSilentNoEventError = class extends Error {
135
+ constructor(silentMs) {
136
+ super(`transaction watcher silent for ${Math.floor(silentMs / 1e3)}s \u2014 no response received (did you approve on your phone?)`);
137
+ this.name = "WatcherSilentNoEventError";
138
+ }
139
+ };
129
140
  var VERIFY_EFFECT_CHAIN_SECONDS = 60;
141
+ var NONCE_ADVANCE_VERIFY_RETRIES = 3;
142
+ var NONCE_ADVANCE_VERIFY_RETRY_INTERVAL_MS = 2e3;
143
+ async function verifyEffectWithGrace(verifyEffect, { retries = NONCE_ADVANCE_VERIFY_RETRIES, intervalMs = NONCE_ADVANCE_VERIFY_RETRY_INTERVAL_MS } = {}) {
144
+ if (await verifyEffect()) return true;
145
+ for (let attempt = 0; attempt < retries; attempt++) {
146
+ await new Promise((r) => setTimeout(r, intervalMs));
147
+ if (await verifyEffect()) return true;
148
+ }
149
+ return false;
150
+ }
130
151
  function classifyTxRetryDecision(err) {
131
152
  const msg = err instanceof Error ? err.message : String(err);
132
153
  const lower = msg.toLowerCase();
@@ -138,6 +159,12 @@ function classifyTxRetryDecision(err) {
138
159
  if (lower.includes("transaction watcher silent")) return "retry";
139
160
  return "abort";
140
161
  }
162
+ function classifyWatcherSilentFastFail(err, isPhoneSigner) {
163
+ if (err instanceof WatcherSilentNoEventError && isPhoneSigner === true) {
164
+ return new NonRetryableError("No signature received from the phone \u2014 re-run when you can approve on your phone.");
165
+ }
166
+ return null;
167
+ }
141
168
  var DOTNS_RETRY_BASE_MS = 400;
142
169
  var DOTNS_RETRY_MAX_MS = 6e3;
143
170
  function dotnsRetryBackoffMs(attempt, rand = Math.random) {
@@ -696,7 +723,7 @@ var ReviveClientWrapper = class _ReviveClientWrapper {
696
723
  if (nonce.advanced) {
697
724
  if (opts.verifyEffect) {
698
725
  statusCallback("verifying");
699
- const observed = await opts.verifyEffect();
726
+ const observed = await verifyEffectWithGrace(opts.verifyEffect);
700
727
  if (!observed) {
701
728
  statusCallback("failed");
702
729
  finish(reject)(new Error(`nonce-advance fallback: nonce moved past ${opts.nonceFallback.expectedNonce} but expected on-chain effect not observable (likely a different tx of ours consumed the nonce, or our tx was reorged out)`));
@@ -724,7 +751,11 @@ var ReviveClientWrapper = class _ReviveClientWrapper {
724
751
  const silentMs = Date.now() - lastEventAt;
725
752
  if (silentMs > TX_NO_PROGRESS_MS) {
726
753
  statusCallback("failed");
727
- finish(reject)(new Error(`transaction watcher silent for ${Math.floor(silentMs / 1e3)}s after ${lastEventType}`));
754
+ if (lastEventType === "(none)") {
755
+ finish(reject)(new WatcherSilentNoEventError(silentMs));
756
+ } else {
757
+ finish(reject)(new Error(`transaction watcher silent for ${Math.floor(silentMs / 1e3)}s after ${lastEventType}`));
758
+ }
728
759
  return;
729
760
  }
730
761
  } catch {
@@ -794,6 +825,11 @@ var ReviveClientWrapper = class _ReviveClientWrapper {
794
825
  return await this.signAndSubmitExtrinsic(buildExtrinsic(), signer, filter.callback, opts);
795
826
  } catch (e) {
796
827
  lastError = e;
828
+ const fastFail = classifyWatcherSilentFastFail(e, opts.isPhoneSigner);
829
+ if (fastFail) {
830
+ filter.flush();
831
+ throw fastFail;
832
+ }
797
833
  const decision = classifyTxRetryDecision(e);
798
834
  const msg = e?.message ?? String(e);
799
835
  if (!shouldRetryTxAttempt(attempt, DOTNS_TX_MAX_ATTEMPTS, decision)) {
@@ -1430,18 +1466,19 @@ var DotNS = class {
1430
1466
  if (rawData.length <= 2) {
1431
1467
  const hasCode = await this.clientWrapper.hasContractCode(contractAddress);
1432
1468
  const name = dotnsContractName(contractAddress, this._contracts);
1469
+ const env = this._environmentId ?? "(unset)";
1433
1470
  if (hasCode === false) {
1434
1471
  throw new Error(
1435
- `No contract deployed at ${contractAddress} (${name}) \u2014 the dry-run call to ${functionName} returned empty success data, which on pallet-revive means the target address has no contract code. Check environments.json / --contract config for this network.`
1472
+ `No contract deployed at ${contractAddress} (${name}) env=${env} \u2014 the dry-run call to ${functionName} returned empty success data, which on pallet-revive means the target address has no contract code. Check environments.json / --contract config for this network.`
1436
1473
  );
1437
1474
  }
1438
1475
  if (hasCode === null) {
1439
1476
  throw new Error(
1440
- `Contract call returned empty data \u2014 contract=${name} (${contractAddress}) functionName=${functionName}. Could not verify whether contract code exists at this address (runtime code-presence query failed); investigate the contract/ABI or the configured address.`
1477
+ `Contract call returned empty data \u2014 contract=${name} (${contractAddress}) env=${env} functionName=${functionName}. Could not verify whether contract code exists at this address (runtime code-presence query failed); investigate the contract/ABI or the configured address.`
1441
1478
  );
1442
1479
  }
1443
1480
  throw new Error(
1444
- `Contract call returned empty data \u2014 contract=${name} (${contractAddress}) functionName=${functionName}. The address has contract code but the call returned no bytes, which is unexpected for this read. Investigate the contract/ABI rather than masking it with a default.`
1481
+ `Contract call returned empty data \u2014 contract=${name} (${contractAddress}) env=${env} functionName=${functionName}. The address has contract code but the call returned no bytes, which is unexpected for this read. Investigate the contract/ABI rather than masking it with a default.`
1445
1482
  );
1446
1483
  }
1447
1484
  return decodeFunctionResult({ abi: contractAbi, functionName, data: rawData });
@@ -1819,7 +1856,7 @@ var DotNS = class {
1819
1856
  const target = this._contracts.DOTNS_CONTENT_RESOLVER;
1820
1857
  let current = null;
1821
1858
  try {
1822
- current = await this.contractCall(this._contracts.DOTNS_REGISTRY, DOTNS_REGISTRY_ABI, "resolver", [node]);
1859
+ current = await this.contractCallNullable(this._contracts.DOTNS_REGISTRY, DOTNS_REGISTRY_ABI, "resolver", [node]);
1823
1860
  } catch {
1824
1861
  }
1825
1862
  if (typeof current === "string" && current.toLowerCase() === target.toLowerCase()) {
@@ -1833,7 +1870,7 @@ var DotNS = class {
1833
1870
  async getTextRecord(domainName, key) {
1834
1871
  this.ensureConnected();
1835
1872
  const node = namehash(`${domainName}.dot`);
1836
- const result = await this.contractCall(
1873
+ const result = await this.contractCallNullable(
1837
1874
  this._contracts.DOTNS_CONTENT_RESOLVER,
1838
1875
  DOTNS_TEXT_RESOLVER_ABI,
1839
1876
  "text",
@@ -1880,7 +1917,7 @@ var DotNS = class {
1880
1917
  let onChainValue = "";
1881
1918
  let lastPrintedElapsed = -1;
1882
1919
  while (true) {
1883
- const onChain = await withTimeout(this.contractCall(this._contracts.DOTNS_CONTENT_RESOLVER, DOTNS_TEXT_RESOLVER_ABI, "text", [node, key]), 3e4, "text");
1920
+ const onChain = await withTimeout(this.contractCallNullable(this._contracts.DOTNS_CONTENT_RESOLVER, DOTNS_TEXT_RESOLVER_ABI, "text", [node, key]), 3e4, "text");
1884
1921
  onChainValue = onChain ?? "";
1885
1922
  if (onChainValue === value) break;
1886
1923
  const nowChainMs = Number(await this.clientWrapper.client.query.Timestamp.Now.getValue());
@@ -1937,7 +1974,7 @@ var DotNS = class {
1937
1974
  const startChainMs = Number(await this.clientWrapper.client.query.Timestamp.Now.getValue());
1938
1975
  let lastResults = [];
1939
1976
  while (true) {
1940
- lastResults = await Promise.all(entries.map((e) => withTimeout(this.contractCall(this._contracts.DOTNS_CONTENT_RESOLVER, DOTNS_TEXT_RESOLVER_ABI, "text", [node, e.key]), 3e4, "text").then((onChain) => ({ key: e.key, expected: e.value, onChain: onChain ?? "" }))));
1977
+ lastResults = await Promise.all(entries.map((e) => withTimeout(this.contractCallNullable(this._contracts.DOTNS_CONTENT_RESOLVER, DOTNS_TEXT_RESOLVER_ABI, "text", [node, e.key]), 3e4, "text").then((onChain) => ({ key: e.key, expected: e.value, onChain: onChain ?? "" }))));
1941
1978
  if (lastResults.every((v) => v.onChain === v.expected)) break;
1942
1979
  const nowChainMs = Number(await this.clientWrapper.client.query.Timestamp.Now.getValue());
1943
1980
  const chainElapsed = (nowChainMs - startChainMs) / 1e3;
@@ -2102,10 +2139,11 @@ var DotNS = class {
2102
2139
  this.ensureConnected();
2103
2140
  const node = namehash(`${domainName}.dot`);
2104
2141
  const result = await withTimeout(
2105
- this.contractCall(this._contracts.DOTNS_CONTENT_RESOLVER, DOTNS_CONTENT_RESOLVER_ABI, "contenthash", [node]),
2142
+ this.contractCallNullable(this._contracts.DOTNS_CONTENT_RESOLVER, DOTNS_CONTENT_RESOLVER_ABI, "contenthash", [node]),
2106
2143
  3e4,
2107
2144
  "contenthash"
2108
2145
  );
2146
+ if (result === null) return "0x";
2109
2147
  return typeof result === "string" ? result : result?.toString?.() ?? String(result);
2110
2148
  }
2111
2149
  async classifyName(label) {
@@ -2759,8 +2797,13 @@ export {
2759
2797
  TX_NO_PROGRESS_MS,
2760
2798
  WS_HEARTBEAT_TIMEOUT_MS,
2761
2799
  DOTNS_TX_MAX_ATTEMPTS,
2800
+ WatcherSilentNoEventError,
2762
2801
  VERIFY_EFFECT_CHAIN_SECONDS,
2802
+ NONCE_ADVANCE_VERIFY_RETRIES,
2803
+ NONCE_ADVANCE_VERIFY_RETRY_INTERVAL_MS,
2804
+ verifyEffectWithGrace,
2763
2805
  classifyTxRetryDecision,
2806
+ classifyWatcherSilentFastFail,
2764
2807
  dotnsRetryBackoffMs,
2765
2808
  shouldRetryTxAttempt,
2766
2809
  shouldRegateBeforeResign,
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-TSPERKUS.js";
4
4
  import {
5
5
  VERSION
6
- } from "./chunk-EAB5STKF.js";
6
+ } from "./chunk-NHGLS7HU.js";
7
7
  import {
8
8
  loadEnvironments
9
9
  } from "./chunk-JSYQ3JQS.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  captureWarning
3
- } from "./chunk-EAB5STKF.js";
3
+ } from "./chunk-NHGLS7HU.js";
4
4
 
5
5
  // src/chunk-probe.ts
6
6
  import { Twox128, Blake2128Concat, decAnyMetadata, unifyMetadata } from "@polkadot-api/substrate-bindings";
@@ -166,6 +166,40 @@ async function probeChunks(cids, options) {
166
166
  }
167
167
  return results;
168
168
  }
169
+ function classifyFinalityGap(missingAtFinalized, bestBlockResults) {
170
+ const presentAtBest = new Set(
171
+ bestBlockResults.filter((r) => r.present === true).map((r) => r.cid)
172
+ );
173
+ const lagging = [];
174
+ const reallyMissing = [];
175
+ for (const cid of missingAtFinalized) {
176
+ if (presentAtBest.has(cid)) lagging.push(cid);
177
+ else reallyMissing.push(cid);
178
+ }
179
+ return { reallyMissing, lagging };
180
+ }
181
+ async function probeFinalityGap(missingAtFinalized, options) {
182
+ if (missingAtFinalized.length === 0) return { reallyMissing: [], lagging: [] };
183
+ let bestBlockResults = await probeChunks(missingAtFinalized, { ...options, atFinalized: false });
184
+ const indeterminate = bestBlockResults.filter((r) => r.present === null).map((r) => r.cid);
185
+ if (indeterminate.length > 0) {
186
+ const retry = await probeChunks(indeterminate, { ...options, atFinalized: false });
187
+ const retryByCid = new Map(retry.map((r) => [r.cid, r]));
188
+ bestBlockResults = bestBlockResults.map((r) => r.present === null ? retryByCid.get(r.cid) ?? r : r);
189
+ }
190
+ return classifyFinalityGap(missingAtFinalized, bestBlockResults);
191
+ }
192
+ async function getBestBlockNumber(client) {
193
+ try {
194
+ const header = await client._request("chain_getHeader", []);
195
+ const hex = header?.number;
196
+ if (typeof hex !== "string") return null;
197
+ const n = parseInt(hex, 16);
198
+ return Number.isFinite(n) ? n : null;
199
+ } catch {
200
+ return null;
201
+ }
202
+ }
169
203
  function _resetProbeSession() {
170
204
  _metadataChecked = false;
171
205
  _crossValidated = false;
@@ -180,6 +214,9 @@ export {
180
214
  ChainProbeCrossValidationError,
181
215
  _decodeStorageValue,
182
216
  probeChunks,
217
+ classifyFinalityGap,
218
+ probeFinalityGap,
219
+ getBestBlockNumber,
183
220
  _resetProbeSession,
184
221
  _bypassMetadataCheckForTest
185
222
  };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-EAB5STKF.js";
3
+ } from "./chunk-NHGLS7HU.js";
4
4
 
5
5
  // src/version-check.ts
6
6
  import { execSync, execFileSync } from "child_process";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  package_default,
3
3
  writeRunState
4
- } from "./chunk-6C75PEHQ.js";
4
+ } from "./chunk-ODF44V67.js";
5
5
 
6
6
  // src/memory-report.ts
7
7
  import * as fs2 from "fs";
@@ -241,7 +241,11 @@ var DEPLOY_SEED_STORAGE = {
241
241
  };
242
242
  var DEPLOY_SEED_PROBE = {
243
243
  "deploy.probe.finality_miss_count": 0,
244
- "deploy.probe.finality_miss_reupload_count": 0
244
+ "deploy.probe.finality_miss_reupload_count": 0,
245
+ // #1049: chunks present at best-block but not yet at finalised head.
246
+ // These are NEVER re-uploaded — only tracked so we can see finality lag
247
+ // without conflating it with genuinely-dropped (re-uploaded) chunks.
248
+ "deploy.probe.finality_lagging_count": 0
245
249
  };
246
250
  var DEPLOY_SEED_POOL = {
247
251
  "deploy.pool.eligible_count": 0,
@@ -337,6 +341,12 @@ var ERROR_KIND_RULES = [
337
341
  [/requires ProofOfPersonhood(?:Full|Lite|Light),\s*but this signer is NoStatus/i, "naming.pop_required"],
338
342
  [/requires NoStatus,\s*but this signer is ProofOfPersonhood/i, "naming.nostatus_required"],
339
343
  [/Cannot decode zero data.*with ABI parameters/i, "naming.contract_unavailable"],
344
+ // Issue #1060: contractCall's own empty-`0x`-data guard (src/dotns.ts, #729) throws
345
+ // an actionable wrapper instead of letting the raw viem message above reach a
346
+ // caller. Same failure family (a DotNS contract read came back empty) — classify
347
+ // it the same way instead of letting it fall into 'unknown'.
348
+ [/No contract deployed at .+ returned empty success data/i, "naming.contract_unavailable"],
349
+ [/Contract call returned empty data — contract=/i, "naming.contract_unavailable"],
340
350
  [/Domain\s+\S+\.dot\s+is already owned by\s+0x[a-fA-F0-9]+/i, "naming.already_owned"],
341
351
  [/Cannot deploy\s+[\w.-]+\.dot:\s*parent\s+[\w.-]+\.dot\s+is owned by/i, "naming.subdomain_orphan"],
342
352
  [/Post-deploy verification failed for .+: on-chain contenthash is /i, "verify.contenthash_mismatch"],
@@ -6,7 +6,7 @@ import * as path from "path";
6
6
  // package.json
7
7
  var package_default = {
8
8
  name: "bulletin-deploy",
9
- version: "0.11.0",
9
+ version: "0.12.0-rc.2",
10
10
  private: false,
11
11
  repository: {
12
12
  type: "git",