@slicedearth/whoisleuth-cli 2.4.0 → 2.5.1

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 (300) hide show
  1. package/NOTICE +2 -2
  2. package/cli/argument-values.mjs +55 -0
  3. package/cli/arguments.mjs +24 -470
  4. package/cli/artifact-validation/case-response.mjs +21 -6
  5. package/cli/artifact-validation/lookup-evidence.mjs +3 -3
  6. package/cli/artifact-validation/signed-review.mjs +59 -13
  7. package/cli/artifact-verify.mjs +2 -0
  8. package/cli/assurance-arguments.mjs +56 -0
  9. package/cli/assurance-command-definitions.mjs +179 -0
  10. package/cli/assurance-command-runner.mjs +1 -1
  11. package/cli/bounded-json.mjs +1 -1
  12. package/cli/bulk-checkpoint.mjs +1 -1
  13. package/cli/bulk-command-runner.mjs +2 -2
  14. package/cli/bulk.mjs +2 -3
  15. package/cli/case-command.mjs +20 -3
  16. package/cli/case-pack.mjs +12 -2
  17. package/cli/ci-report.mjs +6 -3
  18. package/cli/collection-arguments.mjs +123 -0
  19. package/cli/collection-command-definitions.mjs +330 -0
  20. package/cli/collection-preflight.mjs +8 -1
  21. package/cli/command-argument-grammar.mjs +8 -210
  22. package/cli/command-definition.mjs +329 -0
  23. package/cli/command-reference.mjs +55 -1388
  24. package/cli/command-seeds.mjs +18 -0
  25. package/cli/compare.mjs +1 -1
  26. package/cli/config-profile.mjs +1 -1
  27. package/cli/ct-event-intake.mjs +4 -4
  28. package/cli/discover.mjs +2 -2
  29. package/cli/discovery-observation-snapshot.mjs +2 -2
  30. package/cli/discovery-scan-command-runner.mjs +3 -3
  31. package/cli/discovery-scan.mjs +1 -1
  32. package/cli/discovery-snapshot.mjs +2 -2
  33. package/cli/discovery-workflow.mjs +4 -4
  34. package/cli/domain-control-monitor.mjs +6 -6
  35. package/cli/domain-control-observations.mjs +2 -1
  36. package/cli/errors.mjs +5 -6
  37. package/cli/evidence-arguments.mjs +46 -0
  38. package/cli/evidence-command-definitions.mjs +69 -0
  39. package/cli/evidence-command-runner.mjs +2 -3
  40. package/cli/evidence-signing.mjs +3 -2
  41. package/cli/external-observation-mapping.mjs +2 -2
  42. package/cli/formatters/terminal-command-formats.mjs +6 -0
  43. package/cli/formatters/terminal-lookup.mjs +6 -0
  44. package/cli/history-arguments.mjs +39 -0
  45. package/cli/history-command-definitions.mjs +88 -0
  46. package/cli/indicator-set-command.mjs +33 -0
  47. package/cli/inline-command-families.mjs +6 -47
  48. package/cli/inline-command-runner.mjs +4 -31
  49. package/cli/input.mjs +2 -2
  50. package/cli/intake-command.mjs +107 -0
  51. package/cli/interchange-report.mjs +4 -2
  52. package/cli/investigation-artifacts.mjs +1 -1
  53. package/cli/investigation-plan.mjs +32 -154
  54. package/cli/investigation-recipes.mjs +177 -0
  55. package/cli/investigation-run.mjs +78 -12
  56. package/cli/local-mmdb-review.mjs +197 -17
  57. package/cli/lookalike-calibration.mjs +1 -1
  58. package/cli/lookup-brief.mjs +2 -2
  59. package/cli/lookup-command-runner.mjs +18 -35
  60. package/cli/lookup-diff.mjs +10 -9
  61. package/cli/lookup-plan.mjs +10 -14
  62. package/cli/lookup-reconcile.mjs +6 -5
  63. package/cli/lookup-timeline.mjs +7 -6
  64. package/cli/mail-header-review.mjs +24 -247
  65. package/cli/mail-review.mjs +9 -7
  66. package/cli/network-arguments.mjs +51 -0
  67. package/cli/network-command-definitions.mjs +130 -0
  68. package/cli/network-command-runner.mjs +43 -31
  69. package/cli/offline-evidence-review.mjs +39 -6
  70. package/cli/posture.mjs +1 -1
  71. package/cli/registry-fixture-scaffold.mjs +1 -1
  72. package/cli/retained-artifact-diff.mjs +8 -7
  73. package/cli/review-arguments.mjs +131 -0
  74. package/cli/review-command-definitions.mjs +273 -0
  75. package/cli/review-command-runner.mjs +68 -158
  76. package/cli/risk-calibration.mjs +8 -8
  77. package/cli/runner.mjs +20 -32
  78. package/cli/saved-lookup.mjs +2 -2
  79. package/cli/selected-input-worker.mjs +63 -0
  80. package/cli/sharing-review.mjs +5 -64
  81. package/cli/source-reliability.mjs +2 -2
  82. package/cli/support-arguments.mjs +49 -0
  83. package/cli/support-command-definitions.mjs +209 -0
  84. package/cli/support-command-runner.mjs +2 -2
  85. package/cli/workflow-arguments.mjs +86 -0
  86. package/cli/workflow-command-definitions.mjs +78 -0
  87. package/cli/workflow-command-runner.mjs +8 -1
  88. package/docs/cli-reference.md +27 -0
  89. package/docs/cli.md +223 -2
  90. package/docs/contextual-reviews.md +155 -0
  91. package/lib/analyst-taxonomy.mjs +2 -40
  92. package/lib/application-version.mjs +1 -1
  93. package/lib/availability.mjs +13 -789
  94. package/lib/bounded-contract-normalizers.mjs +4 -0
  95. package/lib/bounded-json.mjs +2 -330
  96. package/lib/browser-library-profile.mjs +62 -84
  97. package/lib/candidate-provenance-bounds.mjs +2 -0
  98. package/lib/classify.mjs +1 -1
  99. package/lib/ct-query.mjs +2 -27
  100. package/lib/ct-response-bounds.mjs +2 -9
  101. package/lib/ct-search.mjs +83 -47
  102. package/lib/dmarc-discovery.mjs +62 -0
  103. package/lib/dns-convergence-review.mjs +21 -8
  104. package/lib/dns-delegation-health.mjs +12 -8
  105. package/lib/dns-inheritance-review.mjs +53 -63
  106. package/lib/dns-intelligence.mjs +9 -7
  107. package/lib/dns-query-coverage.mjs +40 -0
  108. package/lib/domain-assurance.mjs +1 -1
  109. package/lib/domain-change-packet.mjs +12 -13
  110. package/lib/domain-change-review.mjs +32 -14
  111. package/lib/domain-control-flight-recorder.mjs +103 -27
  112. package/lib/domain-control-manifest.mjs +25 -17
  113. package/lib/domain-posture-analysis.mjs +22 -5
  114. package/lib/domain-posture.mjs +16 -15
  115. package/lib/domain-registration.mjs +392 -0
  116. package/lib/evidence-export-privacy.mjs +22 -15
  117. package/lib/evidence-export.mjs +3 -3
  118. package/lib/evidence-report.mjs +4 -1
  119. package/lib/external-intelligence-risk.mjs +2 -124
  120. package/lib/generated/cisa-kev-catalog.mjs +41 -3
  121. package/lib/generated/registrar-standing-catalogue.mjs +18 -10
  122. package/lib/generated/retire-browser-catalog.mjs +332 -19
  123. package/lib/generated/{unicode-confusables-17.mjs → unicode-confusables.mjs} +33 -33
  124. package/lib/html-signals.mjs +7 -2
  125. package/lib/http-evidence-bounds.mjs +2 -8
  126. package/lib/http-intelligence.mjs +13 -1
  127. package/lib/idn-confusable-policy.mjs +6 -4
  128. package/lib/idn-confusables.mjs +1 -1
  129. package/lib/interchange-fidelity-registry.mjs +10 -0
  130. package/lib/lookup-contract-primitives.mjs +1 -1
  131. package/lib/lookup-diagnostics.mjs +1 -23
  132. package/lib/lookup-network-evidence-bounds.mjs +1 -10
  133. package/lib/lookup-progress.mjs +50 -1
  134. package/lib/lookup-response-contract.mjs +69 -40
  135. package/lib/lookup-response.mjs +5 -15
  136. package/lib/lookup-source-progress.mjs +8 -22
  137. package/lib/lookup.mjs +1 -1
  138. package/lib/opportunity-scoring.mjs +2 -107
  139. package/lib/outbound-identity.mjs +1 -1
  140. package/lib/page-fingerprints.mjs +18 -1
  141. package/lib/perceptual-hash-comparison.mjs +2 -34
  142. package/lib/perceptual-hash.mjs +36 -10
  143. package/lib/portable-generator.mjs +2 -29
  144. package/lib/project-metadata.mjs +2 -8
  145. package/lib/rdap-client.mjs +11 -7
  146. package/lib/registrable-domain.mjs +2 -56
  147. package/lib/registrar-standing-contract.mjs +1 -1
  148. package/lib/registrar-standing.mjs +9 -13
  149. package/lib/registry-capabilities.mjs +2 -2
  150. package/lib/registry-capability-catalogue.mjs +27 -19
  151. package/lib/risk-calibration-summary.mjs +2 -352
  152. package/lib/risk-scoring.mjs +2 -345
  153. package/lib/scoring-evidence-quality.mjs +2 -107
  154. package/lib/semantic-version.mjs +2 -54
  155. package/lib/service-binding-dns.mjs +27 -0
  156. package/lib/smtp-transport-review.mjs +48 -19
  157. package/lib/sslbl-certificates.generated.mjs +141 -139
  158. package/lib/static-html-analysis.mjs +1 -1
  159. package/lib/structured-data-identity.mjs +1 -1
  160. package/lib/threat-intelligence-manifests.mjs +1 -1
  161. package/lib/threat-intelligence-runtime.mjs +1 -1
  162. package/lib/threat-intelligence-targets.mjs +1 -1
  163. package/lib/threat-intelligence-types.mjs +2 -75
  164. package/lib/typosquat-generator.mjs +11 -2
  165. package/lib/website-enrichment.mjs +388 -0
  166. package/lib/whois-authority.mjs +43 -5
  167. package/lib/whois-normalization.mjs +3 -1
  168. package/lib/whois-parser.mjs +5 -3
  169. package/package.json +13 -14
  170. package/packages/analysis/analyst-taxonomy.mjs +40 -0
  171. package/packages/analysis/bounded-json.mjs +330 -0
  172. package/packages/analysis/candidate-provenance-bounds.mjs +4 -0
  173. package/packages/analysis/cli-argument-grammar.mjs +216 -0
  174. package/packages/analysis/ct-query.mjs +27 -0
  175. package/packages/analysis/ct-response-bounds.mjs +9 -0
  176. package/packages/analysis/external-intelligence-risk.mjs +124 -0
  177. package/packages/analysis/http-evidence-bounds.mjs +8 -0
  178. package/packages/analysis/lookup-network-evidence-bounds.mjs +10 -0
  179. package/packages/analysis/opportunity-scoring.mjs +107 -0
  180. package/packages/analysis/perceptual-hash-comparison.mjs +34 -0
  181. package/packages/analysis/portable-generator.mjs +29 -0
  182. package/packages/analysis/project-metadata.mjs +8 -0
  183. package/packages/analysis/registrable-domain.mjs +56 -0
  184. package/packages/analysis/risk-calibration-summary.mjs +352 -0
  185. package/packages/analysis/risk-scoring.mjs +345 -0
  186. package/packages/analysis/scoring-evidence-quality.mjs +107 -0
  187. package/packages/analysis/semantic-version.mjs +54 -0
  188. package/packages/analysis/threat-intelligence-types.mjs +75 -0
  189. package/packages/cases/case-evidence-links.mjs +135 -0
  190. package/packages/cases/case-evidence-model.mjs +78 -12
  191. package/packages/cases/case-evidence-quality.mjs +5 -0
  192. package/packages/cases/case-export-input.mjs +7 -5
  193. package/packages/cases/case-incident-context.mjs +52 -0
  194. package/packages/cases/case-markdown.mjs +11 -0
  195. package/packages/cases/case-migration-model.mjs +40 -24
  196. package/packages/cases/case-recheck-model.mjs +90 -32
  197. package/packages/cases/case-record-contracts.mjs +1 -1
  198. package/packages/cases/case-record-core.mjs +1 -1
  199. package/packages/cases/case-record-operations.mjs +40 -112
  200. package/packages/cases/case-record-projection.mjs +8 -1
  201. package/packages/cases/case-record-version-input.mjs +19 -0
  202. package/packages/cases/case-report.mjs +63 -27
  203. package/packages/cases/case-requested-evidence.mjs +146 -0
  204. package/packages/cases/case-response-actions.mjs +103 -1
  205. package/packages/cases/case-response-model.mjs +9 -1
  206. package/packages/cases/case-response-outcomes.mjs +29 -8
  207. package/packages/cases/case-response-packet-render.mjs +13 -9
  208. package/packages/cases/case-response-packet.mjs +25 -11
  209. package/packages/cases/case-response-records.mjs +0 -1
  210. package/packages/cases/case-response-review-inputs.mjs +14 -3
  211. package/packages/cases/case-storage-model.mjs +1 -1
  212. package/packages/cases/case-workflow-metadata.mjs +225 -0
  213. package/packages/cases/case-workflow-migration.mjs +43 -0
  214. package/packages/cases/http-summary.mjs +6 -1
  215. package/packages/comparison/favicon-similarity.mjs +1 -1
  216. package/packages/comparison/page-similarity.mjs +1 -1
  217. package/packages/comparison/relationship-evidence.mjs +21 -9
  218. package/packages/comparison/relationship-provenance.mjs +7 -1
  219. package/packages/contracts/analyst-interchange.mjs +28 -10
  220. package/packages/contracts/case-portability.mjs +118 -25
  221. package/packages/contracts/cli-command-semantics.mjs +2 -0
  222. package/packages/contracts/cli-grammar.mjs +6 -0
  223. package/packages/contracts/cli-lookup-brief.mjs +2 -0
  224. package/packages/contracts/context-review.mjs +24 -0
  225. package/packages/contracts/document-intake.mjs +11 -0
  226. package/packages/contracts/domain-control-flight-recorder.mjs +30 -14
  227. package/packages/contracts/domain-control-manifest.mjs +1 -1
  228. package/packages/contracts/domain-control-monitor.mjs +13 -2
  229. package/packages/contracts/domain-control-review.mjs +36 -15
  230. package/packages/contracts/har-review.mjs +2 -0
  231. package/packages/contracts/identity-events.mjs +4 -0
  232. package/packages/contracts/investigation-run.mjs +2 -0
  233. package/packages/contracts/ip-address.mjs +10 -0
  234. package/packages/contracts/mail-authentication.mjs +5 -0
  235. package/packages/contracts/message-intake.mjs +31 -0
  236. package/packages/contracts/offline-comparison.mjs +4 -1
  237. package/packages/contracts/passive-mail.mjs +10 -0
  238. package/packages/contracts/web-capture.mjs +2 -2
  239. package/packages/contracts/workspace-portability.mjs +63 -17
  240. package/packages/evidence/collection-quality.mjs +67 -0
  241. package/packages/evidence/dns-query-outcome.mjs +72 -0
  242. package/packages/evidence/email-recipient.mjs +18 -0
  243. package/packages/evidence/lookup-target.mjs +5 -1
  244. package/packages/evidence/observation.mjs +2 -0
  245. package/packages/evidence/png-pixels.mjs +117 -0
  246. package/packages/evidence/public-address-policy.mjs +83 -0
  247. package/packages/interchange/defensive-indicator-export.mjs +264 -0
  248. package/packages/interchange/external-findings-import.mjs +4 -12
  249. package/packages/interchange/managed-indicator-export.mjs +35 -0
  250. package/packages/interchange/managed-indicator-set.mjs +210 -0
  251. package/packages/interchange/misp-indicator-export.mjs +93 -0
  252. package/packages/interchange/sharing-policy.mjs +56 -0
  253. package/packages/interchange/stix-indicator-export.mjs +129 -0
  254. package/packages/interchange/web-capture-import.mjs +22 -15
  255. package/packages/investigation/acquisition-decision-packet.mjs +1 -1
  256. package/packages/investigation/bulk-mail-exposure.mjs +7 -13
  257. package/packages/investigation/capture-coverage.mjs +57 -0
  258. package/packages/investigation/connector-provenance-review.mjs +100 -0
  259. package/packages/investigation/context-review.mjs +35 -0
  260. package/packages/investigation/document-intake.mjs +81 -0
  261. package/packages/investigation/docx-intake.mjs +133 -0
  262. package/packages/investigation/domain-history-review.mjs +100 -0
  263. package/packages/investigation/domain-transition-review.mjs +47 -0
  264. package/packages/investigation/evidence-coverage-ledger.mjs +9 -1
  265. package/packages/investigation/har-intake.mjs +68 -0
  266. package/packages/investigation/identity-event-intake.mjs +131 -0
  267. package/packages/investigation/identity-incident-review.mjs +39 -0
  268. package/packages/investigation/incident-sequence-review.mjs +58 -0
  269. package/packages/investigation/intake-report.mjs +19 -0
  270. package/packages/investigation/investigation-manifest.mjs +2 -2
  271. package/packages/investigation/link-intake.mjs +179 -0
  272. package/packages/investigation/lookup-asset-graph.mjs +1 -1
  273. package/packages/investigation/mail-authentication-review.mjs +222 -0
  274. package/packages/investigation/mail-header-identity.mjs +209 -0
  275. package/packages/investigation/message-intake.mjs +195 -0
  276. package/packages/investigation/page-behaviour.mjs +113 -0
  277. package/packages/investigation/pdf-intake.mjs +155 -0
  278. package/packages/investigation/platform-continuity-review.mjs +60 -0
  279. package/packages/investigation/qr-intake.mjs +56 -0
  280. package/packages/investigation/requested-action-hints.mjs +11 -0
  281. package/packages/investigation/selected-input-review.mjs +17 -0
  282. package/packages/investigation/storefront-review.mjs +70 -0
  283. package/packages/workspace/brand-profile-model.mjs +2 -2
  284. package/packages/workspace/bulk-review-model.mjs +28 -26
  285. package/packages/workspace/bulk-session-model.mjs +23 -13
  286. package/packages/workspace/campaign-model.mjs +1 -1
  287. package/packages/workspace/detection-rule-model.mjs +1 -1
  288. package/packages/workspace/hostile-input.mjs +1 -1
  289. package/packages/workspace/investigation-guide.mjs +2 -2
  290. package/packages/workspace/investigation-template-model.mjs +26 -2
  291. package/packages/workspace/page-baseline.mjs +2 -2
  292. package/packages/workspace/relationship-observation-model.mjs +17 -13
  293. package/packages/workspace/shortlist-model.mjs +14 -5
  294. package/packages/workspace/watchlist-history.mjs +28 -12
  295. package/packages/workspace/watchlist-store.mjs +17 -5
  296. package/packages/workspace/website-snapshot-model.mjs +28 -3
  297. package/packages/workspace/workspace-archive-crypto.mjs +1 -1
  298. package/packages/workspace/workspace-archive.mjs +5 -4
  299. package/third-party-notices.txt +549 -16
  300. package/lib/hostname.mjs +0 -2
@@ -0,0 +1,86 @@
1
+ import {} from "./command-argument-grammar.mjs";
2
+ import { CliUsageError } from "./errors.mjs";
3
+ import {} from "./fail-policy.mjs";
4
+ import { commandDefaultNumber } from "./command-reference.mjs";
5
+ import { WORKFLOW_COMMAND_DEFINITIONS } from "./workflow-command-definitions.mjs";
6
+ import { terminalOptions, jsonOutput, failPolicies, parseOutput, } from "./argument-values.mjs";
7
+ function parseMonitorOnceArguments(parsed) {
8
+ const selectedFailPolicies = failPolicies(parsed, 'monitor-once');
9
+ return {
10
+ action: 'monitor-once',
11
+ source: parsed.positionalValue('source'),
12
+ previousSource: parsed.optionValue('--previous'),
13
+ output: parseOutput(parsed, WORKFLOW_COMMAND_DEFINITIONS['monitor-once']),
14
+ limit: parsed.integerOption('--limit') ?? commandDefaultNumber('monitor-once', '--limit'),
15
+ concurrency: parsed.integerOption('--concurrency') ??
16
+ commandDefaultNumber('monitor-once', '--concurrency'),
17
+ ...terminalOptions(parsed),
18
+ ...(selectedFailPolicies ? { failOn: selectedFailPolicies } : {}),
19
+ };
20
+ }
21
+ function parseWorkflowPlanArguments(parsed) {
22
+ const options = { output: jsonOutput(parsed), ...terminalOptions(parsed) };
23
+ if (parsed.hasOption('--list')) {
24
+ if (parsed.allPositionals.length > 0)
25
+ throw new CliUsageError('workflow-plan discovery options do not accept a subject.');
26
+ return { action: 'workflow-plan', discovery: 'list', ...options };
27
+ }
28
+ if (parsed.hasOption('--explain')) {
29
+ if (parsed.allPositionals.length > 0)
30
+ throw new CliUsageError('workflow-plan discovery options do not accept a subject.');
31
+ return {
32
+ action: 'workflow-plan',
33
+ discovery: 'explain',
34
+ recipe: parsed.optionValue('--explain'),
35
+ ...options,
36
+ };
37
+ }
38
+ if (parsed.allPositionals.length !== 2) {
39
+ throw new CliUsageError('workflow-plan requires one fixed recipe and one subject, --list, or --explain <recipe>.');
40
+ }
41
+ return {
42
+ action: 'workflow-plan',
43
+ recipe: parsed.positionalValue('recipe'),
44
+ subject: parsed.positionalValue('subject'),
45
+ ...options,
46
+ };
47
+ }
48
+ function parseWorkflowRunArguments(parsed) {
49
+ const artifactBindings = parsed.optionValues('--use-artifact').map((value) => {
50
+ const match = /^([a-z0-9]+(?:-[a-z0-9]+)*):([1-9][0-9]?)=([a-z0-9]+(?:-[a-z0-9]+)*)$/u.exec(value);
51
+ if (!match)
52
+ throw new CliUsageError('--use-artifact requires <step-id>:<input-number>=<earlier-step-id>.');
53
+ return Object.freeze({ stepId: match[1], input: Number(match[2]), sourceStepId: match[3] });
54
+ });
55
+ const selections = parsed.optionValues('--select').map((value) => {
56
+ const separator = value.indexOf('=');
57
+ const stepId = separator === -1 ? '' : value.slice(0, separator);
58
+ const selection = separator === -1 ? '' : value.slice(separator + 1);
59
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(stepId) || !selection.trim()) {
60
+ throw new CliUsageError('--select requires <step-id>=<path-or-value>.');
61
+ }
62
+ if (selection.startsWith('-')) {
63
+ throw new CliUsageError('--select values cannot start with a hyphen; prefix a file path with ./ when needed.');
64
+ }
65
+ return Object.freeze({ stepId, value: selection });
66
+ });
67
+ return {
68
+ action: 'workflow-run',
69
+ recipe: parsed.positionalValue('recipe'),
70
+ subject: parsed.positionalValue('subject'),
71
+ resumeSource: parsed.optionValue('--resume'),
72
+ selections: Object.freeze(selections),
73
+ artifactBindings: Object.freeze(artifactBindings),
74
+ confirmedReviews: Object.freeze(parsed.optionValues('--confirm-review')),
75
+ approveNetwork: parsed.hasOption('--approve-network'),
76
+ interactive: parsed.hasOption('--interactive'),
77
+ ...(parsed.hasOption('--preview') ? { preview: true } : {}),
78
+ output: jsonOutput(parsed),
79
+ ...terminalOptions(parsed),
80
+ };
81
+ }
82
+ export const WORKFLOW_ARGUMENT_PARSERS = Object.freeze({
83
+ 'monitor-once': parseMonitorOnceArguments,
84
+ 'workflow-plan': parseWorkflowPlanArguments,
85
+ 'workflow-run': parseWorkflowRunArguments,
86
+ });
@@ -0,0 +1,78 @@
1
+ import { INVESTIGATION_PLAN_RECIPES, RUNNABLE_INVESTIGATION_PLAN_RECIPES } from "./investigation-recipes.mjs";
2
+ import { positional, OPTIONAL_FILE_POSITIONAL, constraint, commandSeed, integer, BASE_INTEGER_RANGE, file } from "./command-definition.mjs";
3
+ export const WORKFLOW_COMMAND_DEFINITIONS = Object.freeze({
4
+ "monitor-once": commandSeed({
5
+ reference: {
6
+ description: 'Collect one bounded owned-domain review and compare it with an optional prior checkpoint.',
7
+ example: 'whoisleuth monitor-once manifest.json --previous previous.json --json --output next.json',
8
+ boundary: 'This is an operator-scheduled one-shot collection, not a daemon. It caps targets and concurrency, retains normalised observations, and never changes domain configuration.',
9
+ },
10
+ collection: { scope: 'Runs deep collection for at most 20 manifest domains with concurrency capped at 3.' },
11
+ summary: 'Run one bounded domain control review',
12
+ options: ['--previous', '--limit', '--concurrency', '--fail-on', '--json', '--junit', '--quiet', '--no-color'],
13
+ optionOverrides: {
14
+ '--concurrency': integer('Set the maximum number of concurrent collection tasks.', () => Object.freeze([BASE_INTEGER_RANGE(1, 3)]), () => 2),
15
+ },
16
+ positionals: OPTIONAL_FILE_POSITIONAL,
17
+ constraints: Object.freeze([
18
+ constraint({ kind: 'mutually_exclusive', options: ['--json', '--junit'] }),
19
+ ]),
20
+ handlerOwner: 'workflow',
21
+ networkEffect: 'always_network',
22
+ common: false,
23
+ schemaIdentifiers: Object.freeze(['whoisleuth.cli.domain-control-monitor', 'whoisleuth.domain-control-flight-recorder.input']),
24
+ primaryArtefacts: Object.freeze([]),
25
+ planSupport: false,
26
+ additionalOutputFormats: Object.freeze([]),
27
+ bootstrapProfile: 'allowed',
28
+ }),
29
+ "workflow-plan": commandSeed({
30
+ reference: {
31
+ description: 'Build a fixed domain-investigation plan from existing bounded CLI commands.',
32
+ example: 'whoisleuth workflow-plan domain-triage example.test --json',
33
+ boundary: 'Planning is offline and plan-only. It does not execute commands, expand placeholders, read files, make requests, or submit evidence.',
34
+ },
35
+ collection: { scope: 'Builds a fixed typed recipe and executes none of its network or file steps.' },
36
+ summary: 'Plan a fixed investigation recipe',
37
+ options: ['--list', '--explain', '--json', '--quiet', '--no-color'],
38
+ positionals: Object.freeze([
39
+ positional('recipe', 'enum', 0, 1, INVESTIGATION_PLAN_RECIPES),
40
+ positional('subject', 'text', 0, 1),
41
+ ]),
42
+ constraints: Object.freeze([
43
+ constraint({ kind: 'mutually_exclusive', options: ['--list', '--explain'] }),
44
+ ]),
45
+ handlerOwner: 'workflow',
46
+ networkEffect: 'offline',
47
+ common: true,
48
+ schemaIdentifiers: Object.freeze(['whoisleuth.cli.investigation-plan', 'whoisleuth.cli.workflow-recipe-catalogue']),
49
+ primaryArtefacts: Object.freeze(['Plan-only workflow document']),
50
+ planSupport: true,
51
+ additionalOutputFormats: Object.freeze([]),
52
+ bootstrapProfile: 'allowed',
53
+ }),
54
+ "workflow-run": commandSeed({
55
+ reference: {
56
+ description: 'Execute approved steps from a fixed investigation recipe and emit a resumable checkpoint.',
57
+ example: 'whoisleuth workflow-run domain-triage example.test --approve-network --json --output run.json',
58
+ boundary: '--preview validates the checkpoint and shows retained outputs, unresolved inputs and remaining approvals without executing or writing. It cannot be combined with approval, interactive, quiet or output-file flags. Only installed recipe commands can run. Network steps require explicit approval for each invocation. New runs connect compatible earlier outputs using the recipe defaults. Use --use-artifact <step-id>:<input-number>=<earlier-step-id> to override a connection; input numbers start at 1. Repeat --select for remaining placeholders in order, or supply every input for a step to replace its connections with files. Values stay literal and cannot start with a hyphen or invoke a shell. Optional --interactive prompts on terminal stderr for missing inputs; a blank answer pauses. It grants neither network approval nor human-review confirmation. A step declaring human review still requires --confirm-review <step-id> for that invocation. Checkpoints do not grant later approvals. Resumes preserve recorded connections. Content digests identify retained output, not authenticity or freshness. Partial collections pause for review and are not recollected on resume; failed validation or export steps remain retryable. Diagnostics go to stderr. File output holds exclusive adjacent locks and refuses concurrently changed state files.',
59
+ },
60
+ collection: { scope: 'Runs only fixed-recipe steps; network collection requires --approve-network and unresolved analyst selections pause.' },
61
+ summary: 'Execute approved fixed-recipe steps',
62
+ options: ['--select', '--use-artifact', '--confirm-review', '--approve-network', '--resume', '--interactive', '--preview', '--json', '--quiet', '--no-color'],
63
+ optionOverrides: { '--resume': file('Resume the selected workflow checkpoint; approvals must be supplied again.') },
64
+ positionals: Object.freeze([
65
+ positional('recipe', 'enum', 1, 1, RUNNABLE_INVESTIGATION_PLAN_RECIPES),
66
+ positional('subject', 'text', 1, 1),
67
+ ]),
68
+ constraints: Object.freeze([constraint({ kind: 'excludes_all', option: '--preview', excludedOptions: ['--approve-network', '--confirm-review', '--interactive', '--output', '--force', '--quiet'] })]),
69
+ handlerOwner: 'workflow',
70
+ networkEffect: 'conditional_network',
71
+ common: false,
72
+ schemaIdentifiers: Object.freeze(['whoisleuth.cli.investigation-run', 'whoisleuth.cli.investigation-preview']),
73
+ primaryArtefacts: Object.freeze(['Resumable workflow state', 'Offline resume preview']),
74
+ planSupport: false,
75
+ additionalOutputFormats: Object.freeze([]),
76
+ bootstrapProfile: 'allowed',
77
+ }),
78
+ });
@@ -5,7 +5,7 @@ import EXIT_CODES from "./exit-codes.mjs";
5
5
  import { evaluateCliFailPolicies, formatFailPolicyNotice } from "./fail-policy.mjs";
6
6
  import { formatJsonDocument } from "./formatters/json.mjs";
7
7
  import { buildInvestigationPlan, buildWorkflowRecipeCatalogue, formatInvestigationPlan, formatWorkflowRecipeCatalogue, } from "./investigation-plan.mjs";
8
- import { MAX_INVESTIGATION_RUN_BYTES, formatInvestigationRun, investigationRunExitCode, runInvestigationRecipe, } from "./investigation-run.mjs";
8
+ import { MAX_INVESTIGATION_RUN_BYTES, formatInvestigationRun, investigationRunExitCode, runInvestigationRecipe, previewInvestigationRecipe, formatInvestigationPreview, } from "./investigation-run.mjs";
9
9
  import { MAX_OFFLINE_EVIDENCE_INPUT_BYTES } from "./offline-evidence-review.mjs";
10
10
  import { MAX_OFFLINE_ARTIFACT_BYTES } from "./artifact-verify.mjs";
11
11
  import { MAX_RETAINED_ARTIFACT_DIFF_BYTES } from "./retained-artifact-diff.mjs";
@@ -125,6 +125,13 @@ async function runWorkflowRecipeCommand(args, dependencies, context) {
125
125
  throw new CliUsageError(`Could not read investigation resume state: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
126
126
  }
127
127
  }
128
+ if (args.preview) {
129
+ const preview = previewInvestigationRecipe(args.recipe, args.subject, {
130
+ resumeInput, selections: args.selections, artifactBindings: args.artifactBindings, generatedAt: context.now(),
131
+ });
132
+ context.writeStdout(args.output === 'json' ? formatJsonDocument(preview) : context.terminal(formatInvestigationPreview(preview), args.color));
133
+ return EXIT_CODES.SUCCESS;
134
+ }
128
135
  const document = await runInvestigationRecipe(args.recipe, args.subject, {
129
136
  approveNetwork: args.approveNetwork,
130
137
  selections: args.selections,
@@ -10,6 +10,11 @@ This page records the common interfaces and the boundaries that matter across
10
10
  commands. The generated [privacy/data-flow catalogue](https://github.com/slicedearth/whoisleuth/blob/main/docs/privacy-data-flow-catalogue.md)
11
11
  contains the exhaustive recipient, retention and export metadata.
12
12
 
13
+ For an explicitly selected local IP-location database, see the
14
+ [MMDB query and compatibility guide](cli.md#local-ip-location-database-review).
15
+ Current reviews retain exact-byte identity and intrinsic metadata, and require
16
+ an analyst-justified freshness policy. No database is downloaded or updated.
17
+
13
18
  ## Installation
14
19
 
15
20
  Use the [installation guide](cli.md#installation) for runtime requirements,
@@ -61,6 +66,26 @@ Optional browser providers are not implicit CLI actions.
61
66
  The [message-header review](cli.md#message-header-review) is offline and reports
62
67
  publisher claims, not independent DNS or cryptographic validation.
63
68
 
69
+ For copied-content or credential-abuse review, keep the supplied reference,
70
+ candidate, form destination and deliberately observed navigation separately
71
+ attributed. A similar page without a logo or brand name can still merit manual
72
+ evidence review; correspondence does not establish authorship, rights authority
73
+ or maliciousness. Missing optional capture or comparison evidence does not
74
+ prevent ordinary Case pins and conclusions. Installed command help remains the
75
+ authority for offline file inputs and explicit collection plans.
76
+
77
+ The [reporting guide](https://www.whoisleuth.com/resources/reporting-and-takedown-guidance)
78
+ uses the same reviewed official-route catalogue as the Console for social
79
+ content, storefronts, app listings, hosted files/forms and advertising. Verify
80
+ the exact object, current route, reporter authority and disclosure before any
81
+ manual submission. Fraud, order complaints and rights notices are different
82
+ processes; search or app delisting is not source-content removal. Formal domain
83
+ disputes and copyright procedures have their own criteria, remedies and
84
+ jurisdiction-specific rules, not a universal waiting period. CLI preparation
85
+ does not submit a report or make a legal declaration. Preserve original delivery
86
+ references when preparing additional evidence, and recheck each response scope
87
+ independently rather than treating a closed ticket as remediation.
88
+
64
89
  `dnssec-validate` and `mail-transport` are isolated authorised actions. Both
65
90
  require a selected literal public resolver, a local trust-anchor document and
66
91
  `--owned-or-authorized`; mail transport also requires `--active-probe`. Mail
@@ -194,6 +219,8 @@ own retention policy. No automatic key discovery, key storage or trust refresh o
194
219
  evidence. They do not tune the running model, change infrastructure or turn an
195
220
  analyst label into observed truth.
196
221
  - `workflow-plan` lists fixed installed recipes without executing them.
222
+ `workflow-run --preview` validates a selected checkpoint and explains remaining
223
+ inputs and approvals offline, without running steps or rewriting the checkpoint.
197
224
  `workflow-run` executes only installed steps, requires approval for network
198
225
  work and pauses at unresolved analyst selections. Repeat
199
226
  `--select <step-id>=<path-or-value>` for remaining placeholders in order, or bind
package/docs/cli.md CHANGED
@@ -7,6 +7,11 @@ collection boundaries and output. Installed `whoisleuth --help`, focused
7
7
  version. The [CLI reference](cli-reference.md) covers durable command and
8
8
  artefact contracts.
9
9
 
10
+ The [website command reference](https://www.whoisleuth.com/cli) starts with common
11
+ tasks, then offers searchable commands with examples, option explanations and
12
+ defaults. Command-section links can be bookmarked. Its option guidance and
13
+ installed help derive from the same command definitions.
14
+
10
15
  The generated [privacy and data-flow catalogue](https://github.com/slicedearth/whoisleuth/blob/main/docs/privacy-data-flow-catalogue.md)
11
16
  lists the network, recipient, retention and export boundary for every command.
12
17
 
@@ -80,6 +85,11 @@ offline CLI workflow below.
80
85
  When several incident Cases share a domain, select the intended Case before
81
86
  retaining replay evidence. Case packs preserve each current Case ID separately;
82
87
  trusted and public packs exclude analyst-entered incident titles.
88
+ Every audience retains tags, decision summaries and rationale as well as domain
89
+ evidence and Case, pin and decision IDs. Public does not mean anonymous.
90
+ Browser Lookup evidence can be verified or packaged in the CLI. `brief`,
91
+ `export`, `compare`, `registry-doctor` and `source-report` instead require a
92
+ saved CLI Lookup; they do not reconstruct it from a browser export.
83
93
 
84
94
  Process selected local input:
85
95
 
@@ -136,6 +146,93 @@ their focused help. They do not use the hosted login or hosted usage controls.
136
146
  `lookup --plan` lists planned source families and disclosure targets before
137
147
  collection. `doctor` is offline unless `--network` is selected.
138
148
 
149
+ Deep Lookup reports IPv4 and IPv6 results separately, including empty answers,
150
+ NODATA, resolver-reported missing names and query failures. These outcomes remain
151
+ distinct in evidence exports; none alone establishes domain removal.
152
+
153
+ Plans are target-specific: Fast IP and ASN plans contain RDAP only; Deep IP
154
+ adds WHOIS and eligible reverse DNS, while Deep ASN adds WHOIS. Domain-only web,
155
+ TLS and selected intelligence do not apply to address or ASN targets.
156
+ Plans do not probe source health or measure remaining quota, cost, result fan-out
157
+ or exact request counts. CLI configuration profiles repeat supported settings;
158
+ optional selections made on the website do not transfer to a CLI command.
159
+ For completed website results, see [Source quality](https://github.com/slicedearth/whoisleuth/blob/main/docs/application-guide.md#lookup).
160
+
161
+ ### Message and link intake
162
+
163
+ Use `intake email message.eml --json` for MIME email and nested messages,
164
+ `intake calendar invitation.ics`, `intake qr selected.png`, or `intake text`
165
+ with text on standard input. These commands never open a destination or execute
166
+ supplied content. Output compares displayed and actual hosts, retains reported
167
+ header authentication and interprets supplied authorisation parameters without
168
+ retaining URL tokens. The source hash identifies the original bytes.
169
+ Defanged schemes and hosts are accepted, including `hxxps[:]//host[.]example`.
170
+ Path, query and fragment characters keep their supplied meaning; text scanning
171
+ removes unmatched surrounding prose brackets, while explicit links remain exact.
172
+
173
+ Add a repeatable `--reported-action`, such as `entered_device_code` or
174
+ `granted_consent`, to include account-recovery guidance based on an explicitly
175
+ reported action. The report does not infer a stolen session from domain evidence.
176
+ `--strict-exit` returns 4 for a partial review. QR review supports still PNGs;
177
+ no decoded result is not proof that a symbol is absent.
178
+ Excluded links and unsupported QR payloads make the review partial, with
179
+ category counts rather than private payload text. Bare QR hostnames are not
180
+ automatically treated as URLs.
181
+
182
+ ### Local IP-location database review
183
+
184
+ `review-evidence query.json --mmdb selected.mmdb --json --strict-exit` reads
185
+ one analyst-selected database locally. It never downloads, updates or transmits
186
+ the database. A current query uses this input:
187
+
188
+ ```json
189
+ {
190
+ "schema": "whoisleuth.local-mmdb-query",
191
+ "version": 2,
192
+ "address": "192.0.2.1",
193
+ "sourceLabel": "Selected local database",
194
+ "databaseVersion": "Analyst-declared edition",
195
+ "license": "Analyst-declared licence",
196
+ "freshnessPolicy": {
197
+ "maxAgeDays": 90,
198
+ "rationale": "Quarterly research review; recheck before operational use"
199
+ }
200
+ }
201
+ ```
202
+
203
+ Replace the reserved example address with the public IP under review. Reserved
204
+ and private addresses are unavailable in current reviews. Select and justify an
205
+ age limit appropriate to the task; the example is not a default or an accuracy
206
+ guarantee. The policy requires positive whole days and a plain-text rationale
207
+ of at most 240 characters. The command's review time is compared with the
208
+ intrinsic build time; a future build, stale database, unsupported format/type,
209
+ miss or unusable record cannot supply current location context. Malformed
210
+ fields remain unknown, with partial coverage when other useful fields survive.
211
+
212
+ Current output retains SHA-256 of the exact read bytes, byte length, intrinsic
213
+ database type, build time, binary format and IP version. Analyst source, version
214
+ and licence labels remain separate claims. Only coarse network, country, region,
215
+ city and ASN attribution can be returned; no raw record or local path is included.
216
+ The existing version-1 review envelope contains the self-described
217
+ `whoisleuth.local-mmdb-review` version-1 result. Reject future result versions
218
+ rather than treating them as current evidence.
219
+
220
+ The file must remain a regular, stable file of at most 512 MiB. After the bounded
221
+ read and digest, one disposable worker limits synchronous parsing and lookup to
222
+ ten seconds, with bounded heap/stack and an 8 KiB result. This is not a ten-second
223
+ whole-command or filesystem deadline. Default exit 0 means a report was produced;
224
+ `--strict-exit` returns 4 for unavailable or partial current reviews. Version-1
225
+ queries retain their historical JSON shape and exit behaviour, without intrinsic
226
+ metadata or freshness admission; terminal output identifies that limitation.
227
+
228
+ ### Contextual reviews
229
+
230
+ `review-evidence` also accepts [contextual review inputs](contextual-reviews.md)
231
+ for source-qualified incident sequences, retained domain history, platform-object continuity, authorised storefront
232
+ comparison and connector provenance. These workflows are offline; partial
233
+ context reviews return 4 when `--strict-exit` is selected. Browser-generated
234
+ reusable inputs use the same validators.
235
+
139
236
  ### Message-header review
140
237
 
141
238
  `mail-headers` parses only the bounded header block from a selected message file
@@ -145,6 +242,43 @@ reported order. It makes no request and does not retain address local parts,
145
242
  display names, subject, body, attachments, or raw header values in its output.
146
243
  Authentication states are header claims, not an independent DNS or
147
244
  cryptographic validation, and alignment differences can be legitimate.
245
+ Both `mail-headers` and `intake email` preserve individual authentication header
246
+ positions and service identifiers. Evaluate the receiver boundary yourself; to
247
+ record trust in one exact header, add `--trusted-auth-header 1:3` (message part 1,
248
+ header 3). Repeat for separate headers. This annotates the supplied claim without
249
+ validating it or extending trust to matching service names or nested messages.
250
+
251
+ `intake pdf selected.pdf`, `intake docx selected.docx` and `intake har selected.har`
252
+ use the same offline review as the browser. Document results include part hashes,
253
+ page references and extraction coverage. Encrypted documents require a separately
254
+ decrypted copy; no password is requested. HAR results preserve file order and
255
+ reported timings, with unavailable values distinct from zero. Neither document
256
+ resources nor recorded requests are fetched. Add `--strict-exit` for exit 4 when
257
+ coverage is partial, and `--json` for the minimised report.
258
+
259
+ `intake identity events.json --json` reads an Entra sign-in `value` array or an
260
+ Okta System Log array. To record a scoped comparison, wrap the selected provider
261
+ records in this input (timestamps require an explicit timezone):
262
+
263
+ ```json
264
+ {
265
+ "schema": "whoisleuth.identity-events.input",
266
+ "version": 1,
267
+ "provider": "entra",
268
+ "events": [],
269
+ "match": {
270
+ "applicationId": "11111111-1111-4111-8111-111111111111",
271
+ "tenantId": null,
272
+ "actorLabel": null,
273
+ "startedAt": "2026-01-01T00:00:00Z",
274
+ "endedAt": "2026-01-01T01:00:00Z"
275
+ }
276
+ }
277
+ ```
278
+
279
+ Replace `events` with the selected records. Actor labels apply only within that
280
+ file; missing fields stay unavailable. The source hash identifies the complete
281
+ selected file, including an envelope when supplied. No provider API is contacted.
148
282
 
149
283
  ## Output and automation
150
284
 
@@ -162,14 +296,20 @@ Use `--case-id` when the file contains several Cases. `open` reuses the selected
162
296
  Case; `--new-incident --title "Another incident"` creates a distinct ID for the
163
297
  same domain. It does not open a browser or collect anything.
164
298
 
165
- `pin`, `assess` and `recheck` read a selected JSON file with `--input`:
299
+ `pin`, `link`, `withdraw-link`, `assess` and `recheck` read a selected JSON file with `--input`:
166
300
 
167
301
  ```sh
168
302
  whoisleuth case pin cases.json --input pin.json --output cases.json --force
303
+ whoisleuth case link cases.json --input relationship.json --output cases.json --force
169
304
  whoisleuth case assess cases.json --input assessment.json --output cases.json --force
170
305
  whoisleuth case recheck cases.json --input recheck.json --output cases.json --force
171
306
  ```
172
307
 
308
+ For `link`, use `fromPinId`, `toPinId`, `kind` (`derived_from` or
309
+ `shared_source`) and `basis`. `withdraw-link` takes the relationship `id` and
310
+ `reason`; its original declaration remains in the file. `case show` lists the
311
+ identities and `--json` includes the full history.
312
+
173
313
  A pin describes the supplied observation, not a new collection. For example:
174
314
 
175
315
  ```json
@@ -316,6 +456,17 @@ selected evidence exports, not complete workspace backups.
316
456
 
317
457
  ### Resuming a fixed workflow
318
458
 
459
+ Inspect a paused run without executing any step or rewriting its checkpoint:
460
+
461
+ ```sh
462
+ whoisleuth workflow-run domain-triage example.test --resume run.json --preview --json
463
+ ```
464
+
465
+ The preview separates validated complete and partial outputs, failed steps that
466
+ can be retried, unresolved inputs, pending upstream outputs and fresh approval
467
+ requirements. It does not open selected input files. Preview output goes to
468
+ stdout; approval, interactive and output-file flags cannot be combined with it.
469
+
319
470
  All recipes listed by `workflow-plan --list` can run through `workflow-run`.
320
471
  Planning remains offline. Execution emits a checkpoint; terminal output shows
321
472
  retained steps, output identities, missing inputs and the next required action.
@@ -348,6 +499,12 @@ remain analyst selections. No external file is inferred or extracted automatical
348
499
  disagreement and unavailable evidence separate. Distinct labels do not verify
349
500
  independent collection; timing and shared caches can affect the comparison.
350
501
 
502
+ `domain-control-flight-recorder` retains the last comparable complete observation
503
+ across failed or partial collections. A changed recovery records the interval
504
+ between complete source observations; it does not assign the change to the
505
+ recovery time. An approved window must contain that entire interval. Historical
506
+ report versions remain readable without acquiring this newer qualification.
507
+
351
508
  For example, registry review can reuse its collection without extracting files:
352
509
 
353
510
  ```sh
@@ -385,6 +542,52 @@ an interrupted process, confirm that its owner has stopped before removing its
385
542
  abandoned lock. Locks have no automatic expiry. Shell redirection and external
386
543
  editors do not participate in this cooperative file-ownership protocol.
387
544
 
545
+ ### Managed indicator revisions
546
+
547
+ `indicator-set` uses the same file-backed revision model as Bulk. It performs
548
+ no collection or submission. A plan names the set, gives a review `basis` and
549
+ UTC `expiresAt`, and supplies `rows` plus an explicit `selectedDomains` list.
550
+ Rows use the normal Bulk eligibility rules, including available profile
551
+ context, a Suspicious or Confirmed abuse disposition and retained risk evidence.
552
+ Supply `officialDomains`, `allowlistedDomains` and `commonInfrastructureDomains`
553
+ where applicable; excluded candidates never become additions.
554
+
555
+ Example plan shape (fictional input; replace the evidence and expiry):
556
+
557
+ ```json
558
+ {
559
+ "name": "Reviewed candidates",
560
+ "basis": "Reviewed the selected page observation",
561
+ "expiresAt": "2026-10-23T00:00:00.000Z",
562
+ "selectedDomains": ["candidate.example.test"],
563
+ "rows": [{
564
+ "domain": "candidate.example.test", "availability": "registered",
565
+ "analystDisposition": "suspicious", "risk": 80,
566
+ "profileContext": { "sourceState": "ready" }
567
+ }]
568
+ }
569
+ ```
570
+
571
+ ```sh
572
+ whoisleuth indicator-set revise indicator-plan.json --json --output indicators-r1.json
573
+ whoisleuth indicator-set inspect indicators-r1.json
574
+ whoisleuth verify-artifact indicators-r1.json --strict-exit
575
+ whoisleuth indicator-set stix indicators-r1.json --output indicators-r1.stix.json
576
+ whoisleuth indicator-set misp indicators-r1.json --output indicators-r1.misp.json
577
+ ```
578
+
579
+ Later plans embed the last manifest as `previous` and explicitly list `renewIds`
580
+ or `withdrawIds`. New domains still require reviewed rows and selection.
581
+ `reintroduceDomains` permits a previously withdrawn domain to receive a new
582
+ identity; it never revives the old one. Missing domains stay unchanged.
583
+ Renewal updates the review window and note, not the original observation.
584
+
585
+ Keep previous manifests and use separate output paths. File ownership checks
586
+ reject concurrent source/destination changes and refuse replacing the input,
587
+ even with `--force`. STIX and MISP operations always emit JSON. MISP expiry is
588
+ a review deadline, not automatic deletion; exports require separate recipient
589
+ review. Digests verify content, not authorship or an unavailable earlier chain.
590
+
388
591
  ### Formats and exit behaviour
389
592
 
390
593
  Terminal text is the default. Commands expose JSON, JSONL, CSV, Markdown, HTML
@@ -414,7 +617,20 @@ diagnostics, and missing health metadata remains unmeasured.
414
617
  collection times visible. Source-output and review-generation times do not
415
618
  stand in for missing observation times. `domain-change` and
416
619
  `domain-change-packet` retain authority, resolver and certificate observation
417
- times; previously published version-2 packets remain verifiable.
620
+ times; previously published version-2 and version-3 packets remain verifiable.
621
+ Current DNS convergence and domain-change inputs use version 2. Each snapshot
622
+ declares `queries`, for example
623
+ `[{"owner":"example.test","type":"MX","state":"observed"}]`.
624
+ An `observed` query with no matching records means an empty answer; `partial`
625
+ and `unavailable` queries do not. Omitted queries are not queried. Version-1
626
+ inputs remain readable but establish query scope only for supplied records.
627
+ An expected empty set alone cannot establish an observed empty answer.
628
+
629
+ Domain-control review version 3 keeps renewal reminders separate from observed
630
+ configuration: `due` and `not_due` describe the chosen review date, not DNS or
631
+ registration changes. Incomplete evidence remains `partial` even when a reminder
632
+ is due. Terminal and JUnit summaries report due reminders separately. Previous
633
+ review and monitor checkpoints retain their original versioned interpretation.
418
634
 
419
635
  ## Exit codes
420
636
 
@@ -426,6 +642,11 @@ For scripts, use `whoisleuth verify-artifact evidence.json --json --strict-exit`
426
642
  An incomplete verification returns **4** with that option. Without it, **0**
427
643
  means a report was produced, not that the artefact passed verification; inspect
428
644
  the report's state and checks.
645
+ Unencrypted workspace archives retain mutable root metadata outside their
646
+ section digests, so they return 4 under `--strict-exit` even when all supported
647
+ section checks pass. Structure-only exports can pass their narrower contract;
648
+ strict exit is not a ranking of cryptographic assurance. `interchange-report`
649
+ separately describes import/export support and is not the full verifier inventory.
429
650
 
430
651
  ## Command details
431
652