@slicedearth/whoisleuth-cli 2.3.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 (264) hide show
  1. package/NOTICE +6 -0
  2. package/README.md +19 -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 +68 -6
  7. package/cli/artifact-validation/case-response.mjs +47 -41
  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 +83 -61
  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-command-runner.mjs +2 -0
  18. package/cli/bulk-output.mjs +29 -3
  19. package/cli/case-command.mjs +195 -0
  20. package/cli/case-pack.mjs +39 -31
  21. package/cli/command-argument-grammar.mjs +4 -0
  22. package/cli/command-reference.mjs +145 -75
  23. package/cli/completion.mjs +298 -79
  24. package/cli/config-profile.mjs +7 -5
  25. package/cli/ct-event-intake.mjs +12 -12
  26. package/cli/discover.mjs +4 -3
  27. package/cli/discovery-observation-snapshot.mjs +44 -16
  28. package/cli/discovery-scan-command-runner.mjs +2 -0
  29. package/cli/discovery-scan.mjs +7 -4
  30. package/cli/discovery-workflow.mjs +1 -1
  31. package/cli/domain-control-monitor.mjs +50 -20
  32. package/cli/domain-control-observations.mjs +149 -102
  33. package/cli/errors.mjs +33 -1
  34. package/cli/evidence-command-runner.mjs +16 -5
  35. package/cli/evidence-signing.mjs +2 -2
  36. package/cli/external-observation-mapping.mjs +7 -8
  37. package/cli/formatters/terminal-lookup.mjs +18 -46
  38. package/cli/formatters/terminal-shared.mjs +5 -3
  39. package/cli/formatters/terminal-technology.mjs +114 -0
  40. package/cli/inline-command-families.mjs +1 -0
  41. package/cli/interactive-launcher.mjs +2 -160
  42. package/cli/interchange-report.mjs +4 -8
  43. package/cli/investigation-artifacts.mjs +176 -0
  44. package/cli/investigation-checkpoint.mjs +10 -0
  45. package/cli/investigation-folder.mjs +114 -0
  46. package/cli/investigation-manifest.mjs +1 -112
  47. package/cli/investigation-package-review.mjs +103 -0
  48. package/cli/investigation-plan.mjs +32 -19
  49. package/cli/investigation-run.mjs +188 -33
  50. package/cli/local-document-checkpoint.mjs +170 -0
  51. package/cli/lookup-browser.mjs +5 -12
  52. package/cli/lookup-command-runner.mjs +17 -6
  53. package/cli/lookup-diff.mjs +37 -3
  54. package/cli/lookup-plan.mjs +11 -5
  55. package/cli/lookup-reconcile.mjs +10 -8
  56. package/cli/mail-review.mjs +66 -14
  57. package/cli/manual.mjs +1 -0
  58. package/cli/network-command-runner.mjs +2 -0
  59. package/cli/output-file.mjs +31 -5
  60. package/cli/registry-doctor.mjs +3 -1
  61. package/cli/registry-support.mjs +1 -1
  62. package/cli/retained-artifact-diff.mjs +2 -2
  63. package/cli/review-command-runner.mjs +45 -0
  64. package/cli/runner.mjs +55 -6
  65. package/cli/sharing-review.mjs +21 -34
  66. package/cli/signer-trust.mjs +104 -0
  67. package/cli/source-reliability.mjs +74 -83
  68. package/cli/terminal-input.mjs +168 -0
  69. package/cli/workflow-command-runner.mjs +68 -13
  70. package/docs/cli-reference.md +76 -30
  71. package/docs/cli.md +289 -25
  72. package/lib/abort.mjs +4 -1
  73. package/lib/availability.mjs +161 -112
  74. package/lib/bounded-file.mjs +3 -1
  75. package/lib/bounded-html-document.mjs +125 -0
  76. package/lib/bounded-json.mjs +43 -3
  77. package/lib/browser-library-profile.mjs +42 -51
  78. package/lib/browser-library-worker.mjs +112 -0
  79. package/lib/client-behavior-profile.mjs +3 -3
  80. package/lib/ct-search.mjs +11 -4
  81. package/lib/dns-delegation-health.mjs +24 -14
  82. package/lib/dns-inheritance-review.mjs +217 -0
  83. package/lib/dns-intelligence.mjs +36 -23
  84. package/lib/dns-mx.mjs +1 -1
  85. package/lib/dnssec-evidence-validation.mjs +11 -3
  86. package/lib/domain-change-packet.mjs +7 -2
  87. package/lib/domain-change-review.mjs +14 -8
  88. package/lib/domain-control-flight-recorder.mjs +219 -118
  89. package/lib/domain-control-manifest.mjs +117 -57
  90. package/lib/domain-posture-analysis.mjs +20 -7
  91. package/lib/domain-posture-parsers.mjs +88 -6
  92. package/lib/domain-posture.mjs +113 -26
  93. package/lib/evidence-export-privacy.mjs +561 -0
  94. package/lib/evidence-export.mjs +11 -592
  95. package/lib/evidence-report.mjs +3 -1
  96. package/lib/favicon.mjs +117 -41
  97. package/lib/generated/cisa-kev-catalog.mjs +12 -4
  98. package/lib/generated/registrar-standing-catalogue.mjs +7 -7
  99. package/lib/generated/retire-browser-catalog.mjs +2335 -940
  100. package/lib/hostname.mjs +2 -38
  101. package/lib/html-signals.mjs +82 -85
  102. package/lib/http-evidence-bounds.mjs +2 -1
  103. package/lib/http-intelligence.mjs +39 -26
  104. package/lib/lookup-cache.mjs +34 -11
  105. package/lib/lookup-child-profile-contract.mjs +24 -15
  106. package/lib/lookup-diagnostics.mjs +24 -1
  107. package/lib/lookup-progress.mjs +30 -13
  108. package/lib/lookup-response-contract.mjs +1048 -0
  109. package/lib/lookup-response.mjs +7 -10
  110. package/lib/lookup-source-progress.mjs +17 -14
  111. package/lib/lookup.mjs +48 -27
  112. package/lib/observed-network-context.mjs +43 -27
  113. package/lib/outbound-request-bounds.mjs +4 -1
  114. package/lib/page-fingerprints.mjs +69 -133
  115. package/lib/page-role-profile.mjs +12 -8
  116. package/lib/perceptual-hash.mjs +43 -14
  117. package/lib/rdap-bootstrap.mjs +72 -22
  118. package/lib/rdap-client.mjs +15 -7
  119. package/lib/rdap-normalization.mjs +33 -18
  120. package/lib/rdap-search-workbench.mjs +1 -1
  121. package/lib/rdap-transport.mjs +47 -20
  122. package/lib/rdap-types.mjs +2 -1
  123. package/lib/rdap.mjs +18 -3
  124. package/lib/registrable-domain.mjs +21 -1
  125. package/lib/registrar-standing-contract.mjs +1 -0
  126. package/lib/registry-capabilities.mjs +5 -1
  127. package/lib/registry-capability-catalogue.mjs +2 -1
  128. package/lib/registry-dates.mjs +2 -190
  129. package/lib/registry-insights.mjs +56 -18
  130. package/lib/response-policy.mjs +86 -48
  131. package/lib/risk-calibration-summary.mjs +17 -3
  132. package/lib/rpki-evidence.mjs +8 -5
  133. package/lib/safe-fetch.mjs +54 -47
  134. package/lib/security-txt.mjs +10 -7
  135. package/lib/service-binding-dns.mjs +8 -0
  136. package/lib/smtp-transport-review.mjs +27 -15
  137. package/lib/sslbl-certificates.generated.mjs +136 -135
  138. package/lib/static-html-analysis.mjs +117 -146
  139. package/lib/structured-data-identity.mjs +1 -0
  140. package/lib/technology-markup.mjs +100 -0
  141. package/lib/threat-intelligence-manifests.mjs +11 -139
  142. package/lib/threat-intelligence-runtime.mjs +24 -52
  143. package/lib/threat-intelligence-targets.mjs +6 -34
  144. package/lib/threat-intelligence-types.mjs +39 -0
  145. package/lib/typosquat-generator.mjs +19 -15
  146. package/lib/urlhaus-intelligence.mjs +8 -3
  147. package/lib/urlscan-intelligence.mjs +1 -1
  148. package/lib/website-security-posture.mjs +30 -6
  149. package/lib/website-technology.mjs +111 -71
  150. package/lib/whois-authority.mjs +14 -2
  151. package/lib/whois-chain.mjs +9 -2
  152. package/lib/whois-common-formats.mjs +5 -5
  153. package/lib/whois-contracts.mjs +2 -1
  154. package/lib/whois-dialects.mjs +2 -2
  155. package/lib/whois-parser-support.mjs +8 -1
  156. package/lib/whois-transport.mjs +23 -7
  157. package/package.json +2 -1
  158. package/packages/cases/case-attachment-model.mjs +83 -0
  159. package/packages/cases/case-evidence-model.mjs +156 -62
  160. package/packages/cases/case-export-input.mjs +53 -0
  161. package/packages/cases/case-migration-model.mjs +59 -25
  162. package/packages/cases/case-model.mjs +1 -0
  163. package/packages/cases/case-recheck-model.mjs +91 -0
  164. package/packages/cases/case-record-core.mjs +3 -2
  165. package/packages/cases/case-record-operations.mjs +89 -27
  166. package/packages/cases/case-record-projection.mjs +23 -20
  167. package/packages/cases/case-report.mjs +63 -52
  168. package/packages/cases/case-response-actions.mjs +654 -0
  169. package/packages/cases/case-response-model.mjs +41 -1129
  170. package/packages/cases/case-response-outcomes.mjs +317 -0
  171. package/packages/cases/case-response-packet-render.mjs +153 -0
  172. package/packages/cases/case-response-packet-types.mjs +1 -0
  173. package/packages/cases/case-response-packet-vocabulary.mjs +48 -0
  174. package/packages/cases/case-response-packet.mjs +38 -587
  175. package/packages/cases/case-response-records.mjs +94 -0
  176. package/packages/cases/case-response-review-inputs.mjs +371 -0
  177. package/packages/cases/case-response-values.mjs +85 -0
  178. package/packages/cases/case-review-package.mjs +45 -0
  179. package/packages/cases/case-selection.mjs +46 -0
  180. package/packages/cases/case-storage-model.mjs +25 -0
  181. package/packages/cases/response-route-freshness.mjs +16 -0
  182. package/packages/comparison/capture-context.mjs +61 -0
  183. package/packages/comparison/comparison-ledger-bulk.mjs +4 -1
  184. package/packages/comparison/page-similarity.mjs +2 -1
  185. package/packages/comparison/relationship-evidence.mjs +85 -8
  186. package/packages/comparison/relationship-provenance.mjs +93 -0
  187. package/packages/contracts/analyst-interchange.mjs +216 -0
  188. package/packages/contracts/analyst-review-state-contract.mjs +11 -0
  189. package/packages/contracts/analyst-review-state.mjs +5 -4
  190. package/packages/contracts/case-portability.mjs +279 -116
  191. package/packages/contracts/case-views-contract.mjs +12 -0
  192. package/packages/contracts/case-views.mjs +26 -0
  193. package/packages/contracts/cli-command-semantics.mjs +1 -0
  194. package/packages/contracts/cli-lookup.mjs +0 -1
  195. package/packages/contracts/domain-control-flight-recorder.mjs +80 -42
  196. package/packages/contracts/domain-control-manifest.mjs +127 -73
  197. package/packages/contracts/domain-control-monitor.mjs +48 -45
  198. package/packages/contracts/domain-control-review.mjs +179 -121
  199. package/packages/contracts/domain-name.mjs +21 -0
  200. package/packages/contracts/external-observation-interchange.mjs +2 -1
  201. package/packages/contracts/extracted-domain-lifecycle.mjs +11 -32
  202. package/packages/contracts/http-url.mjs +2 -0
  203. package/packages/contracts/investigation-package-limits.mjs +24 -0
  204. package/packages/contracts/investigation-portability.mjs +56 -27
  205. package/packages/contracts/investigation-run.mjs +16 -0
  206. package/packages/contracts/ip-address.mjs +34 -0
  207. package/packages/contracts/lookup-evidence.mjs +35 -0
  208. package/packages/contracts/offline-comparison.mjs +1 -2
  209. package/packages/contracts/page-fingerprints.mjs +15 -0
  210. package/packages/contracts/risk-calibration.mjs +0 -1
  211. package/packages/contracts/schema-compatibility.mjs +1 -1
  212. package/packages/contracts/schema-lifecycle.mjs +83 -153
  213. package/packages/contracts/selected-file-limits.mjs +6 -0
  214. package/packages/contracts/vulnerability-identifiers.mjs +5 -0
  215. package/packages/contracts/web-capture.mjs +10 -0
  216. package/packages/contracts/workspace-portability.mjs +160 -225
  217. package/packages/evidence/artifact-integrity.mjs +8 -2
  218. package/packages/evidence/artifact-structure.mjs +105 -0
  219. package/packages/evidence/decision-fact.mjs +4 -1
  220. package/packages/evidence/domain-control-runtime.mjs +155 -27
  221. package/packages/evidence/domain-posture-context.mjs +79 -0
  222. package/packages/evidence/image-regions.mjs +56 -0
  223. package/packages/evidence/latest-observations.mjs +28 -0
  224. package/packages/evidence/lookup-target.mjs +104 -0
  225. package/packages/evidence/observation.mjs +16 -1
  226. package/packages/evidence/passphrase-encryption.mjs +44 -0
  227. package/packages/evidence/record-identity.mjs +72 -0
  228. package/packages/evidence/registry-dates.mjs +190 -0
  229. package/packages/evidence/retained-file.mjs +69 -0
  230. package/packages/evidence/review-age.mjs +14 -0
  231. package/packages/interchange/bagit.mjs +342 -0
  232. package/packages/interchange/bounded-zip-extraction.mjs +278 -0
  233. package/packages/interchange/crc32.mjs +17 -0
  234. package/packages/interchange/external-findings-import.mjs +89 -49
  235. package/packages/interchange/web-capture-import.mjs +382 -0
  236. package/packages/investigation/bulk-mail-exposure.mjs +2 -1
  237. package/packages/investigation/investigation-bagit.mjs +14 -0
  238. package/packages/investigation/investigation-capsule.mjs +33 -5
  239. package/packages/investigation/investigation-manifest.mjs +200 -0
  240. package/packages/investigation/investigation-package-crypto.mjs +98 -0
  241. package/packages/investigation/investigation-package.mjs +200 -0
  242. package/packages/investigation/lookup-asset-graph-inputs.mjs +38 -0
  243. package/packages/investigation/lookup-asset-graph.mjs +161 -141
  244. package/packages/investigation/lookup-claim-readiness.mjs +18 -0
  245. package/packages/investigation/lookup-investigation-brief.mjs +3 -3
  246. package/packages/investigation/lookup-task-guidance.mjs +58 -0
  247. package/packages/monitoring/analyst-review-state.mjs +38 -90
  248. package/packages/workspace/brand-profile-model.mjs +158 -20
  249. package/packages/workspace/bulk-columns.mjs +17 -0
  250. package/packages/workspace/bulk-review-model.mjs +11 -5
  251. package/packages/workspace/bulk-session-model.mjs +95 -20
  252. package/packages/workspace/bulk-sort.mjs +1 -0
  253. package/packages/workspace/case-views.mjs +102 -0
  254. package/packages/workspace/hostile-input.mjs +29 -8
  255. package/packages/workspace/investigation-template-model.mjs +19 -4
  256. package/packages/workspace/page-baseline.mjs +18 -15
  257. package/packages/workspace/relationship-observation-model.mjs +41 -26
  258. package/packages/workspace/watchlist-history.mjs +2 -2
  259. package/packages/workspace/watchlist-store.mjs +8 -2
  260. package/packages/workspace/website-snapshot-model.mjs +70 -30
  261. package/packages/workspace/workspace-archive-crypto.mjs +41 -61
  262. package/packages/workspace/workspace-archive.mjs +85 -21
  263. package/third-party-notices.txt +29 -2
  264. package/packages/investigation/bulk-review-export.mjs +0 -63
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,13 +74,17 @@ 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
@@ -83,17 +96,15 @@ One strict domain, IP address or ASN can occupy command position as Lookup
83
96
  shorthand. URL-like or ambiguous input requires the explicit `lookup` command.
84
97
  Only `bulk` accepts multiple targets.
85
98
 
86
- ## 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.
87
103
 
88
- The installed registry groups commands under the same analyst vocabulary as the
89
- application:
104
+ ## Command groups
90
105
 
91
- | Group | Common commands |
92
- | --- | --- |
93
- | Investigate | `lookup`, `bulk`, `discover`, `ct-search`, `posture`, `http`, `tls`, `compare`, `mail-headers`, `brief` |
94
- | Respond | `case-pack`, `change-packet`, `sharing-review`, `export` (local handoff from browser-created Cases) |
95
- | Assure | `dnssec-validate`, `mail-transport`, `domain-control`, `assurance`, `workflow-plan`, `diff`, `inspect-archive`, `verify-artifact` |
96
- | 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.
97
108
 
98
109
  Filter the canonical index without running the selected commands:
99
110
 
@@ -104,12 +115,14 @@ whoisleuth commands --json
104
115
  whoisleuth manual | man -l -
105
116
  ```
106
117
 
107
- `registry-scaffold` has a separate fixture bootstrap contract: its `--profile`
108
- selects one fixed fixture profile and it rejects shared `--config` profiles.
109
- It creates sanitised local fixtures and does not contact a registry.
110
-
111
118
  ## Collection boundaries
112
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
+
113
126
  | Mode | Behaviour |
114
127
  | --- | --- |
115
128
  | Fast Lookup | Registration-first triage without WHOIS or deeper website and TLS collection. |
@@ -135,11 +148,253 @@ cryptographic validation, and alignment differences can be legitimate.
135
148
 
136
149
  ## Output and automation
137
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
+
138
390
  Terminal text is the default. Commands expose JSON, JSONL, CSV, Markdown, HTML
139
391
  or domain-only output only where declared by the installed registry. Redirected
140
392
  and machine output contains no ANSI or transient progress text.
141
393
 
142
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.
143
398
  - `lookup --browse` provides an interactive terminal view; press `?` for keys.
144
399
  - `--events` writes versioned lifecycle events to stderr.
145
400
  - `--strict-exit` and `--fail-on` expose selected evidence states to automation.
@@ -151,17 +406,26 @@ concurrency and observer labels. They cannot add targets, enable Deep
151
406
  collection, choose output paths or approve network work. Explicit command
152
407
  options override profile defaults.
153
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
+
154
419
  ## Exit codes
155
420
 
156
- | Code | Meaning |
157
- | ---: | --- |
158
- | 0 | The command completed. |
159
- | 2 | Invalid command, option or input. |
160
- | 3 | Collection, lookup or comparison failed. |
161
- | 4 | The result was partial or a selected evidence policy was not met. |
162
- | 70 | Internal CLI bootstrap failure. |
163
- | 130 | The analyst cancelled the command. |
164
- | 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.
165
429
 
166
430
  ## Command details
167
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
  });