@parity/product-deploy 0.8.1-rc.1 → 0.8.2-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +2 -230
  2. package/bin/bulletin-deploy +15 -1
  3. package/dist/bug-report.js +4 -4
  4. package/dist/{chunk-67LJSVIF.js → chunk-4Y4ZBN45.js} +1 -1
  5. package/dist/{chunk-KVWIZYYV.js → chunk-5EJ247OO.js} +88 -77
  6. package/dist/{chunk-EGPPGZXB.js → chunk-7GYCJPFI.js} +128 -22
  7. package/dist/{chunk-5QX6RJWD.js → chunk-CSDXTU3G.js} +2 -2
  8. package/dist/{chunk-L2SKSKB6.js → chunk-J3NIXHZZ.js} +108 -0
  9. package/dist/{chunk-RZQQLSF4.js → chunk-JQ5X3VMP.js} +15 -102
  10. package/dist/{chunk-XOQ7IJOQ.js → chunk-N27JUWU2.js} +6 -3
  11. package/dist/{chunk-IT2RZLUT.js → chunk-PIGHAAM2.js} +3 -3
  12. package/dist/{chunk-KBZXNQOT.js → chunk-R2ORPNZC.js} +1 -1
  13. package/dist/{chunk-VCOYLHNE.js → chunk-RRYHCOOJ.js} +75 -33
  14. package/dist/{chunk-CNPB4VAM.js → chunk-XFX4VODU.js} +65 -0
  15. package/dist/chunk-probe.js +3 -3
  16. package/dist/deploy.d.ts +38 -1
  17. package/dist/deploy.js +16 -10
  18. package/dist/dotns.d.ts +33 -2
  19. package/dist/dotns.js +9 -5
  20. package/dist/environments.d.ts +15 -2
  21. package/dist/environments.js +3 -1
  22. package/dist/index.d.ts +1 -1
  23. package/dist/index.js +13 -11
  24. package/dist/manifest/publish.js +11 -11
  25. package/dist/manifest-fetch.d.ts +22 -1
  26. package/dist/manifest-fetch.js +7 -1
  27. package/dist/manifest-roundtrip.js +1 -1
  28. package/dist/memory-report.js +2 -2
  29. package/dist/merkle.js +10 -10
  30. package/dist/personhood/bootstrap.js +5 -5
  31. package/dist/personhood/people-client.js +5 -5
  32. package/dist/pool.d.ts +2 -12
  33. package/dist/pool.js +3 -11
  34. package/dist/run-state.js +1 -1
  35. package/dist/telemetry.js +2 -2
  36. package/dist/version-check.js +3 -3
  37. package/docs/bootstrap.md +1 -1
  38. package/package.json +6 -3
package/README.md CHANGED
@@ -1,233 +1,5 @@
1
1
  # bulletin-deploy
2
2
 
3
- `bulletin-deploy` publishes a static web app to Bulletin and binds it to a human-readable `.dot` domain.
3
+ Early alpha code, stay tuned.
4
4
 
5
- The main CLI is deploy-only. Pool bootstrap and other operator setup live in [`bulletin-bootstrap`](docs/bootstrap.md).
6
-
7
- ## Quick Start
8
-
9
- ```bash
10
- npm install -g bulletin-deploy
11
-
12
- # Build your app first, then deploy it.
13
- bulletin-deploy ./dist my-app00.dot
14
- ```
15
-
16
- On success, the CLI prints the CID and the `.dot` domain that now serves your app.
17
-
18
- ## Installation
19
-
20
- - **Node.js 22+**
21
- - **IPFS Kubo** if you want the default merkleization path
22
-
23
- ```bash
24
- # macOS
25
- brew install ipfs
26
- ipfs init
27
-
28
- # Linux
29
- wget https://dist.ipfs.tech/kubo/v0.33.0/kubo_v0.33.0_linux-amd64.tar.gz
30
- tar -xvzf kubo_v0.33.0_linux-amd64.tar.gz
31
- sudo bash kubo/install.sh
32
- ipfs init
33
- ```
34
-
35
- If you do not want a Kubo dependency, pass `--js-merkle`.
36
-
37
- Stable installs:
38
-
39
- - `npm install -g bulletin-deploy`
40
- - `npm install -g bulletin-deploy@latest`
41
-
42
- Release candidates:
43
-
44
- - `npm install -g bulletin-deploy@rc`
45
- - `npm install -g bulletin-deploy@<exact-version>`
46
-
47
- ## CLI Usage
48
-
49
- ```bash
50
- bulletin-deploy <build-dir> <domain.dot>
51
- ```
52
-
53
- Examples:
54
-
55
- ```bash
56
- # Basic deploy (defaults to --env paseo-next-v2)
57
- bulletin-deploy ./dist my-app00.dot
58
-
59
- # Pick a different environment
60
- bulletin-deploy ./dist my-app00.dot --env paseo-review
61
-
62
- # List supported environments
63
- bulletin-deploy --list-environments
64
-
65
- # Refresh the environments cache before deploying
66
- bulletin-deploy ./dist my-app00.dot --refresh-environments --env paseo-next-v2
67
-
68
- # Direct signer deploy
69
- bulletin-deploy ./dist my-app00.dot --mnemonic "..."
70
-
71
- # Custom Bulletin RPC override (asset-hub still comes from --env)
72
- bulletin-deploy ./dist my-app00.dot --rpc wss://custom-bulletin.example.com
73
- ```
74
-
75
- ### Selecting an environment
76
-
77
- `--env <id>` selects a target environment by id. The list of environments and their RPC endpoints is sourced dynamically from [`paritytech/bulletin-deploy/assets/environments.json`](./assets/environments.json), which mirrors [`paritytech/triangle-status/environments.json`](https://github.com/paritytech/triangle-status/blob/main/environments.json) (the latter is private; bulletin-deploy serves as the public mirror).
78
-
79
- | Env id | Network | Bulletin available? |
80
- |---|---|---|
81
- | `paseo-next-v2` (default) | testnet | yes |
82
- | `paseo-next` | testnet | yes |
83
- | `paseo-review` | testnet | yes |
84
- | `previewnet` | testnet | yes |
85
- | `polkadot` | mainnet | not yet |
86
- | `kusama` | mainnet | not yet |
87
-
88
- A single env id drives both the bulletin RPC and the asset-hub RPC used internally for DotNS, so they cannot drift. When you pass `--rpc`, it overrides only the bulletin endpoint within the chosen env; the asset-hub endpoint still comes from `--env`.
89
-
90
- The runtime uses a 24-hour cache at `${XDG_CACHE_HOME:-~/.cache}/bulletin-deploy/environments.json`; `--refresh-environments` busts it and re-fetches. The npm tarball ships a bundled snapshot that is used when the live URL is unreachable.
91
-
92
- ### Options
93
-
94
- | Flag | What it does |
95
- |---|---|
96
- | `--env <id>` | Target environment. Default: `paseo-next-v2`. See `--list-environments` for valid ids. |
97
- | `--list-environments` | Print the environments table and exit. |
98
- | `--refresh-environments` | Bust the cache and re-fetch environments.json. Composes with `--env` (refresh-then-deploy) or runs solo. |
99
- | `--rpc wss://...` | Override the Bulletin RPC endpoint within the chosen `--env`. Also readable from `BULLETIN_RPC`. |
100
- | `--mnemonic "..."` | Use a specific mnemonic as the direct signer for Bulletin uploads and DotNS updates. Also readable from `MNEMONIC`. |
101
- | `--derivation-path "..."` | Apply a Substrate derivation path to `--mnemonic`, for example `//deploy/3`. |
102
- | `--pool-size N` | Change the number of derived pool accounts available for pool-mode Bulletin uploads. Default: `10`. |
103
- | `--password "..."` | Encrypt SPA content before upload. Consumers must provide the password to decrypt it. |
104
- | `--js-merkle` | Use pure-JS merkleization instead of the Kubo binary. |
105
- | `--tag "..."` | Attach a free-form telemetry label. Also readable from `DEPLOY_TAG`. |
106
- | `--gh-pages-mirror` | After a successful deploy, push the generated CAR to the current repo's `gh-pages` branch as an HTTP mirror. |
107
- | `--input-car <path>` | Deploy from a pre-built CAR file instead of a build directory. Skips merkleization; reads the root CID from the CAR header. Usage: `bulletin-deploy --input-car site.car my-app.dot` |
108
- | `--version` | Print the CLI version. |
109
- | `--help` | Show help. |
110
-
111
- ## Concepts
112
-
113
- - `Bulletin`: the chain that stores the app payload in chunked transaction storage.
114
- - `.dot domain`: the DotNS name that points at the deployed content.
115
- - `CAR`: the content-addressed archive produced from your build output before upload.
116
- - `merkleization`: turning a directory into a content-addressed DAG and CAR file.
117
- - `pool accounts`: derived Bulletin uploader accounts used to spread nonce and authorization load.
118
- - `PoP`: Proof of Personhood, which some `.dot` names require before registration.
119
-
120
- ## Incremental Upload
121
-
122
- After the first deploy of a `.dot` domain, every subsequent deploy automatically reuses chunks already stored on Bulletin instead of re-uploading them. There is no flag to enable; it just runs.
123
-
124
- How it works:
125
-
126
- 1. The previous deploy embeds a manifest at `.bulletin-deploy/manifest.json` inside the deployed content (file classification, block ordering, chunk metadata).
127
- 2. The new deploy fetches the previous contenthash from DotNS, then fetches that manifest via the Bulletin IPFS gateway.
128
- 3. The new build's CAR is sliced into chunks; each chunk's CID is HEAD-probed against the gateway. Chunks already present are skipped.
129
- 4. Only the chunks that actually changed (typically: the manifest itself and any modified content) are uploaded.
130
-
131
- The summary line at the end of a deploy shows the savings:
132
-
133
- ```
134
- Cache:
135
- Manifest: embedded (1 attempt)
136
- Probed: 18 chunks → 15 cached, 2 to upload, 1 probe-failed
137
- Recycled: 3 CIDs found on-chain that weren't in the previous manifest
138
- Saved: ~52s and 14.3 MB upload
139
- ```
140
-
141
- CI runners benefit identically — no `actions/cache` wiring required, because the manifest travels with the deployed content rather than living on the runner's disk.
142
-
143
- **Encrypted deploys** (`--password`) bypass the incremental path. Encryption produces non-deterministic CAR bytes per run, so chunk-level dedup can't apply.
144
-
145
- **Force a full re-upload** by deleting `.bulletin-deploy/` from your build output (or changing one byte in any non-volatile file) before deploying. The classifier treats `.bulletin-deploy/` paths as volatile, so removing the manifest forces the next deploy to fall back to the heuristic classifier.
146
-
147
- ## Domain Rules
148
-
149
- DotNS classifies labels on-chain and may require a specific Proof of Personhood level before registration.
150
-
151
- Typical cases:
152
-
153
- | Domain pattern | Typical classification |
154
- |---|---|
155
- | Base name, for example `my-app.dot` | `ProofOfPersonhoodFull` |
156
- | Name with trailing digits, for example `my-app00.dot` | often `NoStatus`, but not guaranteed |
157
-
158
- Do not rely on the string shape alone. The on-chain classifier is authoritative.
159
-
160
- On testnets, `bulletin-deploy` self-grants PoP only when the classifier says it is needed. On mainnet, PoP cannot be self-granted.
161
-
162
- ## GitHub Pages Mirror
163
-
164
- `--gh-pages-mirror` is an opt-in cache path for hosts that want an HTTP fetch path in addition to Bulletin.
165
-
166
- ```bash
167
- bulletin-deploy ./dist my-app.dot --gh-pages-mirror
168
- ```
169
-
170
- After a successful deploy, the CLI pushes:
171
-
172
- - `bulletin/<domain>.dot.car`
173
- - `bulletin/<domain>.dot.json`
174
-
175
- to the current repo's `gh-pages` branch and prints the Pages URL.
176
-
177
- Use it when you want to validate or consume the mirror feature. The source of truth remains Bulletin plus DotNS.
178
-
179
- ## Programmatic API
180
-
181
- ```js
182
- import { deploy } from "bulletin-deploy";
183
-
184
- const result = await deploy("./dist", "my-app00.dot");
185
- console.log(result.cid, result.domainName);
186
- ```
187
-
188
- For environments without Kubo:
189
-
190
- ```js
191
- await deploy("./dist", "my-app00.dot", { jsMerkle: true });
192
- ```
193
-
194
- ## Environment Variables
195
-
196
- | Variable | Default | Description |
197
- |---|---|---|
198
- | `BULLETIN_RPC` | `wss://paseo-bulletin-rpc.polkadot.io` | Override the Bulletin chain WebSocket RPC for the chosen `--env`. |
199
- | `BULLETIN_ENVIRONMENTS_URL` | bulletin-deploy public mirror | Override the runtime URL for environments.json. Internal teams point this at a fork or local proxy. |
200
- | `BULLETIN_DEPLOY_TELEMETRY` | off for external users, on for internal users | `1` to opt in, `0` to force off |
201
- | `BULLETIN_DEPLOY_UPDATE_CHECK` | `1` | Set to `0` to disable version checks on failure |
202
- | `IPFS_CID` | unset | Skip storage and reuse an existing CID |
203
- | `DEPLOY_TAG` | unset | Telemetry label equivalent to `--tag` |
204
- | `BULLETIN_DEPLOY_HOST_APP` | unset | Name of the host app embedding bulletin-deploy (e.g. `playground-cli`). Sets `deploy.host_app` on telemetry spans. |
205
- | `BULLETIN_DEPLOY_HOST_APP_VERSION` | unset | Version of the host app. Sets `deploy.host_app_version` on telemetry spans when `BULLETIN_DEPLOY_HOST_APP` is also set. |
206
-
207
- ## Troubleshooting
208
-
209
- | Error | What to check |
210
- |---|---|
211
- | `Requires Full Personhood verification` | The chosen label needs a higher PoP level. |
212
- | `Domain ... is owned by a different account` | The `.dot` name is already owned by the account indicated. Use that account as parameter or transfer the domain from that account to the new account you want to use |
213
- | `Account ... is not authorized for Bulletin storage` | The uploader account is not authorized on Bulletin yet. For operator-managed pools, see [`bulletin-bootstrap`](docs/bootstrap.md). |
214
- | `fetchNonce timed out` or connection errors | The Bulletin RPC may be unhealthy. Try another endpoint. |
215
- | `IPFS CLI not installed` | Install Kubo or switch to `--js-merkle`. |
216
- | Previous deploy did not exit cleanly / OOM hint | Retry with a larger Node heap, for example `NODE_OPTIONS='--max-old-space-size=8192'`. |
217
-
218
- ## Contributing
219
-
220
- New to the codebase? Start with **[ONBOARDING.md](ONBOARDING.md)** — it covers install, the mental model, the first task, and the working conventions for this repo (worktree-per-branch, squash-merge policy, never-delete-tests, where the per-directory rules live).
221
-
222
- The team uses Claude Code as a primary tool. The repo ships team-shared Claude configuration: `.claude/skills/` (project-specific commands like `/e2e-local`, `/dotns-diagnose`, `/sentry-query`), `.claude/settings.json` (Bash allowlist), and per-directory `CLAUDE.md` files in `src/`, `sentry/`, `test/`, `tools/` that load on demand. Running `claude` inside this repo picks all of that up automatically.
223
-
224
- Already have Claude Code installed? Clone, `npm install`, open `ONBOARDING.md`. New to Claude Code itself? The same doc covers install.
225
-
226
- For maintainers and engineers familiar with the release flow, the canonical procedures live in the root [`CLAUDE.md`](CLAUDE.md): change workflow, dual-stage RC → stable release, post-release Sentry monitoring, and the squash-merge convention that satisfies branch protection without per-commit GPG signing.
227
-
228
- ## More Docs
229
-
230
- - [Bootstrap and operator setup](docs/bootstrap.md)
231
- - [Testing](docs/testing.md)
232
- - [Telemetry](docs/telemetry.md)
233
- - [E2E one-time setup](docs/e2e-bootstrap.md)
5
+ Full documentation and contributing guide: [docs-internal/README.dev.md](docs-internal/README.dev.md)
@@ -42,6 +42,7 @@ for (let i = 0; i < args.length; i++) {
42
42
  }
43
43
  (flags.contracts ??= {})[kv.slice(0, eq)] = kv.slice(eq + 1);
44
44
  }
45
+ else if (args[i] === "--environment-file") { flags.environmentFile = args[++i]; }
45
46
  else if (args[i] === "--list-environments") { flags.listEnvironments = true; }
46
47
  else if (args[i] === "--password") { flags.password = args[++i]; }
47
48
  else if (args[i] === "--js-merkle") { flags.jsMerkle = true; }
@@ -67,6 +68,12 @@ if (flags.publish && flags.unpublish) {
67
68
  process.exit(1);
68
69
  }
69
70
 
71
+ // --environment-file: propagate to the env var so all internal loadEnvironments()
72
+ // call sites (deploy.ts, manifest/publish.ts, personhood) honor the override.
73
+ if (flags.environmentFile) {
74
+ process.env.BULLETIN_DEPLOY_ENV_FILE = flags.environmentFile;
75
+ }
76
+
70
77
  if (flags.version) {
71
78
  console.log(`bulletin-deploy v${VERSION}`);
72
79
  process.exit(0);
@@ -81,7 +88,7 @@ if (flags.removedBootstrap) {
81
88
  // nothing else (deploy positional args are ignored).
82
89
  if (flags.listEnvironments) {
83
90
  try {
84
- const { doc } = await loadEnvironments();
91
+ const { doc } = await loadEnvironments({ userFilePath: flags.environmentFile ?? process.env.BULLETIN_DEPLOY_ENV_FILE });
85
92
  console.log(formatEnvironmentTable(listEnvironments(doc)));
86
93
  process.exit(0);
87
94
  } catch (e) {
@@ -169,6 +176,13 @@ Options:
169
176
  --env <id> Target environment from environments.json (default: paseo-next-v2).
170
177
  Drives both the bulletin RPC and the asset-hub RPC used
171
178
  by DotNS. See --list-environments for valid ids.
179
+ --environment-file <path>
180
+ Path to a JSON file deep-merged over the bundled
181
+ environments.json. Override only the fields you need
182
+ (e.g. contract addresses after a chain reset); unspecified
183
+ fields fall through to the bundled values. Also honored
184
+ via BULLETIN_DEPLOY_ENV_FILE env var.
185
+ Warning: values are not validated against chain.
172
186
  --list-environments Print the environments table and exit.
173
187
  --contract <KEY>=<addr> Supply/override a DotNS contract address (repeatable).
174
188
  Merged over the chosen --env's contracts map; required
@@ -9,10 +9,10 @@ import {
9
9
  offerBugReport,
10
10
  scrubSecrets,
11
11
  setDeployContext
12
- } from "./chunk-5QX6RJWD.js";
13
- import "./chunk-KBZXNQOT.js";
14
- import "./chunk-KVWIZYYV.js";
15
- import "./chunk-XOQ7IJOQ.js";
12
+ } from "./chunk-CSDXTU3G.js";
13
+ import "./chunk-R2ORPNZC.js";
14
+ import "./chunk-5EJ247OO.js";
15
+ import "./chunk-N27JUWU2.js";
16
16
  export {
17
17
  buildCliFlagsSummary,
18
18
  buildLabels,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  captureWarning
3
- } from "./chunk-KVWIZYYV.js";
3
+ } from "./chunk-5EJ247OO.js";
4
4
 
5
5
  // src/chunk-probe.ts
6
6
  import { Twox128, Blake2128Concat, decAnyMetadata, unifyMetadata } from "@polkadot-api/substrate-bindings";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  package_default,
3
3
  writeRunState
4
- } from "./chunk-XOQ7IJOQ.js";
4
+ } from "./chunk-N27JUWU2.js";
5
5
 
6
6
  // src/memory-report.ts
7
7
  import * as fs2 from "fs";
@@ -185,94 +185,104 @@ function resolveRunnerType() {
185
185
  if (process.env.RUNNER_NAME?.startsWith("parity-")) return "self-hosted";
186
186
  return "github-hosted";
187
187
  }
188
+ var DEPLOY_SEED_OUTCOME = {
189
+ "deploy.sad": "false",
190
+ "deploy.expected": "false"
191
+ };
192
+ var DEPLOY_SEED_RPC = {
193
+ "deploy.rpc.failed_over": "false"
194
+ };
195
+ var DEPLOY_SEED_DOTNS = {
196
+ // Preflight balance gate. Seeded "false" so successful spans form the denominator
197
+ // for "% hitting the floor" and "% recovered via testnet auto-top-up". Flipped by gateOnFeeBalance.
198
+ "deploy.dotns.signer_below_floor": "false",
199
+ "deploy.dotns.toppedup": "false",
200
+ // Seeded "hash" so spans for non-DotNS deploys group cleanly in the "hash" bucket.
201
+ "deploy.dotns.tx_resolution_kind": "hash",
202
+ // Backend identity (module constants — stable across calls).
203
+ "deploy.dotns_backend": DOTNS_BACKEND,
204
+ "deploy.dotns_pop_source": DOTNS_POP_SOURCE
205
+ };
206
+ var DEPLOY_SEED_CONTENT = {
207
+ // Flipped by deploy.ts storage phase when content is encrypted.
208
+ "deploy.encrypted": "false",
209
+ // Flipped by deploy.ts after parseDomainName resolves isSubdomain.
210
+ "deploy.subdomain": "false",
211
+ // Flipped by deploy.ts after readPreviousContenthashSafe when a prior CID is found.
212
+ "deploy.incremental": "false"
213
+ };
214
+ var DEPLOY_SEED_STORAGE = {
215
+ // Seeded "false"; flipped by storeDirectoryV2 when Phase A root node is already on-chain.
216
+ "deploy.storage.phase_a.root_already_onchain": "false",
217
+ // Seeded 0; incremented per Phase B chunk confirmed on-chain (probe hit → skip re-upload).
218
+ "deploy.storage.phase_b.probe_hit_count": 0,
219
+ "deploy.phase_a.chunks_uploaded": 0,
220
+ // Manifest-aware Phase A trust: count of section-1 CIDs trusted from prev manifest.
221
+ "deploy.phase_a.chunks_trusted": 0
222
+ };
223
+ var DEPLOY_SEED_PROBE = {
224
+ "deploy.probe.finality_miss_count": 0,
225
+ "deploy.probe.finality_miss_reupload_count": 0
226
+ };
227
+ var DEPLOY_SEED_POOL = {
228
+ "deploy.pool.eligible_count": 0,
229
+ // Nonce-advance collision probe counters. Seeded 0 so every span carries them.
230
+ "deploy.pool.nonce_collision_count": 0,
231
+ "deploy.pool.nonce_collision_missing": 0,
232
+ "deploy.pool.nonce_collision_reupload_count": 0
233
+ };
234
+ var DEPLOY_SEED_MANIFEST = {
235
+ "deploy.manifest.fetch_source": "none",
236
+ "deploy.manifest.fetch_attempts": "0",
237
+ "deploy.manifest.bytes_downloaded": "0"
238
+ };
239
+ var DEPLOY_SEED_BULLETIN_UPLOAD = {
240
+ "bulletin.upload.tx_hash": "",
241
+ "bulletin.upload.block_hash": "",
242
+ "bulletin.upload.block_number": ""
243
+ };
244
+ var DEPLOY_SEED_RECEIPTS = {
245
+ "deploy.contenthash.tx": "",
246
+ "deploy.contenthash.block": "",
247
+ "deploy.contenthash.block_hash": "",
248
+ "deploy.register.tx": "",
249
+ "deploy.register.block": "",
250
+ "deploy.register.block_hash": "",
251
+ "deploy.subnode.tx": "",
252
+ "deploy.subnode.block": "",
253
+ "deploy.subnode.block_hash": ""
254
+ };
255
+ var DEPLOY_SEED_P2P = {
256
+ "deploy.p2p.retrievable": "false",
257
+ "deploy.p2p.check_ms": "0",
258
+ "deploy.p2p.error_variant": "none"
259
+ };
188
260
  function getDeployAttributes(domain) {
189
261
  const hostApp = process.env.BULLETIN_DEPLOY_HOST_APP;
190
262
  const attrs = {
263
+ ...DEPLOY_SEED_OUTCOME,
264
+ ...DEPLOY_SEED_RPC,
265
+ ...DEPLOY_SEED_DOTNS,
266
+ ...DEPLOY_SEED_CONTENT,
267
+ ...DEPLOY_SEED_STORAGE,
268
+ ...DEPLOY_SEED_PROBE,
269
+ ...DEPLOY_SEED_POOL,
270
+ ...DEPLOY_SEED_MANIFEST,
271
+ ...DEPLOY_SEED_BULLETIN_UPLOAD,
272
+ ...DEPLOY_SEED_RECEIPTS,
273
+ ...DEPLOY_SEED_P2P,
274
+ // Computed at call time (depend on domain arg, env vars, or external process calls):
191
275
  "deploy.repo": sanitizeRepo(resolveRepo(domain)),
192
276
  "deploy.branch": sanitizeBranch(process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME || tryGitBranch()),
193
277
  "deploy.source": process.env.CI ? "ci" : "local",
194
278
  "deploy.pr": process.env.GITHUB_PR_NUMBER || void 0,
195
279
  "deploy.tool_version": VERSION,
196
280
  "deploy.runner": resolveRunner(),
197
- "deploy.runner_type": resolveRunnerType(),
198
- // Seed "false" so successful spans form the %SAD denominator; the catch block and
199
- // captureWarning flip it to "true" on friction.
200
- "deploy.sad": "false",
201
- // Same ratio-denominator reasoning as deploy.sad above, but for the
202
- // %EXPECTED-refusal metric: catch block flips to "true" when the error
203
- // matches isExpectedError (user-facing product rule, not tool friction).
204
- "deploy.expected": "false",
205
- // Seed "false" so every span carries the attribute (boolean-both-values rule).
206
- // Flipped to "true" by getWsProvider's onStatusChanged when papi connects to a
207
- // non-primary endpoint, and flushed again in deploy()'s finally block.
208
- "deploy.rpc.failed_over": "false",
209
- // DotNS preflight balance gate. Seeded "false" so successful spans form the
210
- // denominator for "% of deploys hitting the floor" and "% recovered via
211
- // testnet auto-top-up" metrics. Flipped by gateOnFeeBalance.
212
- "deploy.dotns.signer_below_floor": "false",
213
- "deploy.dotns.toppedup": "false",
214
- // Seeded "false" so every span carries the attribute for ratio queries.
215
- // Flipped to "true" by pool.ts ensureAuthorized / topUpBy when authorize_account
216
- // actually submits (i.e. re-auth was needed, not short-circuited).
217
- "deploy.unblock.bulletin_auth.fired": "false",
218
- // Seeded "hash" so spans missing an explicit set (non-DotNS deploys) group
219
- // cleanly in the "hash" bucket.
220
- "deploy.dotns.tx_resolution_kind": "hash",
221
- // Seeded "false" so every span carries the attribute for ratio queries.
222
- // Flipped by deploy.ts storage phase when content is encrypted.
223
- "deploy.encrypted": "false",
224
- // Flipped by deploy.ts after parseDomainName resolves isSubdomain.
225
- "deploy.subdomain": "false",
226
- // Flipped by deploy.ts after readPreviousContenthashSafe when a prior CID is found.
227
- "deploy.incremental": "false",
228
- // Seeded "false" so every span carries the attribute for ratio queries.
229
- // Flipped to "true" by storeDirectoryV2 when the Phase A root node is already on-chain.
230
- "deploy.storage.phase_a.root_already_onchain": "false",
231
- // Seeded 0 so every span carries the attribute; incremented by storeDirectoryV2
232
- // for each Phase B chunk confirmed present on-chain (probe hit → skip re-upload).
233
- "deploy.storage.phase_b.probe_hit_count": 0,
234
- "deploy.phase_a.chunks_uploaded": 0,
235
- "deploy.probe.finality_miss_count": 0,
236
- "deploy.probe.finality_miss_reupload_count": 0,
237
- "deploy.pool.eligible_count": 0,
238
- // Nonce-advance collision probe counters. Seeded to 0 so every span carries
239
- // the attribute for ratio queries even when no collision is detected.
240
- "deploy.pool.nonce_collision_count": 0,
241
- "deploy.pool.nonce_collision_missing": 0,
242
- "deploy.pool.nonce_collision_reupload_count": 0,
243
- // Manifest-aware Phase A trust: count of section-1 CIDs trusted from prev manifest.
244
- "deploy.phase_a.chunks_trusted": 0,
245
- // Manifest fetch outcome. Seeded so every span carries the attributes even when
246
- // fetchPreviousManifest is never reached (first deploy, early error, non-incremental
247
- // path). "none" + "0" form the denominator for ratio queries:
248
- // count_if(deploy.manifest.fetch_source, "heuristic_fallback") / count().
249
- // Both string-valued per @sentry/node EAP numeric-attribute caveat.
250
- "deploy.manifest.fetch_source": "none",
251
- "deploy.manifest.fetch_attempts": "0",
252
- "deploy.manifest.bytes_downloaded": "0",
253
- // Bulletin storage upload chain receipt (root-node tx, or last chunk when root skipped).
254
- // Empty-string default so every span carries the attribute for filter queries.
255
- "bulletin.upload.tx_hash": "",
256
- "bulletin.upload.block_hash": "",
257
- "bulletin.upload.block_number": 0,
258
- // DotNS setContenthash chain receipt.
259
- "deploy.contenthash.tx": "",
260
- "deploy.contenthash.block": 0,
261
- "deploy.contenthash.block_hash": "",
262
- // DotNS register chain receipt (fresh registrations only).
263
- "deploy.register.tx": "",
264
- "deploy.register.block": 0,
265
- "deploy.register.block_hash": "",
266
- // DotNS setSubnodeOwner chain receipt (subdomain registrations only).
267
- "deploy.subnode.tx": "",
268
- "deploy.subnode.block": 0,
269
- "deploy.subnode.block_hash": ""
281
+ "deploy.runner_type": resolveRunnerType()
270
282
  };
271
283
  if (hostApp) attrs["deploy.host_app"] = hostApp;
272
284
  const hostAppVersion = process.env.BULLETIN_DEPLOY_HOST_APP_VERSION;
273
285
  if (hostAppVersion) attrs["deploy.host_app_version"] = hostAppVersion;
274
- attrs["deploy.dotns_backend"] = DOTNS_BACKEND;
275
- attrs["deploy.dotns_pop_source"] = DOTNS_POP_SOURCE;
276
286
  return attrs;
277
287
  }
278
288
  function isExpectedError(msg) {
@@ -280,7 +290,7 @@ function isExpectedError(msg) {
280
290
  }
281
291
  function classifyDeployError(msg) {
282
292
  if (isExpectedError(msg)) return "user";
283
- if (/chunk.*failed after.*retr|tx dropped from best chain|timed out after \d+s waiting for block|Contract reverted|Contract execution would revert|dotns register failed|All promises were rejected|"type"\s*:\s*"Invalid"|Commitment still too new|not finalised after \d+s|chain may have (dropped|evicted)|ReviveApi.*timed out|\b(?:commit|register|setSubnodeOwner|setResolver|setContenthash|setText|publish|unpublish|Revive\.call|Utility\.batch_all) timed out after \d+ms|transaction watcher silent for/i.test(msg)) return "environment";
293
+ if (/chunk.*failed after.*retr|tx dropped from best chain|timed out after \d+s waiting for block|Contract reverted|Contract execution would revert|dotns register failed|All promises were rejected|"type"\s*:\s*"Invalid"|Commitment still too new|not finalised after \d+s|chain may have (dropped|evicted)|ReviveApi.*timed out|ReviveApi.*returned empty result|\b(?:commit|register|setSubnodeOwner|setResolver|setContenthash|setText|publish|unpublish|Revive\.call|Utility\.batch_all) timed out after \d+ms|transaction watcher silent for/i.test(msg)) return "environment";
284
294
  if (/javascript heap out of memory|allocation failed.*heap|External signer mode is not supported with dotns-cli/i.test(msg)) return "internal";
285
295
  return "unknown";
286
296
  }
@@ -312,6 +322,7 @@ var ERROR_KIND_RULES = [
312
322
  [/Deploy verification failed:\s*DAG-PB root.+not finalised/i, "verify.dagpb_not_finalised"],
313
323
  [/Retry budget exhausted:.*recovery attempts/i, "network.recovery_exhausted"],
314
324
  [/ReviveApi\.\w+ timed out after \d+ms/i, "chain.api_timeout"],
325
+ [/ReviveApi\.\w+ returned empty result/i, "chain.api_timeout"],
315
326
  [/transaction watcher silent for \d+s/i, "chain.tx_silent"],
316
327
  [/^(?:commit|register|setSubnodeOwner|setResolver|setContenthash|setText|publish|unpublish|Revive\.call|Utility\.batch_all) timed out after \d+ms/i, "chain.tx_timeout"],
317
328
  [/^INVARIANT FAILED:/i, "tool.invariant"]