@slicedearth/whoisleuth-cli 2.4.0 → 2.6.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 (340) hide show
  1. package/DISCLOSURE +1 -1
  2. package/NOTICE +2 -2
  3. package/cli/argument-values.mjs +55 -0
  4. package/cli/arguments.mjs +24 -470
  5. package/cli/artifact-validation/case-response.mjs +67 -9
  6. package/cli/artifact-validation/lookup-evidence.mjs +18 -4
  7. package/cli/artifact-validation/signed-review.mjs +61 -64
  8. package/cli/artifact-verify.mjs +11 -2
  9. package/cli/assurance-arguments.mjs +56 -0
  10. package/cli/assurance-command-definitions.mjs +179 -0
  11. package/cli/assurance-command-runner.mjs +1 -1
  12. package/cli/bounded-json.mjs +1 -1
  13. package/cli/bulk-checkpoint.mjs +1 -1
  14. package/cli/bulk-command-runner.mjs +6 -8
  15. package/cli/bulk.mjs +2 -3
  16. package/cli/case-command.mjs +99 -6
  17. package/cli/case-pack.mjs +19 -4
  18. package/cli/ci-report.mjs +6 -3
  19. package/cli/collection-arguments.mjs +123 -0
  20. package/cli/collection-command-definitions.mjs +330 -0
  21. package/cli/collection-preflight.mjs +8 -1
  22. package/cli/command-argument-grammar.mjs +8 -210
  23. package/cli/command-definition.mjs +330 -0
  24. package/cli/command-reference.mjs +55 -1388
  25. package/cli/command-seeds.mjs +18 -0
  26. package/cli/compare.mjs +1 -1
  27. package/cli/config-profile.mjs +1 -1
  28. package/cli/ct-event-intake.mjs +4 -4
  29. package/cli/discover.mjs +2 -2
  30. package/cli/discovery-observation-snapshot.mjs +2 -2
  31. package/cli/discovery-scan-command-runner.mjs +7 -9
  32. package/cli/discovery-scan.mjs +1 -1
  33. package/cli/discovery-snapshot.mjs +2 -2
  34. package/cli/discovery-workflow.mjs +4 -4
  35. package/cli/domain-control-monitor.mjs +6 -6
  36. package/cli/domain-control-observations.mjs +2 -1
  37. package/cli/domain-feed.mjs +61 -0
  38. package/cli/errors.mjs +5 -6
  39. package/cli/evidence-arguments.mjs +46 -0
  40. package/cli/evidence-command-definitions.mjs +69 -0
  41. package/cli/evidence-command-runner.mjs +2 -3
  42. package/cli/evidence-signing.mjs +3 -2
  43. package/cli/external-observation-mapping.mjs +2 -2
  44. package/cli/formatters/json.mjs +1 -13
  45. package/cli/formatters/terminal-command-formats.mjs +6 -0
  46. package/cli/formatters/terminal-lookup.mjs +6 -0
  47. package/cli/history-arguments.mjs +39 -0
  48. package/cli/history-command-definitions.mjs +88 -0
  49. package/cli/indicator-set-command.mjs +32 -0
  50. package/cli/inline-command-families.mjs +6 -47
  51. package/cli/inline-command-runner.mjs +4 -31
  52. package/cli/input.mjs +2 -2
  53. package/cli/intake-command.mjs +147 -0
  54. package/cli/interchange-report.mjs +4 -2
  55. package/cli/investigation-artifacts.mjs +3 -4
  56. package/cli/investigation-package-review.mjs +2 -0
  57. package/cli/investigation-plan.mjs +32 -154
  58. package/cli/investigation-recipes.mjs +177 -0
  59. package/cli/investigation-run.mjs +78 -12
  60. package/cli/local-mmdb-review.mjs +220 -17
  61. package/cli/lookalike-calibration.mjs +1 -1
  62. package/cli/lookup-brief.mjs +2 -2
  63. package/cli/lookup-command-runner.mjs +19 -35
  64. package/cli/lookup-diff.mjs +10 -9
  65. package/cli/lookup-plan.mjs +10 -14
  66. package/cli/lookup-reconcile.mjs +6 -5
  67. package/cli/lookup-timeline.mjs +7 -6
  68. package/cli/mail-header-review.mjs +24 -247
  69. package/cli/mail-review.mjs +9 -7
  70. package/cli/network-arguments.mjs +51 -0
  71. package/cli/network-command-definitions.mjs +130 -0
  72. package/cli/network-command-runner.mjs +43 -31
  73. package/cli/offline-evidence-review.mjs +85 -6
  74. package/cli/posture.mjs +1 -1
  75. package/cli/registry-fixture-scaffold.mjs +1 -1
  76. package/cli/retained-artifact-diff.mjs +8 -7
  77. package/cli/review-arguments.mjs +132 -0
  78. package/cli/review-command-definitions.mjs +279 -0
  79. package/cli/review-command-runner.mjs +72 -159
  80. package/cli/risk-calibration.mjs +14 -12
  81. package/cli/runner.mjs +20 -32
  82. package/cli/saved-lookup.mjs +2 -2
  83. package/cli/selected-input-worker.mjs +73 -0
  84. package/cli/sharing-review.mjs +5 -64
  85. package/cli/source-reliability.mjs +2 -2
  86. package/cli/support-arguments.mjs +49 -0
  87. package/cli/support-command-definitions.mjs +209 -0
  88. package/cli/support-command-runner.mjs +4 -4
  89. package/cli/watchlist-review.mjs +43 -0
  90. package/cli/workflow-arguments.mjs +100 -0
  91. package/cli/workflow-command-definitions.mjs +103 -0
  92. package/cli/workflow-command-runner.mjs +40 -1
  93. package/docs/cli-reference.md +27 -0
  94. package/docs/cli.md +322 -4
  95. package/docs/contextual-reviews.md +178 -0
  96. package/docs/infrastructure-observations.md +126 -0
  97. package/docs/offline-intake-context.md +213 -0
  98. package/lib/analyst-taxonomy.mjs +2 -40
  99. package/lib/application-version.mjs +1 -1
  100. package/lib/availability.mjs +13 -789
  101. package/lib/bounded-contract-normalizers.mjs +4 -0
  102. package/lib/bounded-json.mjs +2 -330
  103. package/lib/browser-library-profile.mjs +62 -84
  104. package/lib/candidate-provenance-bounds.mjs +2 -0
  105. package/lib/classify.mjs +1 -1
  106. package/lib/credential-form-attribution.mjs +136 -0
  107. package/lib/credential-surface-profile.mjs +6 -2
  108. package/lib/ct-query.mjs +2 -27
  109. package/lib/ct-response-bounds.mjs +2 -9
  110. package/lib/ct-search.mjs +83 -47
  111. package/lib/dmarc-discovery.mjs +62 -0
  112. package/lib/dns-convergence-review.mjs +21 -8
  113. package/lib/dns-delegation-health.mjs +35 -15
  114. package/lib/dns-inheritance-review.mjs +53 -63
  115. package/lib/dns-intelligence.mjs +27 -11
  116. package/lib/dns-query-coverage.mjs +40 -0
  117. package/lib/domain-assurance.mjs +1 -1
  118. package/lib/domain-change-packet.mjs +12 -13
  119. package/lib/domain-change-review.mjs +32 -14
  120. package/lib/domain-control-flight-recorder.mjs +103 -27
  121. package/lib/domain-control-manifest.mjs +25 -17
  122. package/lib/domain-posture-analysis.mjs +75 -7
  123. package/lib/domain-posture-parsers.mjs +9 -0
  124. package/lib/domain-posture.mjs +45 -25
  125. package/lib/domain-registration.mjs +392 -0
  126. package/lib/evidence-export-privacy.mjs +27 -17
  127. package/lib/evidence-export.mjs +3 -3
  128. package/lib/evidence-report.mjs +5 -1
  129. package/lib/external-intelligence-risk.mjs +2 -124
  130. package/lib/favicon.mjs +13 -4
  131. package/lib/generated/cisa-kev-catalog.mjs +41 -3
  132. package/lib/generated/registrar-standing-catalogue.mjs +18 -10
  133. package/lib/generated/retire-browser-catalog.mjs +332 -19
  134. package/lib/generated/{unicode-confusables-17.mjs → unicode-confusables.mjs} +33 -33
  135. package/lib/html-signals.mjs +13 -2
  136. package/lib/http-evidence-bounds.mjs +2 -8
  137. package/lib/http-intelligence.mjs +13 -1
  138. package/lib/idn-confusable-policy.mjs +6 -4
  139. package/lib/idn-confusables.mjs +1 -1
  140. package/lib/interchange-fidelity-registry.mjs +10 -0
  141. package/lib/lookup-child-profile-contract.mjs +28 -11
  142. package/lib/lookup-contract-primitives.mjs +1 -1
  143. package/lib/lookup-diagnostics.mjs +1 -23
  144. package/lib/lookup-network-evidence-bounds.mjs +1 -10
  145. package/lib/lookup-progress.mjs +50 -1
  146. package/lib/lookup-response-contract.mjs +70 -40
  147. package/lib/lookup-response.mjs +5 -15
  148. package/lib/lookup-source-progress.mjs +8 -22
  149. package/lib/lookup.mjs +1 -1
  150. package/lib/operation-budget.mjs +4 -0
  151. package/lib/opportunity-scoring.mjs +2 -107
  152. package/lib/outbound-identity.mjs +1 -1
  153. package/lib/page-fingerprints.mjs +18 -1
  154. package/lib/perceptual-hash-comparison.mjs +2 -34
  155. package/lib/perceptual-hash.mjs +36 -10
  156. package/lib/portable-generator.mjs +2 -29
  157. package/lib/project-metadata.mjs +2 -8
  158. package/lib/rdap-client.mjs +11 -7
  159. package/lib/registrable-domain.mjs +2 -56
  160. package/lib/registrar-standing-contract.mjs +1 -1
  161. package/lib/registrar-standing.mjs +9 -13
  162. package/lib/registry-capabilities.mjs +2 -2
  163. package/lib/registry-capability-catalogue.mjs +27 -19
  164. package/lib/risk-calibration-summary.mjs +2 -352
  165. package/lib/risk-scoring.mjs +2 -345
  166. package/lib/scoring-evidence-quality.mjs +2 -107
  167. package/lib/semantic-version.mjs +2 -54
  168. package/lib/service-binding-dns.mjs +46 -2
  169. package/lib/smtp-transport-review.mjs +48 -19
  170. package/lib/sslbl-certificates.generated.mjs +142 -139
  171. package/lib/static-html-analysis.mjs +32 -4
  172. package/lib/structured-data-identity.mjs +1 -1
  173. package/lib/technology-evidence-role.mjs +1 -51
  174. package/lib/threat-intelligence-manifests.mjs +1 -1
  175. package/lib/threat-intelligence-runtime.mjs +1 -1
  176. package/lib/threat-intelligence-targets.mjs +1 -1
  177. package/lib/threat-intelligence-types.mjs +2 -75
  178. package/lib/tls-intelligence.mjs +30 -5
  179. package/lib/typosquat-generator.mjs +11 -2
  180. package/lib/website-enrichment.mjs +404 -0
  181. package/lib/whois-authority.mjs +43 -5
  182. package/lib/whois-chain.mjs +12 -3
  183. package/lib/whois-common-formats.mjs +3 -1
  184. package/lib/whois-contacts.mjs +13 -2
  185. package/lib/whois-normalization.mjs +3 -1
  186. package/lib/whois-parser.mjs +5 -3
  187. package/package.json +15 -14
  188. package/packages/analysis/analyst-taxonomy.mjs +40 -0
  189. package/packages/analysis/bounded-json.mjs +330 -0
  190. package/packages/analysis/candidate-provenance-bounds.mjs +4 -0
  191. package/packages/analysis/cli-argument-grammar.mjs +216 -0
  192. package/packages/analysis/ct-query.mjs +27 -0
  193. package/packages/analysis/ct-response-bounds.mjs +9 -0
  194. package/packages/analysis/external-intelligence-risk.mjs +124 -0
  195. package/packages/analysis/http-evidence-bounds.mjs +8 -0
  196. package/packages/analysis/lookup-network-evidence-bounds.mjs +10 -0
  197. package/packages/analysis/opportunity-scoring.mjs +107 -0
  198. package/packages/analysis/perceptual-hash-comparison.mjs +34 -0
  199. package/packages/analysis/portable-generator.mjs +29 -0
  200. package/packages/analysis/project-metadata.mjs +8 -0
  201. package/packages/analysis/registrable-domain.mjs +56 -0
  202. package/packages/analysis/risk-calibration-summary.mjs +352 -0
  203. package/packages/analysis/risk-scoring.mjs +348 -0
  204. package/packages/analysis/scoring-evidence-quality.mjs +107 -0
  205. package/packages/analysis/semantic-version.mjs +54 -0
  206. package/packages/analysis/threat-intelligence-types.mjs +75 -0
  207. package/packages/cases/case-containment-handoff.mjs +195 -0
  208. package/packages/cases/case-evidence-links.mjs +151 -0
  209. package/packages/cases/case-evidence-model.mjs +89 -12
  210. package/packages/cases/case-evidence-quality.mjs +5 -0
  211. package/packages/cases/case-export-input.mjs +7 -5
  212. package/packages/cases/case-incident-context.mjs +52 -0
  213. package/packages/cases/case-markdown.mjs +11 -0
  214. package/packages/cases/case-migration-model.mjs +40 -24
  215. package/packages/cases/case-object-coverage.mjs +33 -0
  216. package/packages/cases/case-packet-correction.mjs +106 -0
  217. package/packages/cases/case-recheck-model.mjs +124 -36
  218. package/packages/cases/case-record-contracts.mjs +1 -1
  219. package/packages/cases/case-record-core.mjs +1 -1
  220. package/packages/cases/case-record-operations.mjs +111 -118
  221. package/packages/cases/case-record-projection.mjs +23 -4
  222. package/packages/cases/case-record-version-input.mjs +41 -0
  223. package/packages/cases/case-report.mjs +93 -27
  224. package/packages/cases/case-requested-evidence.mjs +146 -0
  225. package/packages/cases/case-response-actions.mjs +159 -1
  226. package/packages/cases/case-response-model.mjs +16 -1
  227. package/packages/cases/case-response-object.mjs +119 -0
  228. package/packages/cases/case-response-outcomes.mjs +108 -20
  229. package/packages/cases/case-response-packet-render.mjs +35 -12
  230. package/packages/cases/case-response-packet.mjs +55 -13
  231. package/packages/cases/case-response-records.mjs +0 -1
  232. package/packages/cases/case-response-review-inputs.mjs +60 -8
  233. package/packages/cases/case-storage-model.mjs +1 -1
  234. package/packages/cases/case-workflow-metadata.mjs +217 -0
  235. package/packages/cases/case-workflow-migration.mjs +43 -0
  236. package/packages/cases/http-summary.mjs +6 -1
  237. package/packages/comparison/favicon-similarity.mjs +1 -1
  238. package/packages/comparison/page-similarity.mjs +1 -1
  239. package/packages/comparison/relationship-evidence.mjs +21 -9
  240. package/packages/comparison/relationship-provenance.mjs +7 -1
  241. package/packages/contracts/analyst-interchange.mjs +35 -15
  242. package/packages/contracts/candidate-watch-review.mjs +54 -0
  243. package/packages/contracts/case-portability.mjs +360 -49
  244. package/packages/contracts/cli-command-semantics.mjs +5 -0
  245. package/packages/contracts/cli-grammar.mjs +6 -0
  246. package/packages/contracts/cli-lookup-brief.mjs +2 -0
  247. package/packages/contracts/context-review.mjs +25 -0
  248. package/packages/contracts/document-intake.mjs +11 -0
  249. package/packages/contracts/domain-control-flight-recorder.mjs +30 -14
  250. package/packages/contracts/domain-control-manifest.mjs +1 -1
  251. package/packages/contracts/domain-control-monitor.mjs +13 -2
  252. package/packages/contracts/domain-control-review.mjs +36 -15
  253. package/packages/contracts/external-observation-interchange.mjs +96 -28
  254. package/packages/contracts/har-review.mjs +2 -0
  255. package/packages/contracts/identity-events.mjs +4 -0
  256. package/packages/contracts/investigation-run.mjs +2 -0
  257. package/packages/contracts/ip-address.mjs +10 -0
  258. package/packages/contracts/lookup-evidence.mjs +3 -2
  259. package/packages/contracts/mail-authentication.mjs +5 -0
  260. package/packages/contracts/message-intake.mjs +37 -0
  261. package/packages/contracts/offline-comparison.mjs +4 -1
  262. package/packages/contracts/passive-mail.mjs +10 -0
  263. package/packages/contracts/risk-calibration.mjs +44 -29
  264. package/packages/contracts/schema-lifecycle.mjs +3 -1
  265. package/packages/contracts/web-capture.mjs +2 -2
  266. package/packages/contracts/workspace-portability.mjs +144 -28
  267. package/packages/evidence/collection-quality.mjs +67 -0
  268. package/packages/evidence/credential-form-attribution.mjs +60 -0
  269. package/packages/evidence/dns-query-outcome.mjs +72 -0
  270. package/packages/evidence/email-recipient.mjs +18 -0
  271. package/packages/evidence/image-regions.mjs +15 -0
  272. package/packages/evidence/lookup-target.mjs +5 -1
  273. package/packages/evidence/observation.mjs +2 -0
  274. package/packages/evidence/png-pixels.mjs +117 -0
  275. package/packages/evidence/public-address-policy.mjs +83 -0
  276. package/packages/evidence/record-identity.mjs +60 -16
  277. package/packages/evidence/technology-evidence-role.mjs +51 -0
  278. package/packages/interchange/defensive-indicator-export.mjs +264 -0
  279. package/packages/interchange/external-findings-import.mjs +36 -29
  280. package/packages/interchange/json-output.mjs +14 -0
  281. package/packages/interchange/managed-indicator-export.mjs +36 -0
  282. package/packages/interchange/managed-indicator-set.mjs +223 -0
  283. package/packages/interchange/misp-indicator-export.mjs +93 -0
  284. package/packages/interchange/sharing-policy.mjs +56 -0
  285. package/packages/interchange/stix-indicator-export.mjs +129 -0
  286. package/packages/interchange/web-capture-import.mjs +22 -15
  287. package/packages/investigation/acquisition-decision-packet.mjs +79 -1
  288. package/packages/investigation/bulk-mail-exposure.mjs +7 -13
  289. package/packages/investigation/capture-coverage.mjs +57 -0
  290. package/packages/investigation/connector-provenance-review.mjs +100 -0
  291. package/packages/investigation/context-review.mjs +35 -0
  292. package/packages/investigation/document-intake.mjs +87 -0
  293. package/packages/investigation/docx-intake.mjs +133 -0
  294. package/packages/investigation/domain-history-review.mjs +132 -0
  295. package/packages/investigation/domain-transition-review.mjs +49 -0
  296. package/packages/investigation/evidence-coverage-ledger.mjs +9 -1
  297. package/packages/investigation/har-intake.mjs +68 -0
  298. package/packages/investigation/identity-event-intake.mjs +131 -0
  299. package/packages/investigation/identity-incident-review.mjs +39 -0
  300. package/packages/investigation/incident-sequence-review.mjs +58 -0
  301. package/packages/investigation/infrastructure-observation.mjs +234 -0
  302. package/packages/investigation/intake-context.mjs +284 -0
  303. package/packages/investigation/intake-indicators.mjs +116 -0
  304. package/packages/investigation/intake-phones.mjs +155 -0
  305. package/packages/investigation/intake-report.mjs +20 -0
  306. package/packages/investigation/investigation-manifest.mjs +35 -9
  307. package/packages/investigation/link-intake.mjs +209 -0
  308. package/packages/investigation/lookup-asset-graph.mjs +1 -1
  309. package/packages/investigation/mail-authentication-review.mjs +222 -0
  310. package/packages/investigation/mail-header-identity.mjs +209 -0
  311. package/packages/investigation/message-intake.mjs +235 -0
  312. package/packages/investigation/page-behaviour.mjs +113 -0
  313. package/packages/investigation/pdf-intake.mjs +155 -0
  314. package/packages/investigation/platform-continuity-review.mjs +60 -0
  315. package/packages/investigation/qr-intake.mjs +56 -0
  316. package/packages/investigation/requested-action-hints.mjs +11 -0
  317. package/packages/investigation/selected-input-review.mjs +17 -0
  318. package/packages/investigation/storefront-review.mjs +70 -0
  319. package/packages/monitoring/domain-feed.mjs +321 -0
  320. package/packages/workspace/brand-candidate-workflow.mjs +351 -0
  321. package/packages/workspace/brand-keyword-campaign.mjs +231 -0
  322. package/packages/workspace/brand-profile-model.mjs +48 -4
  323. package/packages/workspace/bulk-review-model.mjs +28 -26
  324. package/packages/workspace/bulk-session-model.mjs +27 -13
  325. package/packages/workspace/campaign-model.mjs +1 -1
  326. package/packages/workspace/candidate-watch-handoff.mjs +195 -0
  327. package/packages/workspace/detection-rule-model.mjs +11 -3
  328. package/packages/workspace/hostile-input.mjs +1 -1
  329. package/packages/workspace/investigation-guide.mjs +2 -2
  330. package/packages/workspace/investigation-template-model.mjs +26 -2
  331. package/packages/workspace/page-baseline.mjs +2 -2
  332. package/packages/workspace/relationship-observation-model.mjs +17 -13
  333. package/packages/workspace/shortlist-model.mjs +24 -7
  334. package/packages/workspace/watchlist-history.mjs +59 -16
  335. package/packages/workspace/watchlist-store.mjs +152 -11
  336. package/packages/workspace/website-snapshot-model.mjs +28 -3
  337. package/packages/workspace/workspace-archive-crypto.mjs +1 -1
  338. package/packages/workspace/workspace-archive.mjs +5 -4
  339. package/third-party-notices.txt +549 -16
  340. package/lib/hostname.mjs +0 -2
@@ -1,6 +1,6 @@
1
1
  import { REGISTRY_CAPABILITIES_VERSION, registryCapabilityFor } from "../lib/registry-capabilities.mjs";
2
- import { buildRiskCalibrationSummaryReport } from "../lib/risk-calibration-summary.mjs";
3
- import { explainRiskScore, explainRiskScoreV7, RISK_MODEL_VERSION, RISK_REVIEW_THRESHOLD } from "../lib/risk-scoring.mjs";
2
+ import { buildRiskCalibrationSummaryReport } from "../packages/analysis/risk-calibration-summary.mjs";
3
+ import { explainRiskScore, explainRiskScoreV8, RISK_MODEL_VERSION, RISK_REVIEW_THRESHOLD } from "../packages/analysis/risk-scoring.mjs";
4
4
  import { buildCliCommandCatalogue, formatCliCommandCatalogue, selectCliCommands } from "./command-catalogue.mjs";
5
5
  import { CLI_COMMAND_REGISTRY, CLI_COMMANDS, COMMAND_COLLECTION, COMMAND_DETAILS, COMMAND_USAGE, } from "./command-reference.mjs";
6
6
  import { buildShellCompletion } from "./completion.mjs";
@@ -169,8 +169,8 @@ async function runRiskCalibrationCommand(args, dependencies, context) {
169
169
  modelVersion: dependencies.riskModelVersion || RISK_MODEL_VERSION,
170
170
  reviewThreshold: dependencies.riskReviewThreshold || RISK_REVIEW_THRESHOLD,
171
171
  ...(!dependencies.explainRiskScore ? {
172
- previousModelVersion: 7,
173
- explainPreviousRiskScore: explainRiskScoreV7,
172
+ previousModelVersion: 8,
173
+ explainPreviousRiskScore: explainRiskScoreV8,
174
174
  } : {}),
175
175
  });
176
176
  if (!args.quiet) {
@@ -0,0 +1,43 @@
1
+ import { parseBoundedJson } from "../packages/analysis/bounded-json.mjs";
2
+ import { applyCandidateWatchHandoff, planCandidateWatchHandoff, } from "../packages/workspace/candidate-watch-handoff.mjs";
3
+ import { buildWatchlistExport, mergeWatchlistStores, } from "../packages/workspace/watchlist-store.mjs";
4
+ import { MAX_WATCHLIST_IMPORT_BYTES } from "../packages/contracts/workspace-portability.mjs";
5
+ import { ordinaryWorkspaceRecord } from "../packages/workspace/hostile-input.mjs";
6
+ import { CANDIDATE_WATCH_INPUT_SCHEMA, CANDIDATE_WATCH_INPUT_VERSION, } from "../packages/contracts/candidate-watch-review.mjs";
7
+ export function parseCandidateWatchInput(input) {
8
+ const value = typeof input === 'string'
9
+ ? parseBoundedJson(input, {
10
+ maximumBytes: MAX_WATCHLIST_IMPORT_BYTES,
11
+ label: 'Candidate watch selection',
12
+ })
13
+ : input;
14
+ const raw = ordinaryWorkspaceRecord(value, 'Candidate watch selection');
15
+ if (!raw ||
16
+ raw.schema !== CANDIDATE_WATCH_INPUT_SCHEMA ||
17
+ raw.version !== CANDIDATE_WATCH_INPUT_VERSION ||
18
+ Object.keys(raw).some((key) => !['schema', 'version', 'watchlists', 'selection'].includes(key)))
19
+ throw new TypeError('Use candidate-watch-input schema 1 with watchlists and an explicit selection.');
20
+ const selection = ordinaryWorkspaceRecord(raw.selection, 'Candidate watch selection');
21
+ if (!selection ||
22
+ Object.keys(selection).some((key) => ![
23
+ 'name',
24
+ 'candidates',
25
+ 'brandProfileId',
26
+ 'priority',
27
+ 'reason',
28
+ 'reviewDueAt',
29
+ 'replaceExistingContext',
30
+ ].includes(key)))
31
+ throw new TypeError('The candidate selection contains undeclared fields.');
32
+ const local = raw.watchlists === null ? {} : mergeWatchlistStores({}, raw.watchlists).watchlists;
33
+ planCandidateWatchHandoff(local, selection);
34
+ return raw;
35
+ }
36
+ export function reviewCandidateWatchInput(input, operation, now) {
37
+ const raw = parseCandidateWatchInput(input), selection = raw.selection;
38
+ const local = raw.watchlists === null ? {} : mergeWatchlistStores({}, raw.watchlists).watchlists;
39
+ if (operation === 'plan')
40
+ return planCandidateWatchHandoff(local, selection);
41
+ const result = applyCandidateWatchHandoff(local, selection, now);
42
+ return buildWatchlistExport(result.watchlists, now);
43
+ }
@@ -0,0 +1,100 @@
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 { parseDomainFeedSelectors } from "./domain-feed.mjs";
7
+ import { terminalOptions, jsonOutput, failPolicies, parseOutput, } from "./argument-values.mjs";
8
+ function parseDomainFeedArguments(parsed) {
9
+ const operation = parsed.positionalValue('operation');
10
+ const contextSource = parsed.positionalValue('context');
11
+ if ((operation === 'watch-input') !== Boolean(contextSource))
12
+ throw new CliUsageError('watch-input requires one existing candidate-watch-input context file; review does not accept a context file.');
13
+ return { action: 'domain-feed', operation, feedId: parsed.positionalValue('feed'), source: parsed.positionalValue('source'), contextSource,
14
+ selection: parseDomainFeedSelectors(parsed.optionValues('--select')), output: jsonOutput(parsed), ...terminalOptions(parsed) };
15
+ }
16
+ function parseWatchlistReviewArguments(parsed) {
17
+ return { action: 'watchlist-review', operation: parsed.positionalValue('operation'), source: parsed.positionalValue('source'), output: jsonOutput(parsed), ...terminalOptions(parsed) };
18
+ }
19
+ function parseMonitorOnceArguments(parsed) {
20
+ const selectedFailPolicies = failPolicies(parsed, 'monitor-once');
21
+ return {
22
+ action: 'monitor-once',
23
+ source: parsed.positionalValue('source'),
24
+ previousSource: parsed.optionValue('--previous'),
25
+ output: parseOutput(parsed, WORKFLOW_COMMAND_DEFINITIONS['monitor-once']),
26
+ limit: parsed.integerOption('--limit') ?? commandDefaultNumber('monitor-once', '--limit'),
27
+ concurrency: parsed.integerOption('--concurrency') ??
28
+ commandDefaultNumber('monitor-once', '--concurrency'),
29
+ ...terminalOptions(parsed),
30
+ ...(selectedFailPolicies ? { failOn: selectedFailPolicies } : {}),
31
+ };
32
+ }
33
+ function parseWorkflowPlanArguments(parsed) {
34
+ const options = { output: jsonOutput(parsed), ...terminalOptions(parsed) };
35
+ if (parsed.hasOption('--list')) {
36
+ if (parsed.allPositionals.length > 0)
37
+ throw new CliUsageError('workflow-plan discovery options do not accept a subject.');
38
+ return { action: 'workflow-plan', discovery: 'list', ...options };
39
+ }
40
+ if (parsed.hasOption('--explain')) {
41
+ if (parsed.allPositionals.length > 0)
42
+ throw new CliUsageError('workflow-plan discovery options do not accept a subject.');
43
+ return {
44
+ action: 'workflow-plan',
45
+ discovery: 'explain',
46
+ recipe: parsed.optionValue('--explain'),
47
+ ...options,
48
+ };
49
+ }
50
+ if (parsed.allPositionals.length !== 2) {
51
+ throw new CliUsageError('workflow-plan requires one fixed recipe and one subject, --list, or --explain <recipe>.');
52
+ }
53
+ return {
54
+ action: 'workflow-plan',
55
+ recipe: parsed.positionalValue('recipe'),
56
+ subject: parsed.positionalValue('subject'),
57
+ ...options,
58
+ };
59
+ }
60
+ function parseWorkflowRunArguments(parsed) {
61
+ const artifactBindings = parsed.optionValues('--use-artifact').map((value) => {
62
+ const match = /^([a-z0-9]+(?:-[a-z0-9]+)*):([1-9][0-9]?)=([a-z0-9]+(?:-[a-z0-9]+)*)$/u.exec(value);
63
+ if (!match)
64
+ throw new CliUsageError('--use-artifact requires <step-id>:<input-number>=<earlier-step-id>.');
65
+ return Object.freeze({ stepId: match[1], input: Number(match[2]), sourceStepId: match[3] });
66
+ });
67
+ const selections = parsed.optionValues('--select').map((value) => {
68
+ const separator = value.indexOf('=');
69
+ const stepId = separator === -1 ? '' : value.slice(0, separator);
70
+ const selection = separator === -1 ? '' : value.slice(separator + 1);
71
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(stepId) || !selection.trim()) {
72
+ throw new CliUsageError('--select requires <step-id>=<path-or-value>.');
73
+ }
74
+ if (selection.startsWith('-')) {
75
+ throw new CliUsageError('--select values cannot start with a hyphen; prefix a file path with ./ when needed.');
76
+ }
77
+ return Object.freeze({ stepId, value: selection });
78
+ });
79
+ return {
80
+ action: 'workflow-run',
81
+ recipe: parsed.positionalValue('recipe'),
82
+ subject: parsed.positionalValue('subject'),
83
+ resumeSource: parsed.optionValue('--resume'),
84
+ selections: Object.freeze(selections),
85
+ artifactBindings: Object.freeze(artifactBindings),
86
+ confirmedReviews: Object.freeze(parsed.optionValues('--confirm-review')),
87
+ approveNetwork: parsed.hasOption('--approve-network'),
88
+ interactive: parsed.hasOption('--interactive'),
89
+ ...(parsed.hasOption('--preview') ? { preview: true } : {}),
90
+ output: jsonOutput(parsed),
91
+ ...terminalOptions(parsed),
92
+ };
93
+ }
94
+ export const WORKFLOW_ARGUMENT_PARSERS = Object.freeze({
95
+ 'domain-feed': parseDomainFeedArguments,
96
+ 'watchlist-review': parseWatchlistReviewArguments,
97
+ 'monitor-once': parseMonitorOnceArguments,
98
+ 'workflow-plan': parseWorkflowPlanArguments,
99
+ 'workflow-run': parseWorkflowRunArguments,
100
+ });
@@ -0,0 +1,103 @@
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, optionDefinition } from "./command-definition.mjs";
3
+ import { DOMAIN_FEED_CATALOGUE } from "../packages/monitoring/domain-feed.mjs";
4
+ export const WORKFLOW_COMMAND_DEFINITIONS = Object.freeze({
5
+ 'domain-feed': commandSeed({
6
+ reference: { description: 'Review exact hosts or literal terms in a selected local plain-domain feed.',
7
+ example: 'whoisleuth domain-feed review tif-mini feed.txt --select host:candidate.example --json',
8
+ boundary: 'Always available offline. Reads one explicit local regular file; never downloads a feed, performs a Lookup, changes scores, retains candidates or enables a schedule. Streams at most 256 MiB and 10 million physical rows with 1,024-byte lines and a ten-minute deadline; retains at most 200 matched hosts. Repeat --select host:<hostname>, term:<literal> or exclude:<literal> for at most 200 exact hosts, 20 positive and 20 negative literal terms of 3–80 characters. A negative match vetoes even an exact host before the match bound; at least one positive term or exact host is required. Matching uses canonical ASCII/punycode substrings, never token boundaries, parent equivalence, typo/confusable expansion or regular expressions. Review emits the full raw-byte digest, separate declared file/local clocks, unknown per-host clocks and omissions. watch-input also requires a candidate-watch-input v1 context file with an empty candidates array, and emits that existing format for a separate watchlist-review plan/export. Inclusion is not a maliciousness, activity, ownership or availability verdict.' },
9
+ collection: { scope: 'Local raw file: 256 MiB, 10 million physical rows, 1,024 bytes per line, ten minutes; 200 retained hosts, 20 positive and 20 negative literal terms, no requests.' },
10
+ summary: 'Review a selected local plain-domain feed', options: ['--select', '--json', '--quiet', '--no-color'],
11
+ optionOverrides: { '--select': optionDefinition('text', 'Select host:<exact-hostname>, term:<literal-text> or exclude:<literal-text>; repeat for separate explicit selectors.', { occurrence: 'repeatable', acceptsOptionLikeValue: true }) },
12
+ positionals: Object.freeze([positional('operation', 'enum', 1, 1, ['review', 'watch-input']),
13
+ positional('feed', 'enum', 1, 1, DOMAIN_FEED_CATALOGUE.map(feed => feed.id)), positional('source', 'file', 1, 1), positional('context', 'file', 0, 1)]),
14
+ constraints: Object.freeze([constraint({ kind: 'required', options: ['--select'] })]),
15
+ handlerOwner: 'workflow', networkEffect: 'offline', common: false,
16
+ schemaIdentifiers: Object.freeze(['whoisleuth.candidate-watch-input']), primaryArtefacts: Object.freeze(['Bounded feed inclusion review', 'Explicit candidate watch selection']),
17
+ planSupport: true, additionalOutputFormats: Object.freeze([]), bootstrapProfile: 'allowed',
18
+ }),
19
+ 'watchlist-review': commandSeed({
20
+ reference: { description: 'Preview or export an explicit candidate selection to a local watchlist.', example: 'whoisleuth watchlist-review plan selection.json --json',
21
+ boundary: 'Reads bounded local JSON only. Plan shows exact domains, duplicates, per-domain outcomes, destination and review priority with zero additional requests. Export emits Watchlist schema 5 without creating a scan or baseline. Neither mode enables collection or scheduling.' },
22
+ collection: { scope: 'At most 2,000 selected candidate domains; no target requests.' },
23
+ summary: 'Review a local candidate watchlist handoff', options: ['--json', '--quiet', '--no-color'],
24
+ positionals: Object.freeze([positional('operation', 'enum', 1, 1, ['plan', 'export']), positional('source', 'file', 0, 1, [], 'argv_or_stdin')]),
25
+ constraints: Object.freeze([]), handlerOwner: 'workflow', networkEffect: 'offline', common: false,
26
+ schemaIdentifiers: Object.freeze(['whoisleuth.candidate-watch-input', 'whoisleuth.watchlists']), primaryArtefacts: Object.freeze(['Exact candidate handoff plan', 'Local watchlist export']),
27
+ planSupport: true, additionalOutputFormats: Object.freeze([]), bootstrapProfile: 'allowed',
28
+ }),
29
+ "monitor-once": commandSeed({
30
+ reference: {
31
+ description: 'Collect one bounded owned-domain review and compare it with an optional prior checkpoint.',
32
+ example: 'whoisleuth monitor-once manifest.json --previous previous.json --json --output next.json',
33
+ 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.',
34
+ },
35
+ collection: { scope: 'Runs deep collection for at most 20 manifest domains with concurrency capped at 3.' },
36
+ summary: 'Run one bounded domain control review',
37
+ options: ['--previous', '--limit', '--concurrency', '--fail-on', '--json', '--junit', '--quiet', '--no-color'],
38
+ optionOverrides: {
39
+ '--concurrency': integer('Set the maximum number of concurrent collection tasks.', () => Object.freeze([BASE_INTEGER_RANGE(1, 3)]), () => 2),
40
+ },
41
+ positionals: OPTIONAL_FILE_POSITIONAL,
42
+ constraints: Object.freeze([
43
+ constraint({ kind: 'mutually_exclusive', options: ['--json', '--junit'] }),
44
+ ]),
45
+ handlerOwner: 'workflow',
46
+ networkEffect: 'always_network',
47
+ common: false,
48
+ schemaIdentifiers: Object.freeze(['whoisleuth.cli.domain-control-monitor', 'whoisleuth.domain-control-flight-recorder.input']),
49
+ primaryArtefacts: Object.freeze([]),
50
+ planSupport: false,
51
+ additionalOutputFormats: Object.freeze([]),
52
+ bootstrapProfile: 'allowed',
53
+ }),
54
+ "workflow-plan": commandSeed({
55
+ reference: {
56
+ description: 'Build a fixed domain-investigation plan from existing bounded CLI commands.',
57
+ example: 'whoisleuth workflow-plan domain-triage example.test --json',
58
+ boundary: 'Planning is offline and plan-only. It does not execute commands, expand placeholders, read files, make requests, or submit evidence.',
59
+ },
60
+ collection: { scope: 'Builds a fixed typed recipe and executes none of its network or file steps.' },
61
+ summary: 'Plan a fixed investigation recipe',
62
+ options: ['--list', '--explain', '--json', '--quiet', '--no-color'],
63
+ positionals: Object.freeze([
64
+ positional('recipe', 'enum', 0, 1, INVESTIGATION_PLAN_RECIPES),
65
+ positional('subject', 'text', 0, 1),
66
+ ]),
67
+ constraints: Object.freeze([
68
+ constraint({ kind: 'mutually_exclusive', options: ['--list', '--explain'] }),
69
+ ]),
70
+ handlerOwner: 'workflow',
71
+ networkEffect: 'offline',
72
+ common: true,
73
+ schemaIdentifiers: Object.freeze(['whoisleuth.cli.investigation-plan', 'whoisleuth.cli.workflow-recipe-catalogue']),
74
+ primaryArtefacts: Object.freeze(['Plan-only workflow document']),
75
+ planSupport: true,
76
+ additionalOutputFormats: Object.freeze([]),
77
+ bootstrapProfile: 'allowed',
78
+ }),
79
+ "workflow-run": commandSeed({
80
+ reference: {
81
+ description: 'Execute approved steps from a fixed investigation recipe and emit a resumable checkpoint.',
82
+ example: 'whoisleuth workflow-run domain-triage example.test --approve-network --json --output run.json',
83
+ 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.',
84
+ },
85
+ collection: { scope: 'Runs only fixed-recipe steps; network collection requires --approve-network and unresolved analyst selections pause.' },
86
+ summary: 'Execute approved fixed-recipe steps',
87
+ options: ['--select', '--use-artifact', '--confirm-review', '--approve-network', '--resume', '--interactive', '--preview', '--json', '--quiet', '--no-color'],
88
+ optionOverrides: { '--resume': file('Resume the selected workflow checkpoint; approvals must be supplied again.') },
89
+ positionals: Object.freeze([
90
+ positional('recipe', 'enum', 1, 1, RUNNABLE_INVESTIGATION_PLAN_RECIPES),
91
+ positional('subject', 'text', 1, 1),
92
+ ]),
93
+ constraints: Object.freeze([constraint({ kind: 'excludes_all', option: '--preview', excludedOptions: ['--approve-network', '--confirm-review', '--interactive', '--output', '--force', '--quiet'] })]),
94
+ handlerOwner: 'workflow',
95
+ networkEffect: 'conditional_network',
96
+ common: false,
97
+ schemaIdentifiers: Object.freeze(['whoisleuth.cli.investigation-run', 'whoisleuth.cli.investigation-preview']),
98
+ primaryArtefacts: Object.freeze(['Resumable workflow state', 'Offline resume preview']),
99
+ planSupport: false,
100
+ additionalOutputFormats: Object.freeze([]),
101
+ bootstrapProfile: 'allowed',
102
+ }),
103
+ });
@@ -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";
@@ -16,7 +16,37 @@ import { MAX_SHARING_REVIEW_BYTES } from "./sharing-review.mjs";
16
16
  import { createBufferedOutput } from "./output-file.mjs";
17
17
  import { boundedInteractiveAnswer, canReadInteractiveLine, readBoundedInteractiveLine } from "./terminal-input.mjs";
18
18
  import { WORKFLOW_INLINE_COMMANDS } from "./inline-command-families.mjs";
19
+ import { reviewCandidateWatchInput, parseCandidateWatchInput } from "./watchlist-review.mjs";
20
+ import { readDomainFeedFile } from "./domain-feed.mjs";
21
+ import { scanDomainFeed, buildDomainFeedWatchInput, normalizeDomainFeedSelection } from "../packages/monitoring/domain-feed.mjs";
22
+ import { MAX_WATCHLIST_IMPORT_BYTES } from "../packages/contracts/workspace-portability.mjs";
19
23
  import { runDiscriminatedCommandHandler } from "./discriminated-command-handlers.mjs";
24
+ async function runWatchlistReviewCommand(args, dependencies, context) {
25
+ const raw = dependencies.readArtifactInput ? await dependencies.readArtifactInput(args.source) : await context.readInput(args.source, MAX_WATCHLIST_IMPORT_BYTES, 'Candidate watch selection');
26
+ const document = reviewCandidateWatchInput(raw, args.operation, context.now());
27
+ if (!args.quiet)
28
+ context.writeStdout(formatJsonDocument(document));
29
+ return EXIT_CODES.SUCCESS;
30
+ }
31
+ async function runDomainFeedCommand(args, dependencies, context) {
32
+ context.setFailureLabel('Offline domain-feed review');
33
+ const handoff = args.contextSource ? parseCandidateWatchInput(dependencies.readArtifactInput
34
+ ? await dependencies.readArtifactInput(args.contextSource)
35
+ : await context.readInput(args.contextSource, MAX_WATCHLIST_IMPORT_BYTES, 'Candidate watch context')) : null;
36
+ const watchSelection = handoff?.selection;
37
+ if (watchSelection && watchSelection.candidates.length)
38
+ throw new CliUsageError('The watch-input context must have an empty candidates array; only reviewed feed matches may be nominated.');
39
+ const selection = normalizeDomainFeedSelection({ ...args.selection, brandProfileId: watchSelection?.brandProfileId ?? null });
40
+ const review = await scanDomainFeed((dependencies.readDomainFeedInput ?? readDomainFeedFile)(args.source, dependencies.signal), {
41
+ feedId: args.feedId, selection, importedAt: context.now(), ...(dependencies.signal ? { signal: dependencies.signal } : {}),
42
+ });
43
+ const document = watchSelection && handoff
44
+ ? buildDomainFeedWatchInput(review, review.matches.map(match => match.domain), watchSelection, handoff.watchlists)
45
+ : review;
46
+ if (!args.quiet)
47
+ context.writeStdout(formatJsonDocument(document));
48
+ return EXIT_CODES.SUCCESS;
49
+ }
20
50
  function boundWorkflowInputs(command, inputs, dependencies, context) {
21
51
  if (!inputs.size)
22
52
  return {};
@@ -125,6 +155,13 @@ async function runWorkflowRecipeCommand(args, dependencies, context) {
125
155
  throw new CliUsageError(`Could not read investigation resume state: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
126
156
  }
127
157
  }
158
+ if (args.preview) {
159
+ const preview = previewInvestigationRecipe(args.recipe, args.subject, {
160
+ resumeInput, selections: args.selections, artifactBindings: args.artifactBindings, generatedAt: context.now(),
161
+ });
162
+ context.writeStdout(args.output === 'json' ? formatJsonDocument(preview) : context.terminal(formatInvestigationPreview(preview), args.color));
163
+ return EXIT_CODES.SUCCESS;
164
+ }
128
165
  const document = await runInvestigationRecipe(args.recipe, args.subject, {
129
166
  approveNetwork: args.approveNetwork,
130
167
  selections: args.selections,
@@ -174,6 +211,8 @@ async function runWorkflowRecipeCommand(args, dependencies, context) {
174
211
  return investigationRunExitCode(document);
175
212
  }
176
213
  const WORKFLOW_COMMAND_HANDLERS = Object.freeze({
214
+ 'domain-feed': runDomainFeedCommand,
215
+ 'watchlist-review': runWatchlistReviewCommand,
177
216
  'monitor-once': runMonitorOnceCommand,
178
217
  'workflow-plan': runWorkflowPlanCommand,
179
218
  'workflow-run': runWorkflowRecipeCommand,
@@ -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