@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
@@ -1,14 +1,14 @@
1
1
  import { Buffer } from 'node:buffer';
2
2
  import { decodeBoundedUtf8 } from "../lib/bounded-file.mjs";
3
- import { isValidAsciiDomainName } from "../lib/hostname.mjs";
4
- import { scanBoundedJson } from "../lib/bounded-json.mjs";
5
- import { canonicalRegistrableDomain } from "../lib/registrable-domain.mjs";
6
- import { ANALYST_REVIEW_REASON_VALUES, analystInteroperabilityTags } from "../lib/analyst-taxonomy.mjs";
7
- import { buildRiskCalibrationSummaryReport, parseRiskCalibrationSummaryReport, RISK_CALIBRATION_SUMMARY_INTERPRETATION, RISK_CALIBRATION_SUMMARY_THRESHOLDS, } from "../lib/risk-calibration-summary.mjs";
3
+ import { isValidAsciiDomainName } from "../packages/contracts/domain-name.mjs";
4
+ import { scanBoundedJson } from "../packages/analysis/bounded-json.mjs";
5
+ import { canonicalRegistrableDomain } from "../packages/analysis/registrable-domain.mjs";
6
+ import { ANALYST_REVIEW_REASON_VALUES, analystInteroperabilityTags } from "../packages/analysis/analyst-taxonomy.mjs";
7
+ import { buildRiskCalibrationSummaryReport, parseRiskCalibrationSummaryReport, RISK_CALIBRATION_SUMMARY_INTERPRETATION, RISK_CALIBRATION_SUMMARY_THRESHOLDS, } from "../packages/analysis/risk-calibration-summary.mjs";
8
8
  import { MAX_RISK_CALIBRATION_ACTIVITY_LENGTH, MAX_RISK_CALIBRATION_AVAILABILITY_LENGTH, MAX_RISK_CALIBRATION_DISPOSITION_LENGTH, MAX_RISK_CALIBRATION_DOMAIN_AGE_DAYS, MAX_RISK_CALIBRATION_DOMAIN_LENGTH, MAX_RISK_CALIBRATION_FINDING_CATEGORY_LENGTH, MAX_RISK_CALIBRATION_FINDINGS_PER_PROVIDER, MAX_RISK_CALIBRATION_INPUT_BYTES, MAX_RISK_CALIBRATION_JSON_CONTAINER_ITEMS, MAX_RISK_CALIBRATION_JSON_DEPTH, MAX_RISK_CALIBRATION_JSON_KEYS, MAX_RISK_CALIBRATION_JSON_VALUES, MAX_RISK_CALIBRATION_MUTATIONS, MAX_RISK_CALIBRATION_PROVIDER_ID_LENGTH, MAX_RISK_CALIBRATION_PROVIDER_STATE_LENGTH, MAX_RISK_CALIBRATION_PROVIDERS, MAX_RISK_CALIBRATION_RECORD_ID_LENGTH, MAX_RISK_CALIBRATION_RECORDS, MAX_RISK_CALIBRATION_REVIEW_REASON_LENGTH, MAX_RISK_CALIBRATION_SCAN_DEPTH_LENGTH, MAX_RISK_CALIBRATION_STRING_LENGTH, MAX_RISK_CALIBRATION_TIMESTAMP_LENGTH, RISK_CALIBRATION_DATASET_SCHEMA, RISK_CALIBRATION_DATASET_VERSION, RISK_CALIBRATION_REPORT_SCHEMA, RISK_CALIBRATION_REPORT_VERSION, RISK_CALIBRATION_DISPOSITIONS, serializeRiskCalibrationSnapshot, snapshotRiskCalibrationReportForSerialization, SUPPORTED_RISK_CALIBRATION_DATASET_VERSIONS, } from "../packages/contracts/risk-calibration.mjs";
9
- import { CliUsageError } from "./arguments.mjs";
10
- import { RISK_MUTATION_TYPES } from "../lib/risk-scoring.mjs";
11
- import { THREAT_INTELLIGENCE_CONTRACT_VERSION, THREAT_INTELLIGENCE_ENVELOPE_VERSION, THREAT_INTELLIGENCE_SCHEMA, } from "../lib/threat-intelligence-types.mjs";
9
+ import { CliUsageError } from "./errors.mjs";
10
+ import { RISK_MUTATION_TYPES } from "../packages/analysis/risk-scoring.mjs";
11
+ import { THREAT_INTELLIGENCE_CONTRACT_VERSION, THREAT_INTELLIGENCE_ENVELOPE_VERSION, THREAT_INTELLIGENCE_SCHEMA, } from "../packages/analysis/threat-intelligence-types.mjs";
12
12
  export { MAX_RISK_CALIBRATION_INPUT_BYTES, MAX_RISK_CALIBRATION_RECORDS, MAX_RISK_CALIBRATION_STRING_LENGTH, RISK_CALIBRATION_DATASET_SCHEMA, RISK_CALIBRATION_DATASET_VERSION, RISK_CALIBRATION_REPORT_SCHEMA, RISK_CALIBRATION_REPORT_VERSION, SUPPORTED_RISK_CALIBRATION_DATASET_VERSIONS, } from "../packages/contracts/risk-calibration.mjs";
13
13
  export const RISK_CALIBRATION_THRESHOLDS = RISK_CALIBRATION_SUMMARY_THRESHOLDS;
14
14
  const CONTROL_RE = /[\x00-\x1f\x7f]/;
package/cli/runner.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createRequire } from 'node:module';
2
2
  import { abortable } from "../lib/abort.mjs";
3
- import { parseCliArguments } from "./arguments.mjs";
3
+ import { isCliCommandForOwner, parseCliArguments } from "./arguments.mjs";
4
4
  import { HELP, commandDefinition, commandHelp, } from "./command-reference.mjs";
5
5
  import { boundedCliErrorMessage, CliUsageError } from "./errors.mjs";
6
6
  import { evidenceCommandFailureLabel, isEvidenceCommand, runEvidenceCommand, } from "./evidence-command-runner.mjs";
@@ -9,7 +9,7 @@ import { readCliHeaderInput, readCliTextInput } from "./input.mjs";
9
9
  import { INLINE_CLI_COMMANDS, runInlineCommand } from "./inline-command-runner.mjs";
10
10
  import { canLaunchInteractiveCli, launchInteractiveCli, } from "./interactive-launcher.mjs";
11
11
  import { MAX_OFFLINE_PASSPHRASE_FILE_BYTES, } from "./artifact-verify.mjs";
12
- import { cleanupPendingOutputFiles, createBufferedOutput, writePrivateFile } from "./output-file.mjs";
12
+ import { cleanupPendingOutputFiles, createBufferedOutput, writePrivateFile, MAX_CLI_OUTPUT_BYTES } from "./output-file.mjs";
13
13
  import { createTerminalProgress } from "./progress.mjs";
14
14
  import { presentTerminalOutput, terminalPresentation, } from "./terminal-presentation.mjs";
15
15
  const require = createRequire(import.meta.url);
@@ -40,16 +40,6 @@ function isCancellation(error, signal) {
40
40
  return signal?.aborted === true
41
41
  || Boolean(error && typeof error === 'object' && 'name' in error && error.name === 'AbortError');
42
42
  }
43
- function usageEventReason(error) {
44
- const message = boundedCliErrorMessage(error, 'Invalid command input').toLowerCase();
45
- if (message.includes('could not read'))
46
- return 'input_unavailable';
47
- if (message.includes('cannot be combined') || message.includes('mutually exclusive'))
48
- return 'conflicting_options';
49
- if (message.includes('requires') || message.includes('did not contain'))
50
- return 'missing_input';
51
- return 'invalid_input';
52
- }
53
43
  async function runParsedCli(args, dependencies = {}, writeBinaryOutput) {
54
44
  const stdout = dependencies.stdout || process.stdout;
55
45
  const stderr = dependencies.stderr || process.stderr;
@@ -113,7 +103,7 @@ async function runParsedCli(args, dependencies = {}, writeBinaryOutput) {
113
103
  catch (error) {
114
104
  if (error instanceof CliUsageError)
115
105
  throw error;
116
- throw new CliUsageError(`Could not read passphrase file: ${boundedCliErrorMessage(error, 'File could not be read')}`);
106
+ throw new CliUsageError(`Could not read passphrase file: ${boundedCliErrorMessage(error, 'File could not be read')}`, 'input_unavailable');
117
107
  }
118
108
  const passphrase = passphraseText.replace(/\r?\n$/u, '');
119
109
  if (!passphrase || /[\r\n\u0000]/u.test(passphrase)) {
@@ -198,25 +188,9 @@ async function runParsedCli(args, dependencies = {}, writeBinaryOutput) {
198
188
  });
199
189
  }
200
190
  if (handlerOwner === 'network') {
201
- if (args.action !== 'ct-search'
202
- && args.action !== 'posture'
203
- && args.action !== 'http'
204
- && args.action !== 'tls'
205
- && args.action !== 'dnssec-validate'
206
- && args.action !== 'mail-transport') {
191
+ if (!isCliCommandForOwner(args, 'network')) {
207
192
  throw new Error('Network command registry ownership is inconsistent.');
208
193
  }
209
- failureLabel = args.action === 'ct-search'
210
- ? 'Certificate Transparency search'
211
- : args.action === 'posture'
212
- ? 'Domain posture audit'
213
- : args.action === 'http'
214
- ? 'HTTP probe'
215
- : args.action === 'tls'
216
- ? 'TLS evidence collection'
217
- : args.action === 'dnssec-validate'
218
- ? 'DNSSEC chain validation'
219
- : 'Mail transport review';
220
194
  const { runNetworkCommand } = await import("./network-command-runner.mjs");
221
195
  return await runNetworkCommand(args, dependencies, commandContext);
222
196
  }
@@ -244,7 +218,7 @@ async function runParsedCli(args, dependencies = {}, writeBinaryOutput) {
244
218
  eventProgress.current?.emit({
245
219
  event: 'failed',
246
220
  state: 'usage',
247
- reason: usageEventReason(error),
221
+ reason: error.reason,
248
222
  exitCode: EXIT_CODES.USAGE,
249
223
  });
250
224
  if (!eventProgress.current?.enabled)
@@ -331,12 +305,26 @@ async function runCliCommand(argv, dependencies = {}) {
331
305
  checkpoint = caseCheckpoint;
332
306
  capturedSource = caseCheckpoint.sourceInput;
333
307
  }
308
+ else if (args.action === 'indicator-set') {
309
+ const { prepareLocalDocumentWrite } = await import("./local-document-checkpoint.mjs");
310
+ const { MAX_MANAGED_INDICATOR_PLAN_BYTES, MAX_MANAGED_INDICATOR_SET_BYTES } = await import("../packages/contracts/analyst-interchange.mjs");
311
+ const indicatorCheckpoint = await prepareLocalDocumentWrite({
312
+ destination: args.destination, source: args.source,
313
+ force: args.force === true, label: 'Indicator revision', allowSourceReplacement: false,
314
+ maximumInputBytes: args.operation === 'revise' ? MAX_MANAGED_INDICATOR_PLAN_BYTES : MAX_MANAGED_INDICATOR_SET_BYTES,
315
+ maximumOutputBytes: MAX_CLI_OUTPUT_BYTES,
316
+ ...(dependencies.signal ? { signal: dependencies.signal } : {}),
317
+ });
318
+ checkpoint = indicatorCheckpoint;
319
+ capturedSource = indicatorCheckpoint.sourceInput;
320
+ }
334
321
  const code = await runParsedCli(args, {
335
322
  ...dependencies, stdout: buffered.stream,
336
323
  ...(args.action === 'workflow-run' && args.resumeSource && capturedSource !== null
337
324
  ? { workflowResumeInput: capturedSource }
338
325
  : {}),
339
326
  ...(args.action === 'case' && capturedSource !== null ? { caseFileInput: capturedSource } : {}),
327
+ ...(args.action === 'indicator-set' && capturedSource !== null ? { readArtifactInput: async () => capturedSource } : {}),
340
328
  }, buffered.writeBinary);
341
329
  if (code !== EXIT_CODES.SUCCESS && code !== EXIT_CODES.PARTIAL_FAILURE)
342
330
  return code;
@@ -367,7 +355,7 @@ async function runCliCommand(argv, dependencies = {}) {
367
355
  }
368
356
  finally {
369
357
  if (checkpoint && await checkpoint.release() > 0) {
370
- write(stderr, `${args.action === 'case' ? 'Case' : 'Workflow'} cleanup warning: File ownership changed or a lease could not be removed. Inspect the selected directory before resuming.\n`);
358
+ write(stderr, `${args.action === 'case' ? 'Case' : args.action === 'indicator-set' ? 'Indicator revision' : 'Workflow'} cleanup warning: File ownership changed or a lease could not be removed. Inspect the selected directory before resuming.\n`);
371
359
  }
372
360
  }
373
361
  }
@@ -1,9 +1,9 @@
1
1
  import { Buffer } from 'node:buffer';
2
2
  import { types as utilTypes } from 'node:util';
3
3
  import { classifyQuery } from "../lib/classify.mjs";
4
- import { isSafeJsonObjectKey, scanBoundedJson, } from "../lib/bounded-json.mjs";
4
+ import { isSafeJsonObjectKey, scanBoundedJson, } from "../packages/analysis/bounded-json.mjs";
5
5
  import { decodeBoundedUtf8 } from "../lib/bounded-file.mjs";
6
- import { CliUsageError } from "./arguments.mjs";
6
+ import { CliUsageError } from "./errors.mjs";
7
7
  import { validHttpDeliveryMetadata, validPagePublicationMetadata, } from "../lib/homepage-metadata-contract.mjs";
8
8
  import { registrarStandingObservedBy, resolveRegistrarIanaId, validRegistrarStanding, } from "../lib/registrar-standing-contract.mjs";
9
9
  import { normalizeExplicitIsoTimestamp } from "../packages/evidence/observation.mjs";
@@ -0,0 +1,63 @@
1
+ import { Worker, isMainThread, parentPort, workerData } from 'node:worker_threads';
2
+ import { reviewSelectedInput } from "../packages/investigation/selected-input-review.mjs";
3
+ import { assertMessageBytes } from "../packages/investigation/intake-report.mjs";
4
+ import { DOCUMENT_REVIEW_DEADLINE_MS } from "../packages/contracts/document-intake.mjs";
5
+ import { MESSAGE_INTAKE_SCHEMA, MESSAGE_INTAKE_VERSION } from "../packages/contracts/message-intake.mjs";
6
+ if (!isMainThread && parentPort) {
7
+ const port = parentPort, request = workerData;
8
+ try {
9
+ port.postMessage({ result: await reviewSelectedInput(request.bytes, request.kind, request.reviewedAt) });
10
+ }
11
+ catch {
12
+ port.postMessage({ error: true });
13
+ }
14
+ finally {
15
+ if (request.bytes instanceof Uint8Array)
16
+ request.bytes.fill(0);
17
+ port.close();
18
+ }
19
+ }
20
+ /** A disposable worker makes synchronous parser work cancellable, not a sandbox. */
21
+ export async function reviewSelectedInputInWorker(bytes, kind, reviewedAt, signal) {
22
+ assertMessageBytes(bytes);
23
+ signal?.throwIfAborted();
24
+ // Buffer.slice() can retain a pooled, non-transferable backing store.
25
+ const copy = Uint8Array.from(bytes);
26
+ const worker = new Worker(new URL(import.meta.url), { workerData: { bytes: copy, kind, reviewedAt },
27
+ transferList: [copy.buffer], env: {}, execArgv: [], stdout: true, stderr: true,
28
+ resourceLimits: { maxOldGenerationSizeMb: 512, maxYoungGenerationSizeMb: 32, stackSizeMb: 4 } });
29
+ worker.stdout.resume();
30
+ worker.stderr.resume();
31
+ try {
32
+ return await new Promise((resolve, reject) => {
33
+ let settled = false;
34
+ const finish = (value, cause) => {
35
+ if (settled)
36
+ return;
37
+ settled = true;
38
+ clearTimeout(timer);
39
+ signal?.removeEventListener('abort', abort);
40
+ if (value)
41
+ resolve(value);
42
+ else
43
+ reject(cause ?? new TypeError('The selected input could not be reviewed within its format and processing bounds.'));
44
+ };
45
+ const abort = () => finish(undefined, new Error('Selected input review was cancelled. Nothing was saved.'));
46
+ const timer = setTimeout(() => finish(undefined, new Error('Selected input review exceeded its processing deadline. Nothing was saved.')), DOCUMENT_REVIEW_DEADLINE_MS);
47
+ worker.once('message', (reply) => {
48
+ if (reply && typeof reply === 'object' && 'result' in reply && reply.result?.report.schema === MESSAGE_INTAKE_SCHEMA && reply.result.report.schemaVersion === MESSAGE_INTAKE_VERSION)
49
+ finish(reply.result);
50
+ else
51
+ finish();
52
+ });
53
+ worker.once('error', () => finish());
54
+ worker.once('exit', () => finish());
55
+ signal?.addEventListener('abort', abort, { once: true });
56
+ if (signal?.aborted)
57
+ abort();
58
+ });
59
+ }
60
+ finally {
61
+ await worker.terminate();
62
+ }
63
+ }
@@ -1,79 +1,18 @@
1
1
  import { hasVerifiedWholeArtifactIntegrity, MAX_OFFLINE_ARTIFACT_BYTES, parseOfflineArtifactJson, UnsupportedOfflineArtifactError, verifyOfflineArtifact, } from "./artifact-verify.mjs";
2
+ import { TLP_MARKINGS, RECIPIENT_SCOPES, expectedScope, tlpLabel, scanSharingMetadata as scanArtifact } from "../packages/interchange/sharing-policy.mjs";
2
3
  const SHARING_REVIEW_SCHEMA = 'whoisleuth.cli.sharing-review';
3
4
  const SHARING_REVIEW_VERSION = 2;
4
5
  const MAX_SHARING_REVIEW_BYTES = MAX_OFFLINE_ARTIFACT_BYTES;
5
- const TLP_MARKINGS = ['clear', 'green', 'amber', 'amber-strict', 'red'];
6
- const RECIPIENT_SCOPES = ['public', 'community', 'organization', 'named-recipients'];
7
6
  const CONTROL_RE = /[\u0000-\u001f\u007f]/u;
8
- const RISKY_KEYS = new Set([
9
- 'authorization', 'cookie', 'cookies', 'credential', 'credentials', 'email', 'emails',
10
- 'entities', 'password', 'phone', 'raw', 'rawrdap', 'rawwhois', 'registrant', 'session', 'token',
11
- ]);
12
- const MARKING_KEYS = new Set([
13
- 'informationmarking', 'marking', 'markings', 'sharingmarking',
14
- 'tlp', 'tlplabel', 'tlpmarking', 'trafficlightprotocol',
15
- ]);
16
7
  function record(value) {
17
8
  return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
18
9
  }
19
- function normalizedKey(value) {
20
- return value.toLowerCase().replace(/[^a-z0-9]/gu, '');
21
- }
22
10
  function boundedMetadataText(value) {
23
11
  if (typeof value !== 'string' || value.length > 120 || CONTROL_RE.test(value))
24
12
  return null;
25
13
  const trimmed = value.trim();
26
14
  return trimmed || null;
27
15
  }
28
- function markingFromText(value) {
29
- const normalized = value.toUpperCase().replace(/\s+/gu, '').replace('TLP:', '').replace('+STRICT', '-STRICT').toLowerCase();
30
- return TLP_MARKINGS.includes(normalized) ? normalized : null;
31
- }
32
- function scanArtifact(root) {
33
- // The caller has already enforced the JSON byte, node, nesting and container
34
- // bounds. Scan that admitted tree in full without a second, smaller prefix.
35
- const stack = [{ value: root, marking: false }];
36
- const markings = new Set();
37
- let riskyKeyCount = 0;
38
- while (stack.length) {
39
- const next = stack.pop();
40
- if (typeof next.value === 'string') {
41
- if (next.marking) {
42
- const marking = markingFromText(next.value);
43
- if (marking)
44
- markings.add(marking);
45
- }
46
- continue;
47
- }
48
- if (Array.isArray(next.value)) {
49
- for (const value of next.value)
50
- stack.push({ value, marking: next.marking });
51
- continue;
52
- }
53
- const valueRecord = record(next.value);
54
- if (!valueRecord)
55
- continue;
56
- for (const [key, value] of Object.entries(valueRecord)) {
57
- const normalized = normalizedKey(key);
58
- if (RISKY_KEYS.has(normalized))
59
- riskyKeyCount += 1;
60
- stack.push({ value, marking: MARKING_KEYS.has(normalized) });
61
- }
62
- }
63
- return { importedMarkings: [...markings], riskyKeyCount };
64
- }
65
- function expectedScope(marking) {
66
- if (marking === 'clear')
67
- return 'public';
68
- if (marking === 'green')
69
- return 'community';
70
- if (marking === 'red')
71
- return 'named-recipients';
72
- return 'organization';
73
- }
74
- function tlpLabel(marking) {
75
- return `TLP:${marking === 'amber-strict' ? 'AMBER+STRICT' : marking.toUpperCase()}`;
76
- }
77
16
  async function buildSharingReview(raw, options, generatedAt = new Date().toISOString()) {
78
17
  if (Buffer.byteLength(raw, 'utf8') > MAX_SHARING_REVIEW_BYTES) {
79
18
  throw new TypeError(`Sharing review input is limited to ${MAX_SHARING_REVIEW_BYTES} bytes.`);
@@ -95,8 +34,10 @@ async function buildSharingReview(raw, options, generatedAt = new Date().toISOSt
95
34
  const importedRank = strictestImported === null ? -1 : TLP_MARKINGS.indexOf(strictestImported);
96
35
  const effective = importedRank > requestedRank ? strictestImported : options.marking;
97
36
  let integrity = 'unsupported';
37
+ let verifiedIdentity = null;
98
38
  try {
99
39
  const verification = await verifyOfflineArtifact(raw);
40
+ verifiedIdentity = verification.artifact;
100
41
  integrity = verification.state === 'verified' && hasVerifiedWholeArtifactIntegrity(verification)
101
42
  ? 'verified'
102
43
  : verification.state === 'integrity_valid'
@@ -141,8 +82,8 @@ async function buildSharingReview(raw, options, generatedAt = new Date().toISOSt
141
82
  caution: findings.filter((finding) => finding.state === 'caution').length,
142
83
  pass: findings.filter((finding) => finding.state === 'pass').length,
143
84
  };
144
- const artifactSchema = boundedMetadataText(artifact.schema);
145
- const rawArtifactVersion = artifact.version ?? artifact.schemaVersion;
85
+ const artifactSchema = boundedMetadataText(verifiedIdentity ? verifiedIdentity.schema : artifact.schema);
86
+ const rawArtifactVersion = verifiedIdentity ? verifiedIdentity.version : artifact.version ?? artifact.schemaVersion;
146
87
  const artifactVersion = Number.isSafeInteger(rawArtifactVersion)
147
88
  && Number(rawArtifactVersion) >= 1
148
89
  && Number(rawArtifactVersion) <= 10_000
@@ -1,6 +1,6 @@
1
1
  import { Buffer } from 'node:buffer';
2
2
  import { canonicalArtifactJson } from "../packages/evidence/artifact-integrity.mjs";
3
- import { scanBoundedJson } from "../lib/bounded-json.mjs";
3
+ import { scanBoundedJson } from "../packages/analysis/bounded-json.mjs";
4
4
  import { LOOKUP_SOURCE_STATES, lookupDiagnosticStates } from "../lib/lookup-diagnostics.mjs";
5
5
  import { normalizeExplicitIsoTimestamp, normalizeLegacyIsoTimestamp, } from "../packages/evidence/observation.mjs";
6
6
  import { CLI_LOOKUP_SCHEMA, MAX_CLI_LOOKUP_BYTES, } from "../packages/contracts/cli-lookup.mjs";
@@ -8,7 +8,7 @@ import { normalizeCliLookupDocument } from "./saved-lookup.mjs";
8
8
  import { CLI_BULK_SCHEMA, CLI_BULK_ITEM_SCHEMA, CLI_BULK_SCHEMA_VERSION } from "./formatters/json.mjs";
9
9
  import { LOOKUP_THREAT_INTELLIGENCE_PROVIDERS } from "../lib/lookup-threat-provider-inventory.mjs";
10
10
  import { MAX_THREAT_INTELLIGENCE_PROVIDERS } from "../lib/lookup-response-contract.mjs";
11
- import { THREAT_INTELLIGENCE_SCHEMA, THREAT_INTELLIGENCE_CONTRACT_VERSION } from "../lib/threat-intelligence-types.mjs";
11
+ import { THREAT_INTELLIGENCE_SCHEMA, THREAT_INTELLIGENCE_CONTRACT_VERSION } from "../packages/analysis/threat-intelligence-types.mjs";
12
12
  export const SOURCE_RELIABILITY_REPORT_SCHEMA = 'whoisleuth.source-reliability-report';
13
13
  export const SOURCE_RELIABILITY_REPORT_VERSION = 1;
14
14
  export const MAX_SOURCE_RELIABILITY_INPUT_BYTES = 12 * 1024 * 1024;
@@ -0,0 +1,49 @@
1
+ import {} from "./command-argument-grammar.mjs";
2
+ import {} from "./command-reference.mjs";
3
+ import { terminalOptions, jsonOutput, singleInputAction } from "./argument-values.mjs";
4
+ export const SUPPORT_ARGUMENT_PARSERS = Object.freeze({
5
+ completion: (parsed) => ({
6
+ action: 'completion',
7
+ shell: parsed.positionalValue('shell'),
8
+ }),
9
+ doctor: (parsed) => ({
10
+ action: 'doctor',
11
+ network: parsed.hasOption('--network'),
12
+ output: jsonOutput(parsed),
13
+ ...terminalOptions(parsed),
14
+ }),
15
+ commands: (parsed) => ({
16
+ action: 'commands',
17
+ output: jsonOutput(parsed),
18
+ common: parsed.hasOption('--common'),
19
+ group: parsed.optionValue('--group'),
20
+ mode: parsed.optionValue('--mode'),
21
+ ...terminalOptions(parsed),
22
+ }),
23
+ manual: () => ({ action: 'manual' }),
24
+ 'registry-support': (parsed) => ({
25
+ action: 'registry-support',
26
+ target: parsed.positionalValue('domain-or-suffix'),
27
+ output: jsonOutput(parsed),
28
+ ...terminalOptions(parsed),
29
+ }),
30
+ 'registry-doctor': (parsed) => singleInputAction('registry-doctor', parsed),
31
+ 'registry-cohort': (parsed) => singleInputAction('registry-cohort', parsed),
32
+ 'registry-scaffold': (parsed) => ({
33
+ action: 'registry-scaffold',
34
+ profile: parsed.optionValue('--profile'),
35
+ suffix: parsed.optionValue('--suffix'),
36
+ scenario: parsed.optionValue('--scenario'),
37
+ }),
38
+ 'risk-calibrate': (parsed) => ({
39
+ action: 'risk-calibrate',
40
+ source: parsed.positionalValue('source'),
41
+ output: parsed.hasOption('--json')
42
+ ? 'json'
43
+ : parsed.hasOption('--summary-json')
44
+ ? 'summary_json'
45
+ : 'terminal',
46
+ ...terminalOptions(parsed),
47
+ }),
48
+ 'lookalike-calibrate': (parsed) => singleInputAction('lookalike-calibrate', parsed),
49
+ });
@@ -0,0 +1,209 @@
1
+ import { RISK_CALIBRATION_DATASET_SCHEMA, RISK_CALIBRATION_REPORT_SCHEMA } from "../packages/contracts/risk-calibration.mjs";
2
+ import { positional, NO_POSITIONALS, OPTIONAL_FILE_POSITIONAL, constraint, EMPTY_CONSTRAINTS, commandSeed, text } from "./command-definition.mjs";
3
+ export const SUPPORT_COMMAND_DEFINITIONS = Object.freeze({
4
+ completion: commandSeed({
5
+ reference: {
6
+ description: 'Print a static shell-completion script for the installed CLI.',
7
+ example: 'whoisleuth completion zsh > ~/.zfunc/_whoisleuth',
8
+ boundary: 'Generation is offline and writes only the script to stdout. The command never modifies shell configuration.',
9
+ },
10
+ collection: { scope: 'Prints one static script and changes no shell configuration.' },
11
+ summary: 'Print shell completion',
12
+ options: [],
13
+ positionals: Object.freeze([positional('shell', 'enum', 1, 1, ['bash', 'zsh', 'fish', 'powershell'])]),
14
+ constraints: EMPTY_CONSTRAINTS,
15
+ handlerOwner: 'support',
16
+ networkEffect: 'offline',
17
+ common: false,
18
+ schemaIdentifiers: Object.freeze([]),
19
+ primaryArtefacts: Object.freeze([]),
20
+ planSupport: false,
21
+ additionalOutputFormats: Object.freeze([]),
22
+ bootstrapProfile: 'allowed',
23
+ }),
24
+ doctor: commandSeed({
25
+ reference: {
26
+ description: 'Check the supported runtime and local terminal capabilities.',
27
+ example: 'whoisleuth doctor --json',
28
+ boundary: 'The default check is offline. Public DNS and port 43 checks run only when --network is explicitly supplied.',
29
+ },
30
+ collection: { scope: 'Network access is opt-in with --network and is limited to fixed public DNS, HTTPS, and WHOIS diagnostics.' },
31
+ summary: 'Check the local CLI runtime',
32
+ options: ['--network', '--json', '--quiet', '--no-color'],
33
+ positionals: NO_POSITIONALS,
34
+ constraints: EMPTY_CONSTRAINTS,
35
+ handlerOwner: 'support',
36
+ networkEffect: 'conditional_network',
37
+ common: true,
38
+ schemaIdentifiers: Object.freeze(['whoisleuth.cli.doctor']),
39
+ primaryArtefacts: Object.freeze([]),
40
+ planSupport: false,
41
+ additionalOutputFormats: Object.freeze([]),
42
+ bootstrapProfile: 'allowed',
43
+ }),
44
+ commands: commandSeed({
45
+ reference: {
46
+ description: 'List the installed command contracts in terminal or versioned JSON form.',
47
+ example: 'whoisleuth commands --json',
48
+ boundary: 'Catalogue generation is offline. It reports declared command modes and limits without executing collection or inspecting local evidence.',
49
+ },
50
+ collection: { scope: 'Reads the embedded command catalogue and performs no collection.' },
51
+ summary: 'List installed command contracts',
52
+ options: ['--common', '--group', '--mode', '--json', '--quiet', '--no-color'],
53
+ positionals: NO_POSITIONALS,
54
+ constraints: Object.freeze([]),
55
+ handlerOwner: 'support',
56
+ networkEffect: 'offline',
57
+ common: true,
58
+ schemaIdentifiers: Object.freeze(['whoisleuth.cli.command-catalogue']),
59
+ primaryArtefacts: Object.freeze([]),
60
+ planSupport: false,
61
+ additionalOutputFormats: Object.freeze([]),
62
+ bootstrapProfile: 'allowed',
63
+ }),
64
+ manual: commandSeed({
65
+ reference: {
66
+ description: 'Print a generated roff manual page for local installation.',
67
+ example: 'whoisleuth manual | man -l -',
68
+ boundary: 'Generation is offline and derives from the same command catalogue as focused help.',
69
+ },
70
+ collection: { scope: 'Builds documentation from the embedded command catalogue.' },
71
+ summary: 'Print the generated manual page',
72
+ options: [],
73
+ positionals: NO_POSITIONALS,
74
+ constraints: EMPTY_CONSTRAINTS,
75
+ handlerOwner: 'support',
76
+ networkEffect: 'offline',
77
+ common: false,
78
+ schemaIdentifiers: Object.freeze([]),
79
+ primaryArtefacts: Object.freeze([]),
80
+ planSupport: false,
81
+ additionalOutputFormats: Object.freeze([]),
82
+ bootstrapProfile: 'allowed',
83
+ }),
84
+ "registry-support": commandSeed({
85
+ reference: {
86
+ description: 'Explain the local registry capability profile for one domain or suffix.',
87
+ example: 'whoisleuth registry-support example.test --json',
88
+ boundary: 'This command is offline. Catalogue coverage does not test live reachability or decide registration or availability.',
89
+ },
90
+ collection: { scope: 'Reads the embedded registry capability catalogue for one domain or suffix.' },
91
+ summary: 'Explain local registry coverage',
92
+ options: ['--json', '--quiet', '--no-color'],
93
+ positionals: Object.freeze([positional('domain-or-suffix', 'text', 0, 1, [], 'argv_or_stdin')]),
94
+ constraints: EMPTY_CONSTRAINTS,
95
+ handlerOwner: 'support',
96
+ networkEffect: 'offline',
97
+ common: false,
98
+ schemaIdentifiers: Object.freeze(['whoisleuth.cli.registry-support', 'whoisleuth.registry-standards-coverage']),
99
+ primaryArtefacts: Object.freeze([]),
100
+ planSupport: false,
101
+ additionalOutputFormats: Object.freeze([]),
102
+ bootstrapProfile: 'allowed',
103
+ }),
104
+ "registry-doctor": commandSeed({
105
+ reference: {
106
+ description: 'Compare a saved Lookup registry result with the reviewed local capability profile.',
107
+ example: 'whoisleuth registry-doctor lookup.json --json',
108
+ boundary: 'The command is offline. It distinguishes expected access constraints from collection results and does not contact a live registry.',
109
+ },
110
+ collection: { scope: 'Reads one saved Lookup and the embedded registry capability catalogue.' },
111
+ summary: 'Diagnose saved registry collection',
112
+ options: ['--json', '--quiet', '--no-color'],
113
+ positionals: OPTIONAL_FILE_POSITIONAL,
114
+ constraints: EMPTY_CONSTRAINTS,
115
+ handlerOwner: 'support',
116
+ networkEffect: 'offline',
117
+ common: false,
118
+ schemaIdentifiers: Object.freeze(['whoisleuth.cli.registry-doctor']),
119
+ primaryArtefacts: Object.freeze([]),
120
+ planSupport: false,
121
+ additionalOutputFormats: Object.freeze([]),
122
+ bootstrapProfile: 'allowed',
123
+ }),
124
+ "registry-cohort": commandSeed({
125
+ reference: {
126
+ description: 'Build privacy-safe suffix and capability-profile timelines from saved observations or retained cohort reports.',
127
+ example: 'whoisleuth registry-cohort saved-lookups.jsonl --json',
128
+ boundary: 'This command is offline and omits domains, queries, and raw evidence. Input families cannot be mixed, and retained samples are never assumed independent.',
129
+ },
130
+ collection: { scope: 'Reads at most 500 saved Lookups or retained cohort reports from one unmixed family and emits bounded target-free timelines.' },
131
+ summary: 'Build target-free registry quality timelines',
132
+ options: ['--json', '--quiet', '--no-color'],
133
+ positionals: OPTIONAL_FILE_POSITIONAL,
134
+ constraints: EMPTY_CONSTRAINTS,
135
+ handlerOwner: 'support',
136
+ networkEffect: 'offline',
137
+ common: false,
138
+ schemaIdentifiers: Object.freeze(['whoisleuth.cli.registry-cohort']),
139
+ primaryArtefacts: Object.freeze([]),
140
+ planSupport: false,
141
+ additionalOutputFormats: Object.freeze([]),
142
+ bootstrapProfile: 'allowed',
143
+ }),
144
+ "registry-scaffold": commandSeed({
145
+ reference: {
146
+ description: 'Create a bounded synthetic WHOIS fixture scaffold for one existing capability profile.',
147
+ example: 'whoisleuth registry-scaffold --profile nic-io-colon --suffix ac --scenario registered',
148
+ boundary: 'The output is a sanitised template only. Its command-owned --profile selects fixture capability, --config is rejected, and contributors must not paste live responses or personal registration data into fixtures.',
149
+ },
150
+ collection: { scope: 'Reads the embedded registry capability catalogue and prints one synthetic fixture template.' },
151
+ summary: 'Create a sanitised registry fixture scaffold',
152
+ options: ['--profile', '--suffix', '--scenario'],
153
+ optionOverrides: { '--profile': text('Select a catalogue profile; find its ID with registry-support <suffix> --json.', true) },
154
+ positionals: NO_POSITIONALS,
155
+ constraints: Object.freeze([
156
+ constraint({ kind: 'required', options: ['--profile', '--suffix', '--scenario'] }),
157
+ ]),
158
+ handlerOwner: 'support',
159
+ networkEffect: 'offline',
160
+ common: false,
161
+ schemaIdentifiers: Object.freeze([]),
162
+ primaryArtefacts: Object.freeze([]),
163
+ planSupport: false,
164
+ additionalOutputFormats: Object.freeze([]),
165
+ bootstrapProfile: 'command_owned',
166
+ }),
167
+ "risk-calibrate": commandSeed({
168
+ reference: {
169
+ description: 'Replay reviewed labels against the current explainable Risk model.',
170
+ example: 'whoisleuth risk-calibrate calibration.json --summary-json',
171
+ boundary: 'Calibration is offline and diagnostic. The summary form omits record identifiers, domains, and evidence; neither form trains, tunes, or changes the scoring model automatically.',
172
+ },
173
+ collection: { scope: 'Reads one bounded reviewed-label dataset and changes no model or evidence.' },
174
+ summary: 'Replay reviewed Risk labels offline',
175
+ options: ['--json', '--summary-json', '--quiet', '--no-color'],
176
+ positionals: OPTIONAL_FILE_POSITIONAL,
177
+ constraints: Object.freeze([
178
+ constraint({ kind: 'mutually_exclusive', options: ['--json', '--summary-json'] }),
179
+ ]),
180
+ handlerOwner: 'support',
181
+ networkEffect: 'offline',
182
+ common: false,
183
+ schemaIdentifiers: Object.freeze([RISK_CALIBRATION_DATASET_SCHEMA, RISK_CALIBRATION_REPORT_SCHEMA]),
184
+ primaryArtefacts: Object.freeze([]),
185
+ planSupport: false,
186
+ additionalOutputFormats: Object.freeze([]),
187
+ bootstrapProfile: 'allowed',
188
+ }),
189
+ "lookalike-calibrate": commandSeed({
190
+ reference: {
191
+ description: 'Summarise reviewed candidate dispositions by mutation family without retaining domains.',
192
+ example: 'whoisleuth lookalike-calibrate reviewed-candidates.json --json',
193
+ boundary: 'Calibration is offline and diagnostic. It omits candidate identifiers, domains, notes, and evidence and never tunes generation or filtering automatically.',
194
+ },
195
+ collection: { scope: 'Reads at most 5,000 reviewed candidate labels from one dataset capped at 2 MiB.' },
196
+ summary: 'Summarise reviewed lookalike yield offline',
197
+ options: ['--json', '--quiet', '--no-color'],
198
+ positionals: OPTIONAL_FILE_POSITIONAL,
199
+ constraints: EMPTY_CONSTRAINTS,
200
+ handlerOwner: 'support',
201
+ networkEffect: 'offline',
202
+ common: false,
203
+ schemaIdentifiers: Object.freeze(['whoisleuth.lookalike-calibration-input', 'whoisleuth.lookalike-calibration']),
204
+ primaryArtefacts: Object.freeze([]),
205
+ planSupport: false,
206
+ additionalOutputFormats: Object.freeze([]),
207
+ bootstrapProfile: 'allowed',
208
+ }),
209
+ });
@@ -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, explainRiskScoreV7, 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";