@slicedearth/whoisleuth-cli 2.2.0 → 2.4.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 (282) hide show
  1. package/NOTICE +6 -0
  2. package/README.md +20 -15
  3. package/bin/whoisleuth.mjs +8 -1
  4. package/cli/archive-content-digest.mjs +14 -0
  5. package/cli/archive-inspect.mjs +20 -53
  6. package/cli/arguments.mjs +454 -1748
  7. package/cli/artifact-validation/case-response.mjs +66 -35
  8. package/cli/artifact-validation/investigation-capsule.mjs +52 -31
  9. package/cli/artifact-validation/lookup-evidence.mjs +12 -6
  10. package/cli/artifact-validation/signed-review.mjs +112 -72
  11. package/cli/artifact-validation/structure-primitives.mjs +1 -105
  12. package/cli/artifact-verify.mjs +90 -24
  13. package/cli/assurance-command-runner.mjs +39 -4
  14. package/cli/bagit-folder.mjs +57 -0
  15. package/cli/bagit-review.mjs +23 -0
  16. package/cli/bounded-json.mjs +1 -1
  17. package/cli/bulk-checkpoint.mjs +34 -3
  18. package/cli/bulk-command-runner.mjs +2 -0
  19. package/cli/bulk-output.mjs +29 -3
  20. package/cli/case-command.mjs +195 -0
  21. package/cli/case-pack.mjs +66 -111
  22. package/cli/command-argument-grammar.mjs +215 -0
  23. package/cli/command-reference.mjs +342 -285
  24. package/cli/completion.mjs +303 -80
  25. package/cli/config-profile.mjs +7 -5
  26. package/cli/ct-event-intake.mjs +12 -12
  27. package/cli/discover.mjs +4 -3
  28. package/cli/discovery-observation-snapshot.mjs +59 -18
  29. package/cli/discovery-scan-command-runner.mjs +21 -0
  30. package/cli/discovery-scan.mjs +13 -4
  31. package/cli/discovery-workflow.mjs +1 -1
  32. package/cli/domain-control-monitor.mjs +50 -20
  33. package/cli/domain-control-observations.mjs +149 -102
  34. package/cli/errors.mjs +33 -1
  35. package/cli/evidence-command-runner.mjs +16 -5
  36. package/cli/evidence-signing.mjs +2 -2
  37. package/cli/external-observation-mapping.mjs +7 -8
  38. package/cli/formatters/terminal-lookup.mjs +18 -46
  39. package/cli/formatters/terminal-shared.mjs +5 -3
  40. package/cli/formatters/terminal-technology.mjs +114 -0
  41. package/cli/inline-command-families.mjs +2 -0
  42. package/cli/input.mjs +116 -0
  43. package/cli/interactive-launcher.mjs +2 -160
  44. package/cli/interchange-report.mjs +18 -8
  45. package/cli/investigation-artifacts.mjs +176 -0
  46. package/cli/investigation-checkpoint.mjs +10 -0
  47. package/cli/investigation-folder.mjs +114 -0
  48. package/cli/investigation-manifest.mjs +1 -112
  49. package/cli/investigation-package-review.mjs +103 -0
  50. package/cli/investigation-plan.mjs +32 -19
  51. package/cli/investigation-run.mjs +188 -33
  52. package/cli/local-document-checkpoint.mjs +170 -0
  53. package/cli/lookalike-calibration.mjs +7 -2
  54. package/cli/lookup-brief.mjs +4 -2
  55. package/cli/lookup-browser-view.mjs +1 -1
  56. package/cli/lookup-browser.mjs +5 -12
  57. package/cli/lookup-command-runner.mjs +17 -6
  58. package/cli/lookup-diff.mjs +37 -3
  59. package/cli/lookup-plan.mjs +11 -5
  60. package/cli/lookup-reconcile.mjs +10 -8
  61. package/cli/mail-header-review.mjs +467 -0
  62. package/cli/mail-review.mjs +66 -14
  63. package/cli/manual.mjs +1 -0
  64. package/cli/network-command-runner.mjs +2 -0
  65. package/cli/output-file.mjs +31 -5
  66. package/cli/registry-doctor.mjs +3 -1
  67. package/cli/registry-support.mjs +1 -1
  68. package/cli/retained-artifact-diff.mjs +2 -2
  69. package/cli/review-command-runner.mjs +70 -0
  70. package/cli/risk-calibration.mjs +19 -17
  71. package/cli/runner.mjs +62 -7
  72. package/cli/sharing-review.mjs +21 -34
  73. package/cli/signer-trust.mjs +104 -0
  74. package/cli/source-reliability.mjs +74 -83
  75. package/cli/terminal-input.mjs +168 -0
  76. package/cli/workflow-command-runner.mjs +68 -13
  77. package/docs/cli-reference.md +77 -24
  78. package/docs/cli.md +300 -25
  79. package/lib/abort.mjs +4 -1
  80. package/lib/availability.mjs +161 -112
  81. package/lib/bounded-file.mjs +3 -1
  82. package/lib/bounded-html-document.mjs +125 -0
  83. package/lib/bounded-json.mjs +51 -8
  84. package/lib/browser-library-profile.mjs +42 -51
  85. package/lib/browser-library-worker.mjs +112 -0
  86. package/lib/client-behavior-profile.mjs +3 -3
  87. package/lib/ct-search.mjs +11 -4
  88. package/lib/dns-convergence-review.mjs +1 -0
  89. package/lib/dns-delegation-health.mjs +24 -14
  90. package/lib/dns-inheritance-review.mjs +217 -0
  91. package/lib/dns-intelligence.mjs +73 -26
  92. package/lib/dns-mx.mjs +1 -1
  93. package/lib/dnssec-chain-validation.mjs +6 -0
  94. package/lib/dnssec-evidence-validation.mjs +11 -3
  95. package/lib/domain-change-packet.mjs +49 -16
  96. package/lib/domain-change-review.mjs +14 -8
  97. package/lib/domain-control-flight-recorder.mjs +221 -117
  98. package/lib/domain-control-manifest.mjs +129 -69
  99. package/lib/domain-posture-analysis.mjs +20 -7
  100. package/lib/domain-posture-parsers.mjs +201 -8
  101. package/lib/domain-posture.mjs +147 -29
  102. package/lib/evidence-export-privacy.mjs +561 -0
  103. package/lib/evidence-export.mjs +11 -543
  104. package/lib/evidence-report.mjs +5 -3
  105. package/lib/favicon.mjs +117 -41
  106. package/lib/generated/cisa-kev-catalog.mjs +12 -4
  107. package/lib/generated/registrar-standing-catalogue.mjs +7 -7
  108. package/lib/generated/retire-browser-catalog.mjs +2335 -940
  109. package/lib/hostname.mjs +2 -38
  110. package/lib/html-signals.mjs +111 -108
  111. package/lib/http-evidence-bounds.mjs +2 -1
  112. package/lib/http-intelligence.mjs +39 -26
  113. package/lib/local-geoip-evidence.mjs +19 -3
  114. package/lib/lookup-cache.mjs +34 -11
  115. package/lib/lookup-child-profile-contract.mjs +24 -15
  116. package/lib/lookup-diagnostics.mjs +24 -1
  117. package/lib/lookup-progress.mjs +30 -13
  118. package/lib/lookup-response-contract.mjs +1048 -0
  119. package/lib/lookup-response.mjs +8 -10
  120. package/lib/lookup-source-progress.mjs +40 -14
  121. package/lib/lookup.mjs +48 -27
  122. package/lib/nameserver-preflight-review.mjs +1 -0
  123. package/lib/observed-network-context.mjs +43 -27
  124. package/lib/outbound-request-bounds.mjs +4 -1
  125. package/lib/page-fingerprints.mjs +69 -133
  126. package/lib/page-role-profile.mjs +12 -8
  127. package/lib/perceptual-hash.mjs +43 -14
  128. package/lib/rdap-bootstrap.mjs +72 -22
  129. package/lib/rdap-capabilities.mjs +1 -1
  130. package/lib/rdap-client.mjs +15 -7
  131. package/lib/rdap-normalization.mjs +33 -18
  132. package/lib/rdap-search-workbench.mjs +1 -1
  133. package/lib/rdap-transport.mjs +47 -20
  134. package/lib/rdap-types.mjs +2 -1
  135. package/lib/rdap.mjs +18 -3
  136. package/lib/registrable-domain.mjs +21 -1
  137. package/lib/registrar-standing-contract.mjs +1 -0
  138. package/lib/registry-capabilities.mjs +5 -1
  139. package/lib/registry-capability-catalogue.mjs +2 -1
  140. package/lib/registry-dates.mjs +2 -190
  141. package/lib/registry-insights.mjs +85 -34
  142. package/lib/response-policy.mjs +86 -48
  143. package/lib/risk-calibration-summary.mjs +18 -6
  144. package/lib/rpki-evidence.mjs +8 -5
  145. package/lib/safe-fetch.mjs +54 -47
  146. package/lib/security-txt.mjs +42 -7
  147. package/lib/service-binding-dns.mjs +8 -0
  148. package/lib/smtp-transport-review.mjs +27 -15
  149. package/lib/sslbl-certificates.generated.mjs +136 -135
  150. package/lib/static-html-analysis.mjs +178 -151
  151. package/lib/structured-data-identity.mjs +1 -0
  152. package/lib/technology-markup.mjs +100 -0
  153. package/lib/threat-intelligence-manifests.mjs +11 -139
  154. package/lib/threat-intelligence-runtime.mjs +24 -52
  155. package/lib/threat-intelligence-targets.mjs +6 -34
  156. package/lib/threat-intelligence-types.mjs +39 -0
  157. package/lib/typosquat-generator.mjs +19 -15
  158. package/lib/urlhaus-intelligence.mjs +8 -3
  159. package/lib/urlscan-intelligence.mjs +1 -1
  160. package/lib/website-security-posture.mjs +30 -6
  161. package/lib/website-technology.mjs +153 -45
  162. package/lib/whois-authority.mjs +14 -2
  163. package/lib/whois-chain.mjs +9 -2
  164. package/lib/whois-common-formats.mjs +5 -5
  165. package/lib/whois-contracts.mjs +2 -1
  166. package/lib/whois-dialects.mjs +2 -2
  167. package/lib/whois-parser-support.mjs +8 -1
  168. package/lib/whois-transport.mjs +23 -7
  169. package/lib/zone-intent-review.mjs +16 -6
  170. package/package.json +2 -1
  171. package/packages/cases/case-attachment-model.mjs +83 -0
  172. package/packages/cases/case-evidence-model.mjs +156 -62
  173. package/packages/cases/case-export-input.mjs +53 -0
  174. package/packages/cases/case-migration-model.mjs +127 -44
  175. package/packages/cases/case-model.mjs +1 -0
  176. package/packages/cases/case-recheck-model.mjs +91 -0
  177. package/packages/cases/case-record-contracts.mjs +5 -20
  178. package/packages/cases/case-record-core.mjs +12 -22
  179. package/packages/cases/case-record-decisions.mjs +72 -0
  180. package/packages/cases/case-record-model.mjs +1 -0
  181. package/packages/cases/case-record-operations.mjs +95 -31
  182. package/packages/cases/case-record-projection.mjs +221 -0
  183. package/packages/cases/case-report.mjs +70 -54
  184. package/packages/cases/case-response-actions.mjs +654 -0
  185. package/packages/cases/case-response-model.mjs +64 -1115
  186. package/packages/cases/case-response-outcomes.mjs +317 -0
  187. package/packages/cases/case-response-packet-render.mjs +153 -0
  188. package/packages/cases/case-response-packet-types.mjs +1 -0
  189. package/packages/cases/case-response-packet-vocabulary.mjs +48 -0
  190. package/packages/cases/case-response-packet.mjs +74 -572
  191. package/packages/cases/case-response-records.mjs +94 -0
  192. package/packages/cases/case-response-review-inputs.mjs +371 -0
  193. package/packages/cases/case-response-values.mjs +85 -0
  194. package/packages/cases/case-review-package.mjs +45 -0
  195. package/packages/cases/case-selection.mjs +46 -0
  196. package/packages/cases/case-storage-model.mjs +31 -2
  197. package/packages/cases/response-route-freshness.mjs +16 -0
  198. package/packages/comparison/capture-context.mjs +61 -0
  199. package/packages/comparison/comparison-ledger-bulk.mjs +13 -11
  200. package/packages/comparison/page-similarity.mjs +5 -2
  201. package/packages/comparison/relationship-evidence.mjs +85 -8
  202. package/packages/comparison/relationship-provenance.mjs +93 -0
  203. package/packages/contracts/analyst-interchange.mjs +216 -0
  204. package/packages/contracts/analyst-review-state-contract.mjs +11 -0
  205. package/packages/contracts/analyst-review-state.mjs +5 -4
  206. package/packages/contracts/case-portability.mjs +366 -83
  207. package/packages/contracts/case-views-contract.mjs +12 -0
  208. package/packages/contracts/case-views.mjs +26 -0
  209. package/packages/contracts/cli-command-semantics.mjs +94 -0
  210. package/packages/contracts/cli-lookup.mjs +0 -1
  211. package/packages/contracts/domain-control-flight-recorder.mjs +80 -42
  212. package/packages/contracts/domain-control-manifest.mjs +127 -73
  213. package/packages/contracts/domain-control-monitor.mjs +48 -45
  214. package/packages/contracts/domain-control-review.mjs +179 -121
  215. package/packages/contracts/domain-name.mjs +21 -0
  216. package/packages/contracts/external-observation-interchange.mjs +2 -1
  217. package/packages/contracts/extracted-domain-lifecycle.mjs +11 -32
  218. package/packages/contracts/http-url.mjs +2 -0
  219. package/packages/contracts/investigation-package-limits.mjs +24 -0
  220. package/packages/contracts/investigation-portability.mjs +56 -27
  221. package/packages/contracts/investigation-run.mjs +16 -0
  222. package/packages/contracts/ip-address.mjs +34 -0
  223. package/packages/contracts/lookup-evidence.mjs +35 -0
  224. package/packages/contracts/offline-comparison.mjs +1 -2
  225. package/packages/contracts/page-fingerprints.mjs +15 -0
  226. package/packages/contracts/risk-calibration.mjs +15 -11
  227. package/packages/contracts/schema-compatibility.mjs +1 -1
  228. package/packages/contracts/schema-lifecycle.mjs +83 -153
  229. package/packages/contracts/selected-file-limits.mjs +6 -0
  230. package/packages/contracts/vulnerability-identifiers.mjs +5 -0
  231. package/packages/contracts/web-capture.mjs +10 -0
  232. package/packages/contracts/workspace-portability.mjs +183 -211
  233. package/packages/evidence/artifact-integrity.mjs +8 -2
  234. package/packages/evidence/artifact-structure.mjs +105 -0
  235. package/packages/evidence/decision-fact.mjs +4 -1
  236. package/packages/evidence/domain-control-runtime.mjs +201 -33
  237. package/packages/evidence/domain-posture-context.mjs +79 -0
  238. package/packages/evidence/image-regions.mjs +56 -0
  239. package/packages/evidence/latest-observations.mjs +28 -0
  240. package/packages/evidence/lookup-target.mjs +104 -0
  241. package/packages/evidence/observation.mjs +16 -1
  242. package/packages/evidence/passphrase-encryption.mjs +44 -0
  243. package/packages/evidence/record-identity.mjs +72 -0
  244. package/packages/evidence/registry-dates.mjs +190 -0
  245. package/packages/evidence/retained-file.mjs +69 -0
  246. package/packages/evidence/review-age.mjs +14 -0
  247. package/packages/interchange/bagit.mjs +342 -0
  248. package/packages/interchange/bounded-zip-extraction.mjs +278 -0
  249. package/packages/interchange/crc32.mjs +17 -0
  250. package/packages/interchange/external-findings-import.mjs +92 -51
  251. package/packages/interchange/web-capture-import.mjs +382 -0
  252. package/packages/investigation/bulk-mail-exposure.mjs +2 -1
  253. package/packages/investigation/investigation-bagit.mjs +14 -0
  254. package/packages/investigation/investigation-capsule.mjs +33 -5
  255. package/packages/investigation/investigation-manifest.mjs +200 -0
  256. package/packages/investigation/investigation-package-crypto.mjs +98 -0
  257. package/packages/investigation/investigation-package.mjs +200 -0
  258. package/packages/investigation/lookup-asset-graph-inputs.mjs +38 -0
  259. package/packages/investigation/lookup-asset-graph.mjs +193 -165
  260. package/packages/investigation/lookup-claim-readiness.mjs +18 -0
  261. package/packages/investigation/lookup-investigation-brief.mjs +3 -3
  262. package/packages/investigation/lookup-task-guidance.mjs +58 -0
  263. package/packages/monitoring/analyst-review-state.mjs +38 -90
  264. package/packages/workspace/brand-profile-model.mjs +276 -22
  265. package/packages/workspace/bulk-columns.mjs +17 -0
  266. package/packages/workspace/bulk-review-model.mjs +23 -11
  267. package/packages/workspace/bulk-session-model.mjs +100 -24
  268. package/packages/workspace/bulk-sort.mjs +1 -0
  269. package/packages/workspace/case-views.mjs +102 -0
  270. package/packages/workspace/detection-rule-model.mjs +70 -22
  271. package/packages/workspace/hostile-input.mjs +29 -8
  272. package/packages/workspace/investigation-template-model.mjs +19 -4
  273. package/packages/workspace/page-baseline.mjs +18 -15
  274. package/packages/workspace/relationship-observation-model.mjs +41 -23
  275. package/packages/workspace/watchlist-history.mjs +2 -2
  276. package/packages/workspace/watchlist-store.mjs +8 -2
  277. package/packages/workspace/website-snapshot-model.mjs +164 -26
  278. package/packages/workspace/workspace-archive-crypto.mjs +41 -61
  279. package/packages/workspace/workspace-archive.mjs +87 -21
  280. package/third-party-notices.txt +29 -2
  281. package/cli/evidence-command-arguments.mjs +0 -156
  282. package/packages/investigation/bulk-review-export.mjs +0 -63
@@ -12,20 +12,8 @@ contains the exhaustive recipient, retention and export metadata.
12
12
 
13
13
  ## Installation
14
14
 
15
- Public releases require Node.js 24 or later. Release verification uses the
16
- exact Node.js 24 maintainer runtime and separately exercises the installed
17
- package on Node.js 26:
18
-
19
- ```bash
20
- npm exec --yes --ignore-scripts --package=@slicedearth/whoisleuth-cli -- whoisleuth --help
21
- npm install --global --ignore-scripts @slicedearth/whoisleuth-cli
22
- whoisleuth doctor
23
- ```
24
-
25
- The scoped package and application share one semantic version. The package
26
- requires no dependency lifecycle scripts and does not call the hosted
27
- WHOISleuth deployment. From a repository checkout, replace `whoisleuth` with
28
- `node bin/whoisleuth.mts`.
15
+ Use the [installation guide](cli.md#installation) for runtime requirements,
16
+ installation and updates. The CLI runs locally without a hosted account.
29
17
 
30
18
  ## Find and inspect commands
31
19
 
@@ -46,6 +34,13 @@ command position as Lookup shorthand. Use explicit `lookup` where input needs
46
34
  supported URL-like normalisation. Credentials, paths, queries, fragments, ports
47
35
  and unsupported special-use targets are rejected by the shorthand.
48
36
 
37
+ Explicit `lookup` normally uses only a pasted URL's hostname. Add
38
+ `--deep --exact-url` to collect that page's path and query instead of the
39
+ homepage; fragments are not sent. `--plan` discloses this scope without making
40
+ requests or copying the URL into the plan. Use stdin to keep a sensitive URL
41
+ out of shell history. Saved request provenance omits queries, but paths and
42
+ page-derived text still require review before sharing.
43
+
49
44
  `registry-scaffold` has a separate fixture contract: its `--profile` selects one
50
45
  fixed fixture profile and shared `--config` profiles are rejected. It produces
51
46
  sanitised local fixture material and makes no registry request.
@@ -63,6 +58,9 @@ disclosures without collecting. Fast is the Lookup default. Deep adds the
63
58
  applicable registration, DNS, HTTP, TLS, page, technology and network context.
64
59
  Optional browser providers are not implicit CLI actions.
65
60
 
61
+ The [message-header review](cli.md#message-header-review) is offline and reports
62
+ publisher claims, not independent DNS or cryptographic validation.
63
+
66
64
  `dnssec-validate` and `mail-transport` are isolated authorised actions. Both
67
65
  require a selected literal public resolver, a local trust-anchor document and
68
66
  `--owned-or-authorized`; mail transport also requires `--active-probe`. Mail
@@ -70,6 +68,8 @@ transport handles at most three selected MX hosts sequentially, sends `EHLO`
70
68
  and uses `STARTTLS` only when advertised. It does not send mail, authenticate,
71
69
  test relay, enumerate recipients or retry automatically. DNSSEC, TLSA/DANE,
72
70
  PKIX, STARTTLS and SMTP transport remain separate evidence states.
71
+ STARTTLS detection uses the full bounded reply. If the retained capability
72
+ inventory is shortened, the report remains partial and names the omission.
73
73
 
74
74
  The repository-only rendered-capture package is outside hosted and distributable
75
75
  collection. It executes remote page JavaScript only for an explicitly
@@ -84,11 +84,20 @@ refused. Output goes to the terminal unless a command supports `--output`.
84
84
  Private output is written atomically and an existing path is refused unless
85
85
  replacement is explicit.
86
86
 
87
+ `--` ends option processing. All later arguments are literal positional inputs,
88
+ including names such as `--help`. Use `./-evidence.json` for a hyphen-prefixed
89
+ filename when completing paths across shells. Options must precede `--`.
90
+
87
91
  Terminal text is the default. JSON, JSONL, CSV, Markdown, HTML and domain-only
88
92
  formats are available only where the installed command declares them.
89
93
  Redirected and machine output contains no ANSI or progress text. Diagnostics
90
94
  and optional target-free `--events` output use standard error.
91
95
 
96
+ For `bulk` and `discover-scan`, `--csv-with-metadata` adds the source schema and
97
+ version, observation and report times, collection origin, scan mode, diagnostic
98
+ version and source-health states. Missing clocks remain `unknown`; a null
99
+ source state is unmeasured, not a negative result. `--csv` keeps the compact columns.
100
+
92
101
  `--fail-on` and `--strict-exit` expose selected evidence states to automation
93
102
  without changing the result document. Review focused help for the policies a
94
103
  command supports.
@@ -109,16 +118,18 @@ Readers accept only their declared public and current versions. Unknown,
109
118
  unreleased historical and future schemas fail before partial interpretation;
110
119
  an invalid import is not treated as an empty document.
111
120
 
112
- `inspect-archive` reads current workspace archive v7 and exact versions 5 and 6. It
121
+ `inspect-archive` reads current workspace archive v9 and exact versions 5–8. It
113
122
  reports section metadata and digest-only search results unless `--reveal` is
114
123
  explicitly selected. It never searches notes, contacts or arbitrary raw fields.
115
124
 
116
125
  `export` reads supported saved Lookup v1 or v2 and writes current Lookup
117
- evidence schema 28. Published v2 schema 27 and exact v1 schema 26 remain
118
- readable. Versions 27 and 28 exclude raw registration payloads, expanded
126
+ evidence schema 29. Published v2 schemas 27 and 28 and exact v1 schema 26 remain
127
+ readable. Versions 27–29 exclude raw registration payloads, expanded
119
128
  contacts, credentials, complete query-bearing URLs and provider payloads;
120
- schema 28 can also retain the bounded, separately attributed registrar-standing
121
- projection shown by Lookup.
129
+ schemas 28 and 29 also retain the bounded registrar-standing projection.
130
+ Schema 29 identifies the hostname used for DNS, TLS and web observations,
131
+ separately from the registrable domain used by registration sources. Older
132
+ evidence retains its original collection scope.
122
133
 
123
134
  `verify-artifact` checks a recognised structure and its applicable integrity
124
135
  contract. `interchange-report` describes retained and omitted fields.
@@ -126,6 +137,45 @@ contract. `interchange-report` describes retained and omitted fields.
126
137
  validity and signer trust separate. None of these checks establishes that
127
138
  evidence is accurate, current, safe to share or attributable to a person.
128
139
 
140
+ ### Signer trust
141
+
142
+ Use `verify-signature package.json --trust-store-file trust.json --json` to
143
+ check an explicitly selected fingerprint policy offline. The result contains
144
+ the ordinary signature verification and a separate current trust decision.
145
+ Only a matching `trusted` entry succeeds; unknown, `retired`, `revoked` or
146
+ future-reviewed entries return exit code 4, including with `--quiet`.
147
+ Malformed files return 2. Without this option, the existing verification
148
+ output is unchanged. A supplied `--public-key-file` must also match.
149
+
150
+ The trust file contains no keys. For example:
151
+
152
+ ```json
153
+ {
154
+ "schema": "whoisleuth.evidence-signer-trust-store",
155
+ "version": 1,
156
+ "entries": [{
157
+ "keyIdSha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
158
+ "label": "Evidence reviewer",
159
+ "status": "trusted",
160
+ "updatedAt": "2026-09-01T00:00:00.000Z",
161
+ "note": "Fingerprint confirmed through the established contact channel."
162
+ }]
163
+ }
164
+ ```
165
+
166
+ Replace the example fingerprint with the SHA-256 fingerprint of the signer's
167
+ SPKI DER public-key bytes, confirmed through an authenticated channel—not
168
+ merely copied from the package being checked. Review rotation and revocation
169
+ updates through that channel. On rotation, mark the old entry `retired` and
170
+ optionally record `successorKeyIdSha256`; independently confirm and add the
171
+ replacement entry. Use `revoked` for a withdrawn trust decision. Neither a
172
+ successor link nor a claimed signing date overrides current status.
173
+
174
+ Files support 1,024 distinct entries within 4 MiB. Labels and single-line notes
175
+ allow 160 and 2,048 characters respectively. Reports include only the matching
176
+ entry and the exact file digest. Keep the file and any saved reports under your
177
+ own retention policy. No automatic key discovery, key storage or trust refresh occurs.
178
+
129
179
  ## Command-family boundaries
130
180
 
131
181
  - Evidence collection commands retain separate source states. A failed or
@@ -137,17 +187,20 @@ evidence is accurate, current, safe to share or attributable to a person.
137
187
  - Bulk applies one declared collection contract per target. Fast accepts up to
138
188
  500 targets and Deep up to 50; each target remains a separate request.
139
189
  - Respond commands package browser-created Cases and prepare local packets,
140
- reports and sharing reviews. They do not create durable Cases, submit,
141
- publish, notify or grant recipient authorisation.
190
+ reports and sharing reviews. The offline [`case` workflow](cli.md#local-case-files)
191
+ also creates and updates ordinary local Case files through explicit, conflict-checked
192
+ output. Neither workflow submits, publishes, notifies or grants recipient authorisation.
142
193
  - Assurance, comparison and calibration commands describe supplied or retained
143
194
  evidence. They do not tune the running model, change infrastructure or turn an
144
195
  analyst label into observed truth.
145
196
  - `workflow-plan` lists fixed installed recipes without executing them.
146
197
  `workflow-run` executes only installed steps, requires approval for network
147
198
  work and pauses at unresolved analyst selections. Repeat
148
- `--select <step-id>=<path-or-value>` in placeholder order to resume a selected
149
- step; version-1 checkpoints remain readable and version 2 retains the exact
150
- selections in the local checkpoint.
199
+ `--select <step-id>=<path-or-value>` for remaining placeholders in order, or bind
200
+ a compatible earlier output with `--use-artifact <step-id>:<input-number>=<earlier-step-id>`.
201
+ Checkpoints retain exact selections and bindings and distinguish incomplete
202
+ collection from retryable failures; see [resuming a fixed workflow](cli.md#resuming-a-fixed-workflow)
203
+ for the supported versions and resume behaviour.
151
204
 
152
205
  Use the installed focused help for positional inputs, exact ceilings, options,
153
206
  network effects, outputs and command-specific exit behaviour.
package/docs/cli.md CHANGED
@@ -35,6 +35,10 @@ dependency lifecycle scripts. From a repository checkout, replace `whoisleuth`
35
35
  with `node bin/whoisleuth.mts`; maintainers can verify the exact package closure
36
36
  with `npm run cli:package:check`.
37
37
 
38
+ Rendered-page collection uses the separate [optional capture companion](https://github.com/slicedearth/whoisleuth/tree/main/packages/web-capture#install-a-local-candidate),
39
+ not the main CLI. It can be installed from a verified local archive; browser
40
+ installation and each authorised capture remain explicit actions.
41
+
38
42
  ## First commands
39
43
 
40
44
  Inspect the installed command set and plan one Lookup before collecting:
@@ -56,6 +60,11 @@ whoisleuth lookup example.test --deep --browse
56
60
  whoisleuth lookup example.test --deep --markdown --output lookup.md
57
61
  ```
58
62
 
63
+ Pasted URLs normally select only their hostname. To collect one particular
64
+ page, use `whoisleuth lookup 'https://portal.example.test/review' --deep --exact-url`.
65
+ This sends the path and query to the website, without the fragment. Review
66
+ retained paths and page-derived text before sharing the result.
67
+
59
68
  To continue a reviewed terminal Lookup in the browser, save the completed
60
69
  private document from the interactive view:
61
70
 
@@ -65,16 +74,21 @@ whoisleuth lookup example.test --deep --browse --save-lookup lookup.json
65
74
 
66
75
  In Console Lookup, open **Replay exported evidence**, select the file, verify
67
76
  its digest and source states, then create or update a browser-local Case. The
68
- file is not uploaded. Case classification, exact incident links, response
69
- actions and packet preparation remain deliberate browser steps.
77
+ file is not uploaded. Case classification, exact incident links and response
78
+ actions remain deliberate analyst steps. Ordinary Case files also support the
79
+ offline CLI workflow below.
80
+ When several incident Cases share a domain, select the intended Case before
81
+ retaining replay evidence. Case packs preserve each current Case ID separately;
82
+ trusted and public packs exclude analyst-entered incident titles.
70
83
 
71
84
  Process selected local input:
72
85
 
73
86
  ```bash
74
- whoisleuth bulk domains.txt --csv
87
+ whoisleuth bulk domains.txt --csv-with-metadata
75
88
  whoisleuth discover example.test --preset common --jsonl
76
89
  whoisleuth verify-artifact lookup.json --json --strict-exit
77
90
  whoisleuth compare lookup.json --json
91
+ whoisleuth mail-headers message.eml --json
78
92
  whoisleuth brief lookup.json
79
93
  ```
80
94
 
@@ -82,17 +96,15 @@ One strict domain, IP address or ASN can occupy command position as Lookup
82
96
  shorthand. URL-like or ambiguous input requires the explicit `lookup` command.
83
97
  Only `bulk` accepts multiple targets.
84
98
 
85
- ## Command groups
99
+ `discover` accepts a brand label or registrable domain and supports multi-part
100
+ public suffixes in `--tlds` (for example, `co.uk,com.au`). It preserves the full
101
+ suffix and does not collect candidate evidence; use `discover-scan` only after
102
+ reviewing its separate network plan. Subdomains and URLs are not generation seeds.
86
103
 
87
- The installed registry groups commands under the same analyst vocabulary as the
88
- application:
104
+ ## Command groups
89
105
 
90
- | Group | Common commands |
91
- | --- | --- |
92
- | Investigate | `lookup`, `bulk`, `discover`, `ct-search`, `posture`, `http`, `tls`, `compare`, `brief` |
93
- | Respond | `case-pack`, `change-packet`, `sharing-review`, `export` (local handoff from browser-created Cases) |
94
- | Assure | `dnssec-validate`, `mail-transport`, `domain-control`, `assurance`, `workflow-plan`, `diff`, `inspect-archive`, `verify-artifact` |
95
- | Utilities | `doctor`, `commands`, `completion`, `manual`, `registry-scaffold` |
106
+ The installed catalogue groups commands under Investigate, Respond, Assure and
107
+ Utilities, with each command's inputs, output and network boundary.
96
108
 
97
109
  Filter the canonical index without running the selected commands:
98
110
 
@@ -103,12 +115,14 @@ whoisleuth commands --json
103
115
  whoisleuth manual | man -l -
104
116
  ```
105
117
 
106
- `registry-scaffold` has a separate fixture bootstrap contract: its `--profile`
107
- selects one fixed fixture profile and it rejects shared `--config` profiles.
108
- It creates sanitised local fixtures and does not contact a registry.
109
-
110
118
  ## Collection boundaries
111
119
 
120
+ For an explicit inherited-policy and direct parent-delegation review, use
121
+ `whoisleuth posture example.test --include-inherited-dns --json`. Without that
122
+ option, posture collection keeps its existing exact-name scope. The extra
123
+ results distinguish recursive policy publication from sampled parent referrals;
124
+ they do not establish name existence, message authentication or DNSSEC validity.
125
+
112
126
  | Mode | Behaviour |
113
127
  | --- | --- |
114
128
  | Fast Lookup | Registration-first triage without WHOIS or deeper website and TLS collection. |
@@ -122,13 +136,265 @@ their focused help. They do not use the hosted login or hosted usage controls.
122
136
  `lookup --plan` lists planned source families and disclosure targets before
123
137
  collection. `doctor` is offline unless `--network` is selected.
124
138
 
139
+ ### Message-header review
140
+
141
+ `mail-headers` parses only the bounded header block from a selected message file
142
+ or standard input. It extracts domain-only identity, reported SPF, DKIM, DMARC
143
+ and ARC states, exact-domain alignment, and the bounded `Received` route in its
144
+ reported order. It makes no request and does not retain address local parts,
145
+ display names, subject, body, attachments, or raw header values in its output.
146
+ Authentication states are header claims, not an independent DNS or
147
+ cryptographic validation, and alignment differences can be legitimate.
148
+
125
149
  ## Output and automation
126
150
 
151
+ ### Local Case files
152
+
153
+ Create a working file, inspect its Case IDs, then append a note:
154
+
155
+ ```sh
156
+ whoisleuth case open --domain example.test --title "Review the selected form" --output cases.json
157
+ whoisleuth case show cases.json
158
+ whoisleuth case note cases.json --note-file note.txt --output cases.json --force
159
+ ```
160
+
161
+ Use `--case-id` when the file contains several Cases. `open` reuses the selected
162
+ Case; `--new-incident --title "Another incident"` creates a distinct ID for the
163
+ same domain. It does not open a browser or collect anything.
164
+
165
+ `pin`, `assess` and `recheck` read a selected JSON file with `--input`:
166
+
167
+ ```sh
168
+ whoisleuth case pin cases.json --input pin.json --output cases.json --force
169
+ whoisleuth case assess cases.json --input assessment.json --output cases.json --force
170
+ whoisleuth case recheck cases.json --input recheck.json --output cases.json --force
171
+ ```
172
+
173
+ A pin describes the supplied observation, not a new collection. For example:
174
+
175
+ ```json
176
+ {
177
+ "label": "Selected page observation",
178
+ "value": "A form was retained in the supplied capture.",
179
+ "source": "Analyst supplied capture",
180
+ "observedAt": "2026-09-01T12:00:00.000Z",
181
+ "completeness": "complete",
182
+ "sourceState": "complete",
183
+ "observationHostname": "example.test"
184
+ }
185
+ ```
186
+
187
+ Supply an observation time only when known; use `null` otherwise. Describe
188
+ partial or unavailable evidence honestly. IDs and creation times are allocated
189
+ locally. Unknown fields, truncated values and unsupported formats are rejected.
190
+
191
+ An assessment includes a reviewed disposition and reason, a summary, a rationale,
192
+ and evidence relationships. Replace `selected-pin-id` with an ID from `case show`:
193
+
194
+ ```json
195
+ {
196
+ "disposition": "suspicious",
197
+ "reviewReasonCode": "other_reviewed",
198
+ "summary": "Review the apparent credential request",
199
+ "rationale": "The supplied observation needs independent corroboration.",
200
+ "evidence": [{ "pinId": "selected-pin-id", "stance": "supports" }]
201
+ }
202
+ ```
203
+
204
+ Each fact selects one retained `pinId` or a new `pin` object. Stances are
205
+ `supports`, `contradicts` or `unresolved`; at least one must support the decision.
206
+ Counterevidence and unresolved evidence remain linked, not discarded. Assessment
207
+ summary and rationale use single-line text; note files can contain paragraphs.
208
+
209
+ A recheck input contains `state`, `observedAt`, `completeness`, `source` and
210
+ `comparisonSummary`, with optional limitations and follow-up time. For example,
211
+ use `state: "unavailable"` and `completeness: "partial"` when a supplied capture
212
+ did not complete. `not_reproduced` requires a saved recheck question from the
213
+ Case's console workflow, its exact `recheck` context, the target
214
+ `observationHostname`, a complete later observation where a baseline exists,
215
+ and `conditionsMatch: "comparable"`. It never means global removal or takedown.
216
+
217
+ Mutations require an explicit output path and write the complete current Case
218
+ export. They never prune old evidence to fit. Input formatting may use up to
219
+ 16 MiB; the canonical store remains limited to 4 MiB and 500 Cases. `show --json`
220
+ can export a selected subset to a **different** path. Ordinary Case imports also
221
+ allow 16 MiB for formatting and export metadata; the stored-data limit remains
222
+ 4 MiB. Case packs retain their separate 25-Case selection and 4-MiB source-file
223
+ limits, and include generated reports within the bounded import allowance.
224
+
225
+ `show` reports the exact file digest. Add `--expect-file-digest sha256:<digest>`
226
+ to reject a file changed since that review. Source/output locks and atomic
227
+ replacement also detect changes during execution. After an interrupted process,
228
+ inspect its adjacent `.workflow.lock` before deliberately removing it. Validation
229
+ or conflict failures leave the Case file unchanged and return 2. Filesystem
230
+ publication failures return 3.
231
+
232
+ Working files contain private analyst content and file references, not attachment
233
+ bytes. Use `case-pack` for a reviewed audience projection and a separate evidence
234
+ package for selected original files. Nothing is uploaded or reported automatically.
235
+
236
+ ### Portable evidence files
237
+
238
+ Package selected files without changing their bytes, then verify the ZIP offline:
239
+
240
+ ```sh
241
+ whoisleuth manifest evidence.json screenshot.png --workflow "Evidence review" \
242
+ --package --output evidence.zip
243
+ whoisleuth verify-artifact evidence.zip --package --json --strict-exit
244
+ whoisleuth manifest evidence.json screenshot.png --workflow "Evidence review" \
245
+ --folder ./evidence-project
246
+ whoisleuth verify-artifact --folder ./evidence-project --json --strict-exit
247
+ ```
248
+
249
+ Packages admit the 128-file, 64-MiB original selection plus one bounded Case
250
+ export, with a separate manifest allowance. Original-file limits remain unchanged.
251
+ `verify-artifact` uses the same exact ordinary Case reader as `case show`,
252
+ without repairing content. Package verification also reports original-reference
253
+ completeness for each Case file; `--strict-exit` returns 4 when a referenced original is missing.
254
+ An ordinary Case JSON file alone provides structural validity, not a checksum.
255
+ They use generated entry names, not original paths. Ordinary ZIPs and folders
256
+ are private and unencrypted; packaging does not redact selected files. The report distinguishes
257
+ file identity, supported source formats, opaque content, exact capsule/source
258
+ links and capture-manifest attachment matches. Include the capture manifest and
259
+ its screenshot/DOM-digest files together to check their declared bytes; original
260
+ filenames are not needed to establish a match. It does not import files or establish source truth, signature trust or a
261
+ trusted timestamp. Unsupported or rejected entries produce a partial report;
262
+ `--strict-exit` returns 4. Without `--package` or `--folder`, `manifest` produces a
263
+ standalone JSON manifest; exact public version-2 manifests remain readable.
264
+
265
+ For [BagIt 1.0](https://www.rfc-editor.org/rfc/rfc8493.html) interchange, use
266
+ `--bagit` with `--package` or `--folder` on both commands:
267
+
268
+ ```sh
269
+ whoisleuth manifest evidence.json screenshot.png --workflow "Evidence review" \
270
+ --bagit --package --output bag.zip
271
+ whoisleuth verify-artifact bag.zip --bagit --package --json --strict-exit
272
+ whoisleuth manifest evidence.json --workflow "Evidence review" --bagit --folder ./bag
273
+ whoisleuth verify-artifact --bagit --folder ./bag --json --strict-exit
274
+ ```
275
+
276
+ BagIt output contains unchanged files under `data/`, SHA-512 payload and tag
277
+ manifests, and the existing source-declaration manifest as a tag file. It is
278
+ unencrypted and cannot be combined with `--passphrase-file`. Verification also
279
+ accepts SHA-256. It checks every manifest, reports absent files and unsupported
280
+ algorithms, and never downloads `fetch.txt` locations. A valid result establishes
281
+ declared byte integrity, not source-format validity, authenticity or factual
282
+ accuracy. Unchecked tags remain explicit; reported entry names are generated.
283
+
284
+ The bounded reader accepts UTF-8 tags, safe relative paths up to 1,024 UTF-8
285
+ bytes and 16 levels, and the same payload byte/file allowances as evidence
286
+ packages. Tags have a separate allowance of 32 files, 512 KiB each and 2 MiB
287
+ combined. Symbolic links, special files, path collisions and unsupported ZIP
288
+ features are refused. BagIt itself does not impose these implementation limits.
289
+
290
+ For an encrypted ordinary package, supply a local passphrase file:
291
+
292
+ ```sh
293
+ whoisleuth manifest evidence.json screenshot.png --workflow "Evidence review" \
294
+ --package --passphrase-file ./package-passphrase.txt --output evidence.wlep
295
+ whoisleuth verify-artifact evidence.wlep --package \
296
+ --passphrase-file ./package-passphrase.txt --json --strict-exit
297
+ ```
298
+
299
+ The file contains one UTF-8 line of at least 12 characters, at most 1,024 bytes.
300
+ Keep it private and separate from the package. Neither command uploads it or
301
+ includes it in its report. Version-1 encrypted packages protect the entire ZIP,
302
+ including its manifest. Unlocking authenticates the container before validating
303
+ file identities. The report's package digest identifies the decrypted ZIP;
304
+ its input byte count identifies the encrypted file. Wrong passphrases, corrupted
305
+ containers and unsupported versions exit 3 without a verification report.
306
+ Encryption does not establish authorship, factual accuracy or redaction, and
307
+ does not apply to folder exports or separately downloaded JSON backups.
308
+
309
+ Folders contain `manifest.json` and generated `artifacts/artifact-N` files.
310
+ Creation requires a new destination and never replaces an existing folder.
311
+ Failed writes can leave partial private output; inspect or remove it before
312
+ choosing another destination. Verification rejects symbolic links, unexpected
313
+ trees and unlisted files. Its package digest and byte count describe the
314
+ canonical stored-ZIP representation, not filesystem metadata. These are
315
+ selected evidence exports, not complete workspace backups.
316
+
317
+ ### Resuming a fixed workflow
318
+
319
+ All recipes listed by `workflow-plan --list` can run through `workflow-run`.
320
+ Planning remains offline. Execution emits a checkpoint; terminal output shows
321
+ retained steps, output identities, missing inputs and the next required action.
322
+ A partial collection
323
+ pauses for review; resuming keeps that observation and does not collect it
324
+ again. Later steps can finish without making the earlier evidence complete:
325
+ the run still exits with code 4. Validation, usage and export failures remain
326
+ failures and are retried on resume. Step diagnostics stay on stderr, separate
327
+ from checkpoint JSON. New network steps still need `--approve-network`.
328
+
329
+ New runs connect compatible earlier outputs for domain triage, registry review,
330
+ historical comparison and handoff linting. For a complete domain-triage hand-off:
331
+
332
+ ```sh
333
+ whoisleuth workflow-run domain-triage example.test --approve-network \
334
+ --json --output run.json
335
+ ```
336
+
337
+ Bindings accept Lookup outputs for export, diff, timeline, comparison, source
338
+ reports and briefs; evidence exports for verification; and Case-pack output
339
+ for sharing review. Remaining placeholders use `--select` in order;
340
+ for example, `--select diff=previous.json` keeps the current observation as the
341
+ second input. Supply every input for an uncompleted step to use files instead,
342
+ or override a connection with `--use-artifact <step-id>:<input-number>=<earlier-step-id>`.
343
+ Input numbers start at 1 and refer to the fixed recipe's placeholders.
344
+ `diff` compares saved observations of the same or different domains; `timeline`
345
+ orders observations of one domain. Candidate and domain-control intent inputs
346
+ remain analyst selections. No external file is inferred or extracted automatically.
347
+ `reconcile` lists each observation time and declared observer/vantage, keeping
348
+ disagreement and unavailable evidence separate. Distinct labels do not verify
349
+ independent collection; timing and shared caches can affect the comparison.
350
+
351
+ For example, registry review can reuse its collection without extracting files:
352
+
353
+ ```sh
354
+ whoisleuth workflow-run registry-disagreement example.test --approve-network \
355
+ --json --output registry-run.json
356
+ ```
357
+
358
+ The evidence-handoff recipe pauses before any human-review declaration. Review
359
+ the selected material and the listed declarations, then use
360
+ `--confirm-review package` or `--confirm-review lint` for that particular step.
361
+ Confirmation applies only to the current invocation and is not inferred from
362
+ the checkpoint. A completed recipe means its commands ran, not that an
363
+ investigation is resolved or sharing is authorised.
364
+
365
+ Optional `--interactive` asks for missing paths or values on terminal stderr,
366
+ keeping checkpoint JSON on stdout or in the selected output file. A blank answer
367
+ pauses. Prompts do not grant network approval or confirm review. Redirected input
368
+ and unattended runs use `--select` instead. Resuming a checkpoint preserves its
369
+ recorded connections; new defaults are not applied to old checkpoints.
370
+
371
+ Checkpoint version 3 reads versions 1 and 2. Older installations reject version
372
+ 3. Checkpoints retain exact output schemas, content digests and input bindings;
373
+ changed identities or completed inputs are rejected. Digests identify content,
374
+ not source authenticity, truth or freshness. The checkpoint can contain
375
+ selected local paths and evidence; review it before sharing.
376
+
377
+ Use `--json --output state.json` for a resumable file, and add `--force` when
378
+ replacing it. File output holds private adjacent `.workflow.lock` files for
379
+ the selected resume and output paths until publication. Another writer is
380
+ refused; changes to either file during execution prevent replacement. State
381
+ files must be regular files without symbolic or additional hard links.
382
+
383
+ Locks contain only a local process ID and are removed after the run. Following
384
+ an interrupted process, confirm that its owner has stopped before removing its
385
+ abandoned lock. Locks have no automatic expiry. Shell redirection and external
386
+ editors do not participate in this cooperative file-ownership protocol.
387
+
388
+ ### Formats and exit behaviour
389
+
127
390
  Terminal text is the default. Commands expose JSON, JSONL, CSV, Markdown, HTML
128
391
  or domain-only output only where declared by the installed registry. Redirected
129
392
  and machine output contains no ANSI or transient progress text.
130
393
 
131
394
  - `--summary` and `--verbose` change presentation, not collection.
395
+ Verbose Lookup includes retained technology signals and library advisory
396
+ identifiers. Their source times are separate; ages are measured at output
397
+ generation, not at a later replay.
132
398
  - `lookup --browse` provides an interactive terminal view; press `?` for keys.
133
399
  - `--events` writes versioned lifecycle events to stderr.
134
400
  - `--strict-exit` and `--fail-on` expose selected evidence states to automation.
@@ -140,17 +406,26 @@ concurrency and observer labels. They cannot add targets, enable Deep
140
406
  collection, choose output paths or approve network work. Explicit command
141
407
  options override profile defaults.
142
408
 
409
+ `source-report` summarises collector-owned health envelopes in saved Lookup
410
+ and Bulk output. Raw publications and extension fields are not interpreted as
411
+ diagnostics, and missing health metadata remains unmeasured.
412
+
413
+ `mail-review` keeps failed input targets, incomplete DNS coverage and original
414
+ collection times visible. Source-output and review-generation times do not
415
+ stand in for missing observation times. `domain-change` and
416
+ `domain-change-packet` retain authority, resolver and certificate observation
417
+ times; previously published version-2 packets remain verifiable.
418
+
143
419
  ## Exit codes
144
420
 
145
- | Code | Meaning |
146
- | ---: | --- |
147
- | 0 | The command completed. |
148
- | 2 | Invalid command, option or input. |
149
- | 3 | Collection, lookup or comparison failed. |
150
- | 4 | The result was partial or a selected evidence policy was not met. |
151
- | 70 | Internal CLI bootstrap failure. |
152
- | 130 | The analyst cancelled the command. |
153
- | 143 | The process received SIGTERM. |
421
+ See the [exit-code reference](cli-reference.md#files-output-and-automation).
422
+ A completed command can still contain partial sources; use the selected
423
+ command's strict-exit policy when automation requires complete evidence.
424
+
425
+ For scripts, use `whoisleuth verify-artifact evidence.json --json --strict-exit`.
426
+ An incomplete verification returns **4** with that option. Without it, **0**
427
+ means a report was produced, not that the artefact passed verification; inspect
428
+ the report's state and checks.
154
429
 
155
430
  ## Command details
156
431
 
package/lib/abort.mjs CHANGED
@@ -10,7 +10,10 @@ function abortable(operation, signal) {
10
10
  const aborted = () => reject(abortReason(signal));
11
11
  signal.addEventListener('abort', aborted, { once: true });
12
12
  Promise.resolve()
13
- .then(operation)
13
+ .then(() => {
14
+ signal.throwIfAborted();
15
+ return operation();
16
+ })
14
17
  .then(resolve, reject)
15
18
  .finally(() => signal.removeEventListener('abort', aborted));
16
19
  });