@slicedearth/whoisleuth-cli 2.3.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 (418) hide show
  1. package/NOTICE +8 -2
  2. package/README.md +19 -15
  3. package/bin/whoisleuth.mjs +8 -1
  4. package/cli/archive-content-digest.mjs +14 -0
  5. package/cli/archive-inspect.mjs +20 -53
  6. package/cli/argument-values.mjs +55 -0
  7. package/cli/arguments.mjs +24 -408
  8. package/cli/artifact-validation/case-response.mjs +65 -44
  9. package/cli/artifact-validation/investigation-capsule.mjs +52 -31
  10. package/cli/artifact-validation/lookup-evidence.mjs +15 -9
  11. package/cli/artifact-validation/signed-review.mjs +136 -68
  12. package/cli/artifact-validation/structure-primitives.mjs +1 -105
  13. package/cli/artifact-verify.mjs +92 -24
  14. package/cli/assurance-arguments.mjs +56 -0
  15. package/cli/assurance-command-definitions.mjs +179 -0
  16. package/cli/assurance-command-runner.mjs +40 -5
  17. package/cli/bagit-folder.mjs +57 -0
  18. package/cli/bagit-review.mjs +23 -0
  19. package/cli/bounded-json.mjs +1 -1
  20. package/cli/bulk-checkpoint.mjs +1 -1
  21. package/cli/bulk-command-runner.mjs +4 -2
  22. package/cli/bulk-output.mjs +29 -3
  23. package/cli/bulk.mjs +2 -3
  24. package/cli/case-command.mjs +212 -0
  25. package/cli/case-pack.mjs +50 -32
  26. package/cli/ci-report.mjs +6 -3
  27. package/cli/collection-arguments.mjs +123 -0
  28. package/cli/collection-command-definitions.mjs +330 -0
  29. package/cli/collection-preflight.mjs +8 -1
  30. package/cli/command-argument-grammar.mjs +8 -206
  31. package/cli/command-definition.mjs +329 -0
  32. package/cli/command-reference.mjs +94 -1357
  33. package/cli/command-seeds.mjs +18 -0
  34. package/cli/compare.mjs +1 -1
  35. package/cli/completion.mjs +298 -79
  36. package/cli/config-profile.mjs +8 -6
  37. package/cli/ct-event-intake.mjs +16 -16
  38. package/cli/discover.mjs +5 -4
  39. package/cli/discovery-observation-snapshot.mjs +46 -18
  40. package/cli/discovery-scan-command-runner.mjs +5 -3
  41. package/cli/discovery-scan.mjs +7 -4
  42. package/cli/discovery-snapshot.mjs +2 -2
  43. package/cli/discovery-workflow.mjs +5 -5
  44. package/cli/domain-control-monitor.mjs +50 -20
  45. package/cli/domain-control-observations.mjs +151 -103
  46. package/cli/errors.mjs +37 -6
  47. package/cli/evidence-arguments.mjs +46 -0
  48. package/cli/evidence-command-definitions.mjs +69 -0
  49. package/cli/evidence-command-runner.mjs +18 -8
  50. package/cli/evidence-signing.mjs +5 -4
  51. package/cli/external-observation-mapping.mjs +9 -10
  52. package/cli/formatters/terminal-command-formats.mjs +6 -0
  53. package/cli/formatters/terminal-lookup.mjs +24 -46
  54. package/cli/formatters/terminal-shared.mjs +5 -3
  55. package/cli/formatters/terminal-technology.mjs +114 -0
  56. package/cli/history-arguments.mjs +39 -0
  57. package/cli/history-command-definitions.mjs +88 -0
  58. package/cli/indicator-set-command.mjs +33 -0
  59. package/cli/inline-command-families.mjs +6 -46
  60. package/cli/inline-command-runner.mjs +4 -31
  61. package/cli/input.mjs +2 -2
  62. package/cli/intake-command.mjs +107 -0
  63. package/cli/interactive-launcher.mjs +2 -160
  64. package/cli/interchange-report.mjs +8 -10
  65. package/cli/investigation-artifacts.mjs +176 -0
  66. package/cli/investigation-checkpoint.mjs +10 -0
  67. package/cli/investigation-folder.mjs +114 -0
  68. package/cli/investigation-manifest.mjs +1 -112
  69. package/cli/investigation-package-review.mjs +103 -0
  70. package/cli/investigation-plan.mjs +38 -147
  71. package/cli/investigation-recipes.mjs +177 -0
  72. package/cli/investigation-run.mjs +256 -35
  73. package/cli/local-document-checkpoint.mjs +170 -0
  74. package/cli/local-mmdb-review.mjs +197 -17
  75. package/cli/lookalike-calibration.mjs +1 -1
  76. package/cli/lookup-brief.mjs +2 -2
  77. package/cli/lookup-browser.mjs +5 -12
  78. package/cli/lookup-command-runner.mjs +33 -39
  79. package/cli/lookup-diff.mjs +47 -12
  80. package/cli/lookup-plan.mjs +16 -14
  81. package/cli/lookup-reconcile.mjs +15 -12
  82. package/cli/lookup-timeline.mjs +7 -6
  83. package/cli/mail-header-review.mjs +24 -247
  84. package/cli/mail-review.mjs +75 -21
  85. package/cli/manual.mjs +1 -0
  86. package/cli/network-arguments.mjs +51 -0
  87. package/cli/network-command-definitions.mjs +130 -0
  88. package/cli/network-command-runner.mjs +45 -31
  89. package/cli/offline-evidence-review.mjs +39 -6
  90. package/cli/output-file.mjs +31 -5
  91. package/cli/posture.mjs +1 -1
  92. package/cli/registry-doctor.mjs +3 -1
  93. package/cli/registry-fixture-scaffold.mjs +1 -1
  94. package/cli/registry-support.mjs +1 -1
  95. package/cli/retained-artifact-diff.mjs +10 -9
  96. package/cli/review-arguments.mjs +131 -0
  97. package/cli/review-command-definitions.mjs +273 -0
  98. package/cli/review-command-runner.mjs +104 -149
  99. package/cli/risk-calibration.mjs +8 -8
  100. package/cli/runner.mjs +74 -37
  101. package/cli/saved-lookup.mjs +2 -2
  102. package/cli/selected-input-worker.mjs +63 -0
  103. package/cli/sharing-review.mjs +8 -80
  104. package/cli/signer-trust.mjs +104 -0
  105. package/cli/source-reliability.mjs +75 -84
  106. package/cli/support-arguments.mjs +49 -0
  107. package/cli/support-command-definitions.mjs +209 -0
  108. package/cli/support-command-runner.mjs +2 -2
  109. package/cli/terminal-input.mjs +168 -0
  110. package/cli/workflow-arguments.mjs +86 -0
  111. package/cli/workflow-command-definitions.mjs +78 -0
  112. package/cli/workflow-command-runner.mjs +75 -13
  113. package/docs/cli-reference.md +103 -30
  114. package/docs/cli.md +510 -25
  115. package/docs/contextual-reviews.md +155 -0
  116. package/lib/abort.mjs +4 -1
  117. package/lib/analyst-taxonomy.mjs +2 -40
  118. package/lib/application-version.mjs +1 -1
  119. package/lib/availability.mjs +27 -754
  120. package/lib/bounded-contract-normalizers.mjs +4 -0
  121. package/lib/bounded-file.mjs +3 -1
  122. package/lib/bounded-html-document.mjs +125 -0
  123. package/lib/bounded-json.mjs +2 -290
  124. package/lib/browser-library-profile.mjs +99 -130
  125. package/lib/browser-library-worker.mjs +112 -0
  126. package/lib/candidate-provenance-bounds.mjs +2 -0
  127. package/lib/classify.mjs +1 -1
  128. package/lib/client-behavior-profile.mjs +3 -3
  129. package/lib/ct-query.mjs +2 -27
  130. package/lib/ct-response-bounds.mjs +2 -9
  131. package/lib/ct-search.mjs +89 -46
  132. package/lib/dmarc-discovery.mjs +62 -0
  133. package/lib/dns-convergence-review.mjs +21 -8
  134. package/lib/dns-delegation-health.mjs +36 -22
  135. package/lib/dns-inheritance-review.mjs +207 -0
  136. package/lib/dns-intelligence.mjs +44 -29
  137. package/lib/dns-mx.mjs +1 -1
  138. package/lib/dns-query-coverage.mjs +40 -0
  139. package/lib/dnssec-evidence-validation.mjs +11 -3
  140. package/lib/domain-assurance.mjs +1 -1
  141. package/lib/domain-change-packet.mjs +18 -14
  142. package/lib/domain-change-review.mjs +40 -16
  143. package/lib/domain-control-flight-recorder.mjs +301 -124
  144. package/lib/domain-control-manifest.mjs +126 -58
  145. package/lib/domain-posture-analysis.mjs +41 -11
  146. package/lib/domain-posture-parsers.mjs +88 -6
  147. package/lib/domain-posture.mjs +120 -32
  148. package/lib/domain-registration.mjs +392 -0
  149. package/lib/evidence-export-privacy.mjs +568 -0
  150. package/lib/evidence-export.mjs +12 -593
  151. package/lib/evidence-report.mjs +7 -2
  152. package/lib/external-intelligence-risk.mjs +2 -124
  153. package/lib/favicon.mjs +117 -41
  154. package/lib/generated/cisa-kev-catalog.mjs +50 -4
  155. package/lib/generated/registrar-standing-catalogue.mjs +18 -10
  156. package/lib/generated/retire-browser-catalog.mjs +2742 -1034
  157. package/lib/generated/{unicode-confusables-17.mjs → unicode-confusables.mjs} +33 -33
  158. package/lib/html-signals.mjs +87 -85
  159. package/lib/http-evidence-bounds.mjs +2 -7
  160. package/lib/http-intelligence.mjs +52 -27
  161. package/lib/idn-confusable-policy.mjs +6 -4
  162. package/lib/idn-confusables.mjs +1 -1
  163. package/lib/interchange-fidelity-registry.mjs +10 -0
  164. package/lib/lookup-cache.mjs +34 -11
  165. package/lib/lookup-child-profile-contract.mjs +24 -15
  166. package/lib/lookup-contract-primitives.mjs +1 -1
  167. package/lib/lookup-diagnostics.mjs +2 -1
  168. package/lib/lookup-network-evidence-bounds.mjs +1 -10
  169. package/lib/lookup-progress.mjs +79 -13
  170. package/lib/lookup-response-contract.mjs +1077 -0
  171. package/lib/lookup-response.mjs +11 -24
  172. package/lib/lookup-source-progress.mjs +24 -35
  173. package/lib/lookup.mjs +49 -28
  174. package/lib/observed-network-context.mjs +43 -27
  175. package/lib/opportunity-scoring.mjs +2 -107
  176. package/lib/outbound-identity.mjs +1 -1
  177. package/lib/outbound-request-bounds.mjs +4 -1
  178. package/lib/page-fingerprints.mjs +87 -134
  179. package/lib/page-role-profile.mjs +12 -8
  180. package/lib/perceptual-hash-comparison.mjs +2 -34
  181. package/lib/perceptual-hash.mjs +79 -24
  182. package/lib/portable-generator.mjs +2 -29
  183. package/lib/project-metadata.mjs +2 -8
  184. package/lib/rdap-bootstrap.mjs +72 -22
  185. package/lib/rdap-client.mjs +26 -14
  186. package/lib/rdap-normalization.mjs +33 -18
  187. package/lib/rdap-search-workbench.mjs +1 -1
  188. package/lib/rdap-transport.mjs +47 -20
  189. package/lib/rdap-types.mjs +2 -1
  190. package/lib/rdap.mjs +18 -3
  191. package/lib/registrable-domain.mjs +2 -36
  192. package/lib/registrar-standing-contract.mjs +2 -1
  193. package/lib/registrar-standing.mjs +9 -13
  194. package/lib/registry-capabilities.mjs +7 -3
  195. package/lib/registry-capability-catalogue.mjs +29 -20
  196. package/lib/registry-dates.mjs +2 -190
  197. package/lib/registry-insights.mjs +56 -18
  198. package/lib/response-policy.mjs +86 -48
  199. package/lib/risk-calibration-summary.mjs +2 -338
  200. package/lib/risk-scoring.mjs +2 -345
  201. package/lib/rpki-evidence.mjs +8 -5
  202. package/lib/safe-fetch.mjs +54 -47
  203. package/lib/scoring-evidence-quality.mjs +2 -107
  204. package/lib/security-txt.mjs +10 -7
  205. package/lib/semantic-version.mjs +2 -54
  206. package/lib/service-binding-dns.mjs +35 -0
  207. package/lib/smtp-transport-review.mjs +75 -34
  208. package/lib/sslbl-certificates.generated.mjs +141 -138
  209. package/lib/static-html-analysis.mjs +117 -146
  210. package/lib/structured-data-identity.mjs +2 -1
  211. package/lib/technology-markup.mjs +100 -0
  212. package/lib/threat-intelligence-manifests.mjs +11 -139
  213. package/lib/threat-intelligence-runtime.mjs +24 -52
  214. package/lib/threat-intelligence-targets.mjs +6 -34
  215. package/lib/threat-intelligence-types.mjs +2 -36
  216. package/lib/typosquat-generator.mjs +29 -16
  217. package/lib/urlhaus-intelligence.mjs +8 -3
  218. package/lib/urlscan-intelligence.mjs +1 -1
  219. package/lib/website-enrichment.mjs +388 -0
  220. package/lib/website-security-posture.mjs +30 -6
  221. package/lib/website-technology.mjs +111 -71
  222. package/lib/whois-authority.mjs +55 -5
  223. package/lib/whois-chain.mjs +9 -2
  224. package/lib/whois-common-formats.mjs +5 -5
  225. package/lib/whois-contracts.mjs +2 -1
  226. package/lib/whois-dialects.mjs +2 -2
  227. package/lib/whois-normalization.mjs +3 -1
  228. package/lib/whois-parser-support.mjs +8 -1
  229. package/lib/whois-parser.mjs +5 -3
  230. package/lib/whois-transport.mjs +23 -7
  231. package/package.json +14 -14
  232. package/packages/analysis/analyst-taxonomy.mjs +40 -0
  233. package/packages/analysis/bounded-json.mjs +330 -0
  234. package/packages/analysis/candidate-provenance-bounds.mjs +4 -0
  235. package/packages/analysis/cli-argument-grammar.mjs +216 -0
  236. package/packages/analysis/ct-query.mjs +27 -0
  237. package/packages/analysis/ct-response-bounds.mjs +9 -0
  238. package/packages/analysis/external-intelligence-risk.mjs +124 -0
  239. package/packages/analysis/http-evidence-bounds.mjs +8 -0
  240. package/packages/analysis/lookup-network-evidence-bounds.mjs +10 -0
  241. package/packages/analysis/opportunity-scoring.mjs +107 -0
  242. package/packages/analysis/perceptual-hash-comparison.mjs +34 -0
  243. package/packages/analysis/portable-generator.mjs +29 -0
  244. package/packages/analysis/project-metadata.mjs +8 -0
  245. package/packages/analysis/registrable-domain.mjs +56 -0
  246. package/packages/analysis/risk-calibration-summary.mjs +352 -0
  247. package/packages/analysis/risk-scoring.mjs +345 -0
  248. package/packages/analysis/scoring-evidence-quality.mjs +107 -0
  249. package/packages/analysis/semantic-version.mjs +54 -0
  250. package/packages/analysis/threat-intelligence-types.mjs +75 -0
  251. package/packages/cases/case-attachment-model.mjs +83 -0
  252. package/packages/cases/case-evidence-links.mjs +135 -0
  253. package/packages/cases/case-evidence-model.mjs +229 -69
  254. package/packages/cases/case-evidence-quality.mjs +5 -0
  255. package/packages/cases/case-export-input.mjs +55 -0
  256. package/packages/cases/case-incident-context.mjs +52 -0
  257. package/packages/cases/case-markdown.mjs +11 -0
  258. package/packages/cases/case-migration-model.mjs +98 -48
  259. package/packages/cases/case-model.mjs +1 -0
  260. package/packages/cases/case-recheck-model.mjs +149 -0
  261. package/packages/cases/case-record-contracts.mjs +1 -1
  262. package/packages/cases/case-record-core.mjs +4 -3
  263. package/packages/cases/case-record-operations.mjs +123 -133
  264. package/packages/cases/case-record-projection.mjs +30 -20
  265. package/packages/cases/case-record-version-input.mjs +19 -0
  266. package/packages/cases/case-report.mjs +114 -67
  267. package/packages/cases/case-requested-evidence.mjs +146 -0
  268. package/packages/cases/case-response-actions.mjs +756 -0
  269. package/packages/cases/case-response-model.mjs +49 -1129
  270. package/packages/cases/case-response-outcomes.mjs +338 -0
  271. package/packages/cases/case-response-packet-render.mjs +157 -0
  272. package/packages/cases/case-response-packet-types.mjs +1 -0
  273. package/packages/cases/case-response-packet-vocabulary.mjs +48 -0
  274. package/packages/cases/case-response-packet.mjs +58 -593
  275. package/packages/cases/case-response-records.mjs +93 -0
  276. package/packages/cases/case-response-review-inputs.mjs +382 -0
  277. package/packages/cases/case-response-values.mjs +85 -0
  278. package/packages/cases/case-review-package.mjs +45 -0
  279. package/packages/cases/case-selection.mjs +46 -0
  280. package/packages/cases/case-storage-model.mjs +26 -1
  281. package/packages/cases/case-workflow-metadata.mjs +225 -0
  282. package/packages/cases/case-workflow-migration.mjs +43 -0
  283. package/packages/cases/http-summary.mjs +6 -1
  284. package/packages/cases/response-route-freshness.mjs +16 -0
  285. package/packages/comparison/capture-context.mjs +61 -0
  286. package/packages/comparison/comparison-ledger-bulk.mjs +4 -1
  287. package/packages/comparison/favicon-similarity.mjs +1 -1
  288. package/packages/comparison/page-similarity.mjs +3 -2
  289. package/packages/comparison/relationship-evidence.mjs +103 -14
  290. package/packages/comparison/relationship-provenance.mjs +99 -0
  291. package/packages/contracts/analyst-interchange.mjs +234 -0
  292. package/packages/contracts/analyst-review-state-contract.mjs +11 -0
  293. package/packages/contracts/analyst-review-state.mjs +5 -4
  294. package/packages/contracts/case-portability.mjs +370 -114
  295. package/packages/contracts/case-views-contract.mjs +12 -0
  296. package/packages/contracts/case-views.mjs +26 -0
  297. package/packages/contracts/cli-command-semantics.mjs +3 -0
  298. package/packages/contracts/cli-grammar.mjs +6 -0
  299. package/packages/contracts/cli-lookup-brief.mjs +2 -0
  300. package/packages/contracts/cli-lookup.mjs +0 -1
  301. package/packages/contracts/context-review.mjs +24 -0
  302. package/packages/contracts/document-intake.mjs +11 -0
  303. package/packages/contracts/domain-control-flight-recorder.mjs +98 -44
  304. package/packages/contracts/domain-control-manifest.mjs +128 -74
  305. package/packages/contracts/domain-control-monitor.mjs +59 -45
  306. package/packages/contracts/domain-control-review.mjs +200 -121
  307. package/packages/contracts/domain-name.mjs +21 -0
  308. package/packages/contracts/external-observation-interchange.mjs +2 -1
  309. package/packages/contracts/extracted-domain-lifecycle.mjs +11 -32
  310. package/packages/contracts/har-review.mjs +2 -0
  311. package/packages/contracts/http-url.mjs +2 -0
  312. package/packages/contracts/identity-events.mjs +4 -0
  313. package/packages/contracts/investigation-package-limits.mjs +24 -0
  314. package/packages/contracts/investigation-portability.mjs +56 -27
  315. package/packages/contracts/investigation-run.mjs +18 -0
  316. package/packages/contracts/ip-address.mjs +44 -0
  317. package/packages/contracts/lookup-evidence.mjs +35 -0
  318. package/packages/contracts/mail-authentication.mjs +5 -0
  319. package/packages/contracts/message-intake.mjs +31 -0
  320. package/packages/contracts/offline-comparison.mjs +4 -2
  321. package/packages/contracts/page-fingerprints.mjs +15 -0
  322. package/packages/contracts/passive-mail.mjs +10 -0
  323. package/packages/contracts/risk-calibration.mjs +0 -1
  324. package/packages/contracts/schema-compatibility.mjs +1 -1
  325. package/packages/contracts/schema-lifecycle.mjs +83 -153
  326. package/packages/contracts/selected-file-limits.mjs +6 -0
  327. package/packages/contracts/vulnerability-identifiers.mjs +5 -0
  328. package/packages/contracts/web-capture.mjs +10 -0
  329. package/packages/contracts/workspace-portability.mjs +219 -238
  330. package/packages/evidence/artifact-integrity.mjs +8 -2
  331. package/packages/evidence/artifact-structure.mjs +105 -0
  332. package/packages/evidence/collection-quality.mjs +67 -0
  333. package/packages/evidence/decision-fact.mjs +4 -1
  334. package/packages/evidence/dns-query-outcome.mjs +72 -0
  335. package/packages/evidence/domain-control-runtime.mjs +155 -27
  336. package/packages/evidence/domain-posture-context.mjs +79 -0
  337. package/packages/evidence/email-recipient.mjs +18 -0
  338. package/packages/evidence/image-regions.mjs +56 -0
  339. package/packages/evidence/latest-observations.mjs +28 -0
  340. package/packages/evidence/lookup-target.mjs +108 -0
  341. package/packages/evidence/observation.mjs +18 -1
  342. package/packages/evidence/passphrase-encryption.mjs +44 -0
  343. package/packages/evidence/png-pixels.mjs +117 -0
  344. package/packages/evidence/public-address-policy.mjs +83 -0
  345. package/packages/evidence/record-identity.mjs +72 -0
  346. package/packages/evidence/registry-dates.mjs +190 -0
  347. package/packages/evidence/retained-file.mjs +69 -0
  348. package/packages/evidence/review-age.mjs +14 -0
  349. package/packages/interchange/bagit.mjs +342 -0
  350. package/packages/interchange/bounded-zip-extraction.mjs +278 -0
  351. package/packages/interchange/crc32.mjs +17 -0
  352. package/packages/interchange/defensive-indicator-export.mjs +264 -0
  353. package/packages/interchange/external-findings-import.mjs +93 -61
  354. package/packages/interchange/managed-indicator-export.mjs +35 -0
  355. package/packages/interchange/managed-indicator-set.mjs +210 -0
  356. package/packages/interchange/misp-indicator-export.mjs +93 -0
  357. package/packages/interchange/sharing-policy.mjs +56 -0
  358. package/packages/interchange/stix-indicator-export.mjs +129 -0
  359. package/packages/interchange/web-capture-import.mjs +389 -0
  360. package/packages/investigation/acquisition-decision-packet.mjs +1 -1
  361. package/packages/investigation/bulk-mail-exposure.mjs +9 -14
  362. package/packages/investigation/capture-coverage.mjs +57 -0
  363. package/packages/investigation/connector-provenance-review.mjs +100 -0
  364. package/packages/investigation/context-review.mjs +35 -0
  365. package/packages/investigation/document-intake.mjs +81 -0
  366. package/packages/investigation/docx-intake.mjs +133 -0
  367. package/packages/investigation/domain-history-review.mjs +100 -0
  368. package/packages/investigation/domain-transition-review.mjs +47 -0
  369. package/packages/investigation/evidence-coverage-ledger.mjs +9 -1
  370. package/packages/investigation/har-intake.mjs +68 -0
  371. package/packages/investigation/identity-event-intake.mjs +131 -0
  372. package/packages/investigation/identity-incident-review.mjs +39 -0
  373. package/packages/investigation/incident-sequence-review.mjs +58 -0
  374. package/packages/investigation/intake-report.mjs +19 -0
  375. package/packages/investigation/investigation-bagit.mjs +14 -0
  376. package/packages/investigation/investigation-capsule.mjs +33 -5
  377. package/packages/investigation/investigation-manifest.mjs +200 -0
  378. package/packages/investigation/investigation-package-crypto.mjs +98 -0
  379. package/packages/investigation/investigation-package.mjs +200 -0
  380. package/packages/investigation/link-intake.mjs +179 -0
  381. package/packages/investigation/lookup-asset-graph-inputs.mjs +38 -0
  382. package/packages/investigation/lookup-asset-graph.mjs +161 -141
  383. package/packages/investigation/lookup-claim-readiness.mjs +18 -0
  384. package/packages/investigation/lookup-investigation-brief.mjs +3 -3
  385. package/packages/investigation/lookup-task-guidance.mjs +58 -0
  386. package/packages/investigation/mail-authentication-review.mjs +222 -0
  387. package/packages/investigation/mail-header-identity.mjs +209 -0
  388. package/packages/investigation/message-intake.mjs +195 -0
  389. package/packages/investigation/page-behaviour.mjs +113 -0
  390. package/packages/investigation/pdf-intake.mjs +155 -0
  391. package/packages/investigation/platform-continuity-review.mjs +60 -0
  392. package/packages/investigation/qr-intake.mjs +56 -0
  393. package/packages/investigation/requested-action-hints.mjs +11 -0
  394. package/packages/investigation/selected-input-review.mjs +17 -0
  395. package/packages/investigation/storefront-review.mjs +70 -0
  396. package/packages/monitoring/analyst-review-state.mjs +38 -90
  397. package/packages/workspace/brand-profile-model.mjs +160 -22
  398. package/packages/workspace/bulk-columns.mjs +17 -0
  399. package/packages/workspace/bulk-review-model.mjs +39 -31
  400. package/packages/workspace/bulk-session-model.mjs +112 -27
  401. package/packages/workspace/bulk-sort.mjs +1 -0
  402. package/packages/workspace/campaign-model.mjs +1 -1
  403. package/packages/workspace/case-views.mjs +102 -0
  404. package/packages/workspace/detection-rule-model.mjs +1 -1
  405. package/packages/workspace/hostile-input.mjs +29 -8
  406. package/packages/workspace/investigation-guide.mjs +2 -2
  407. package/packages/workspace/investigation-template-model.mjs +45 -6
  408. package/packages/workspace/page-baseline.mjs +20 -17
  409. package/packages/workspace/relationship-observation-model.mjs +54 -35
  410. package/packages/workspace/shortlist-model.mjs +14 -5
  411. package/packages/workspace/watchlist-history.mjs +30 -14
  412. package/packages/workspace/watchlist-store.mjs +24 -6
  413. package/packages/workspace/website-snapshot-model.mjs +98 -33
  414. package/packages/workspace/workspace-archive-crypto.mjs +41 -61
  415. package/packages/workspace/workspace-archive.mjs +90 -25
  416. package/third-party-notices.txt +577 -17
  417. package/lib/hostname.mjs +0 -38
  418. package/packages/investigation/bulk-review-export.mjs +0 -63
@@ -1,14 +1,20 @@
1
1
  import { ACQUISITION_DECISIONS, ACQUISITION_MANUAL_CHECKS, ACQUISITION_DECISION_PACKET_SCHEMA, ACQUISITION_DECISION_PACKET_VERSION, } from "../../packages/investigation/acquisition-decision-packet.mjs";
2
2
  import { BULK_DOMAIN_COMPARISON_EXPORT_VERSION, BULK_DOMAIN_COMPARISON_SCHEMA, } from "../../packages/investigation/bulk-domain-comparison.mjs";
3
3
  import { BULK_MAIL_EXPOSURE_EXPORT_VERSION, BULK_MAIL_EXPOSURE_SCHEMA, MAX_BULK_MAIL_EXPOSURE_ROWS, } from "../../packages/investigation/bulk-mail-exposure.mjs";
4
- import { BULK_REVIEW_MANIFEST_SCHEMA, BULK_REVIEW_MANIFEST_VERSION, } from "../../packages/investigation/bulk-review-export.mjs";
4
+ import { BULK_REVIEW_MANIFEST_SCHEMA, BULK_REVIEW_MANIFEST_VERSION, SUPPORTED_BULK_REVIEW_MANIFEST_VERSIONS, } from "../../packages/contracts/investigation-portability.mjs";
5
+ import { MAX_BULK_SESSION_ROWS, MAX_BULK_SESSION_SOURCES } from "../../packages/contracts/workspace-portability.mjs";
6
+ import { MANAGED_INDICATOR_SET_SCHEMA } from "../../packages/contracts/analyst-interchange.mjs";
7
+ import { validateManagedIndicatorSet } from "../../packages/interchange/managed-indicator-set.mjs";
8
+ import { BULK_SORT_KEYS } from "../../packages/workspace/bulk-sort.mjs";
9
+ import { normalizeExplicitIsoTimestamp } from "../../packages/evidence/observation.mjs";
5
10
  import { LOOKUP_CLAIM_PASSPORT_SCHEMA, LOOKUP_CLAIM_PASSPORT_TARGET_TYPES, LOOKUP_CLAIM_PASSPORT_VERSION, MAX_LOOKUP_CLAIM_PASSPORT_LIMITATIONS, MAX_LOOKUP_CLAIM_PASSPORT_REQUIREMENTS, } from "../../packages/investigation/lookup-claim-passport.mjs";
6
11
  import { LOOKUP_CLAIM_IDS, LOOKUP_CLAIM_READINESS_VERSION, LOOKUP_CLAIM_REQUIREMENT_IDS, } from "../../packages/investigation/lookup-claim-readiness.mjs";
7
12
  import { normalizeDomainControlManifestDocument } from "../../packages/evidence/domain-control-runtime.mjs";
8
- import { DOMAIN_CHANGE_PACKET_SCHEMA, DOMAIN_CHANGE_PACKET_VERSION, } from "../../lib/domain-change-packet.mjs";
13
+ import { DOMAIN_CHANGE_PACKET_SCHEMA, DOMAIN_CHANGE_PACKET_REVIEW_VERSIONS, } from "../../lib/domain-change-packet.mjs";
14
+ import { DOMAIN_CHANGE_REVIEW_SCHEMA } from "../../lib/domain-change-review.mjs";
9
15
  import { DOMAIN_CONTROL_MANIFEST_SCHEMA } from "../../packages/contracts/domain-control-manifest.mjs";
10
- import { INVESTIGATION_MANIFEST_SCHEMA, INVESTIGATION_MANIFEST_VERSION, MAX_INVESTIGATION_MANIFEST_ARTIFACTS, MAX_INVESTIGATION_MANIFEST_TOTAL_BYTES, } from "../investigation-manifest.mjs";
11
- import { array, boolean, domain, digest, enumeration, exact, fail, integer, iso, optionalText, sameValues, strings, text, validateIntegrity, } from "./structure-primitives.mjs";
16
+ import { INVESTIGATION_MANIFEST_SCHEMA, validateInvestigationManifest, } from "../investigation-manifest.mjs";
17
+ import { array, boolean, domain, enumeration, exact, fail, integer, iso, optionalText, sameValues, strings, text, validateIntegrity, } from "./structure-primitives.mjs";
12
18
  const CLAIM_PASSPORT_SOURCE_STATES = ['complete', 'not_found', 'partial', 'skipped', 'unavailable', 'unknown', 'unsupported'];
13
19
  const COMPARISON_STATES = ['conflicting', 'different', 'equal', 'missing', 'not_recorded', 'unavailable'];
14
20
  const SOURCE_STATES = ['complete', 'error', 'not_found', 'not_recorded', 'partial', 'skipped', 'unavailable', 'unsupported'];
@@ -209,11 +215,17 @@ function validateDomainComparison(value) {
209
215
  strings(comparison.limitations, 'Domain comparison limitations', 12, 600);
210
216
  validateIntegrity(root.integrity, 'Domain comparison integrity', root.version, BULK_DOMAIN_COMPARISON_EXPORT_VERSION);
211
217
  }
212
- function validateSourceCoverage(value, label) {
213
- const source = exact(value, ['source', 'state'], label);
218
+ function validateNullableObservationTime(value, label) {
219
+ if (value !== null && normalizeExplicitIsoTimestamp(value) !== value)
220
+ fail(label);
221
+ }
222
+ function validateSourceCoverage(value, label, observedTime = false) {
223
+ const source = exact(value, ['source', 'state', ...(observedTime ? ['observedAt'] : [])], label);
214
224
  if (typeof source.source !== 'string' || !/^[a-z][a-z0-9_-]{0,39}$/u.test(source.source))
215
225
  fail(label);
216
226
  enumeration(source.state, SOURCE_STATES.filter((state) => state !== 'not_recorded'), label);
227
+ if (observedTime)
228
+ validateNullableObservationTime(source.observedAt, `${label} observedAt`);
217
229
  }
218
230
  function validateProfileContext(value, label) {
219
231
  const context = exact(value, ['sourceState', 'activeProfileId', 'profileUpdatedAt', 'limitation'], label);
@@ -281,29 +293,40 @@ function validateBulkView(value, label) {
281
293
  text(view.caseDispositionFilter, label, 60, true);
282
294
  enumeration(view.reviewStateFilter, ['', 'unreviewed', 'reviewing', 'reviewed', 'deferred'], label);
283
295
  text(view.groupBy, label, 60, true);
284
- enumeration(view.sortKey, ['domain', 'availability', 'risk', 'opportunity', 'activity', 'registrar', 'mutation'], label);
296
+ enumeration(view.sortKey, BULK_SORT_KEYS, label);
285
297
  if (view.sortDirection !== 1 && view.sortDirection !== -1)
286
298
  fail(label);
287
299
  }
288
300
  function validateBulkReviewManifest(value) {
289
301
  const root = exact(value, ['schema', 'version', 'generatedAt', 'observedAt', 'lookupProfile', 'selection', 'view', 'rows', 'limitations', 'integrity'], 'Bulk review manifest');
302
+ const version = SUPPORTED_BULK_REVIEW_MANIFEST_VERSIONS.find((candidate) => candidate === root.version);
303
+ if (version === undefined)
304
+ fail('Bulk review manifest version');
305
+ const sourceTimes = version === BULK_REVIEW_MANIFEST_VERSION;
306
+ const maximumRows = sourceTimes ? MAX_BULK_SESSION_ROWS : 2_000;
307
+ const maximumSources = sourceTimes ? MAX_BULK_SESSION_SOURCES : 12;
290
308
  iso(root.generatedAt, 'Bulk review manifest generatedAt');
291
- iso(root.observedAt, 'Bulk review manifest observedAt');
309
+ if (sourceTimes)
310
+ validateNullableObservationTime(root.observedAt, 'Bulk review manifest observedAt');
311
+ else
312
+ iso(root.observedAt, 'Bulk review manifest observedAt');
292
313
  enumeration(root.lookupProfile, ['deep', 'fast'], 'Bulk review manifest lookup profile');
293
314
  const selection = exact(root.selection, ['count', 'domains'], 'Bulk review manifest selection');
294
- const domains = strings(selection.domains, 'Bulk review manifest selected domains', 2_000, 253);
315
+ const domains = strings(selection.domains, 'Bulk review manifest selected domains', maximumRows, 253);
295
316
  domains.forEach((item) => domain(item, 'Bulk review manifest selected domain'));
296
- if (integer(selection.count, 'Bulk review manifest selection count', 0, 2_000) !== domains.length)
317
+ if (integer(selection.count, 'Bulk review manifest selection count', 0, maximumRows) !== domains.length)
297
318
  fail('Bulk review manifest selection');
298
319
  validateBulkView(root.view, 'Bulk review manifest view');
299
- const rows = array(root.rows, 'Bulk review manifest rows', 2_000);
320
+ const rows = array(root.rows, 'Bulk review manifest rows', maximumRows);
300
321
  for (const [index, candidate] of rows.entries()) {
301
- const row = exact(candidate, ['domain', 'reviewState', 'resultState', 'scanDepth', 'sourceCoverage', 'profileContext'], `Bulk review manifest row ${index + 1}`);
322
+ const row = exact(candidate, ['domain', 'reviewState', 'resultState', 'scanDepth', ...(sourceTimes ? ['observedAt'] : []), 'sourceCoverage', 'profileContext'], `Bulk review manifest row ${index + 1}`);
302
323
  domain(row.domain, 'Bulk review manifest row domain');
303
324
  enumeration(row.reviewState, ['unreviewed', 'reviewing', 'reviewed', 'deferred'], 'Bulk review manifest review state');
304
325
  enumeration(row.resultState, ['complete', 'error'], 'Bulk review manifest result state');
305
326
  enumeration(row.scanDepth, ['deep', 'fast'], 'Bulk review manifest scan depth');
306
- array(row.sourceCoverage, 'Bulk review manifest source coverage', 12).forEach((item, sourceIndex) => validateSourceCoverage(item, `Bulk review manifest source ${sourceIndex + 1}`));
327
+ if (sourceTimes)
328
+ validateNullableObservationTime(row.observedAt, 'Bulk review manifest row observedAt');
329
+ array(row.sourceCoverage, 'Bulk review manifest source coverage', maximumSources).forEach((item, sourceIndex) => validateSourceCoverage(item, `Bulk review manifest source ${sourceIndex + 1}`, sourceTimes));
307
330
  validateProfileContext(row.profileContext, 'Bulk review manifest profile context');
308
331
  if (domains[index] !== row.domain)
309
332
  fail('Bulk review manifest selection');
@@ -311,59 +334,81 @@ function validateBulkReviewManifest(value) {
311
334
  if (rows.length !== domains.length)
312
335
  fail('Bulk review manifest selection');
313
336
  strings(root.limitations, 'Bulk review manifest limitations', 8, 600);
314
- validateIntegrity(root.integrity, 'Bulk review manifest integrity', root.version, BULK_REVIEW_MANIFEST_VERSION);
337
+ validateIntegrity(root.integrity, 'Bulk review manifest integrity', root.version, version);
315
338
  }
316
- function validateInvestigationManifest(value) {
317
- const root = exact(value, ['schema', 'version', 'generatedAt', 'application', 'workflow', 'configuration', 'artifacts', 'steps', 'summary', 'limitations', 'integrity'], 'Investigation manifest');
318
- iso(root.generatedAt, 'Investigation manifest generatedAt');
319
- const application = exact(root.application, ['name', 'version'], 'Investigation manifest application');
320
- if (application.name !== 'WHOISleuth CLI' || typeof application.version !== 'string'
321
- || !/^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/u.test(application.version))
322
- fail('Investigation manifest application');
323
- text(root.workflow, 'Investigation manifest workflow', 160);
324
- const configuration = exact(root.configuration, ['digestSha256'], 'Investigation manifest configuration');
325
- if (configuration.digestSha256 !== null)
326
- digest(configuration.digestSha256, 'Investigation manifest configuration');
327
- const artifacts = array(root.artifacts, 'Investigation manifest artifacts', MAX_INVESTIGATION_MANIFEST_ARTIFACTS, 1);
328
- const steps = array(root.steps, 'Investigation manifest steps', MAX_INVESTIGATION_MANIFEST_ARTIFACTS, 1);
329
- let totalBytes = 0;
330
- for (const [index, candidate] of artifacts.entries()) {
331
- const item = exact(candidate, ['sequence', 'id', 'schema', 'version', 'byteLength', 'contentDigestSha256', 'canonicalDigestSha256'], `Investigation manifest artifact ${index + 1}`);
332
- if (integer(item.sequence, 'Investigation manifest artifact sequence', 1, artifacts.length) !== index + 1
333
- || item.id !== `artifact-${index + 1}`)
334
- fail('Investigation manifest artifact order');
335
- if (item.schema !== null)
336
- text(item.schema, 'Investigation manifest artifact schema', 160);
337
- if (item.version !== null)
338
- integer(item.version, 'Investigation manifest artifact version', 1, 1_000);
339
- totalBytes += integer(item.byteLength, 'Investigation manifest artifact bytes', 1, 15 * 1024 * 1024);
340
- digest(item.contentDigestSha256, 'Investigation manifest content digest');
341
- digest(item.canonicalDigestSha256, 'Investigation manifest canonical digest');
342
- const step = exact(steps[index], ['sequence', 'artifactId', 'contentDigestSha256'], `Investigation manifest step ${index + 1}`);
343
- if (step.sequence !== item.sequence || step.artifactId !== item.id || step.contentDigestSha256 !== item.contentDigestSha256)
344
- fail('Investigation manifest step linkage');
345
- }
346
- const summary = exact(root.summary, ['artifactCount', 'totalBytes'], 'Investigation manifest summary');
347
- if (integer(summary.artifactCount, 'Investigation manifest artifact count', 1, MAX_INVESTIGATION_MANIFEST_ARTIFACTS) !== artifacts.length
348
- || integer(summary.totalBytes, 'Investigation manifest total bytes', 1, MAX_INVESTIGATION_MANIFEST_TOTAL_BYTES) !== totalBytes)
349
- fail('Investigation manifest summary');
350
- strings(root.limitations, 'Investigation manifest limitations', 8, 600);
351
- validateIntegrity(root.integrity, 'Investigation manifest integrity', root.version, INVESTIGATION_MANIFEST_VERSION);
339
+ function validateSourceObservations(value, label, queryCoverage) {
340
+ const names = new Set();
341
+ let queryCount = 0;
342
+ return array(value, label, 16).map((candidate) => {
343
+ const source = exact(candidate, ['label', 'source', 'state', 'observedAt', ...(queryCoverage ? ['queries'] : [])], label);
344
+ const name = text(source.label, `${label} label`, 120).toLowerCase();
345
+ if (names.has(name))
346
+ fail(`${label} duplicate label`);
347
+ names.add(name);
348
+ text(source.source, `${label} source`, 240);
349
+ enumeration(source.state, ['observed', 'partial', 'unavailable'], `${label} state`);
350
+ iso(source.observedAt, `${label} observedAt`);
351
+ if (queryCoverage) {
352
+ const keys = new Set();
353
+ const queries = array(source.queries, `${label} queries`, 500 - queryCount);
354
+ queryCount += queries.length;
355
+ for (const candidate of queries) {
356
+ const query = exact(candidate, ['owner', 'type', 'state'], `${label} query`);
357
+ text(query.owner, `${label} query owner`, 253);
358
+ enumeration(query.type, DNS_TYPES, `${label} query type`);
359
+ enumeration(query.state, ['observed', 'partial', 'unavailable'], `${label} query state`);
360
+ const key = `${query.owner}\u0000${query.type}`;
361
+ if (keys.has(key))
362
+ fail(`${label} duplicate query`);
363
+ keys.add(key);
364
+ if ((source.state === 'observed' && query.state !== 'observed')
365
+ || (source.state === 'unavailable' && query.state !== 'unavailable'))
366
+ fail(`${label} query state`);
367
+ }
368
+ }
369
+ return source;
370
+ });
352
371
  }
353
- function validateReviewMatrix(value, label) {
372
+ function validateReviewMatrix(value, label, sources, queryCoverage = false) {
373
+ const rowKeys = new Set();
354
374
  for (const [rowIndex, candidate] of array(value, label, 500).entries()) {
355
375
  const row = exact(candidate, ['owner', 'type', 'state', 'observations'], `${label} row ${rowIndex + 1}`);
356
376
  text(row.owner, `${label} owner`, 253);
357
377
  enumeration(row.type, DNS_TYPES, `${label} type`);
378
+ const key = `${row.owner}\u0000${row.type}`;
379
+ if (queryCoverage && rowKeys.has(key))
380
+ fail(`${label} duplicate row`);
381
+ rowKeys.add(key);
358
382
  const observations = array(row.observations, `${label} observations`, 16);
383
+ if (sources && observations.length !== sources.length)
384
+ fail(`${label} source coverage`);
359
385
  let complete = 0;
360
386
  const signatures = new Set();
387
+ const names = new Set();
361
388
  for (const observationCandidate of observations) {
362
- const observation = exact(observationCandidate, ['label', 'source', 'state', 'values', 'ttlRange'], `${label} observation`);
389
+ const observation = exact(observationCandidate, ['label', 'source', 'state', 'values', 'ttlRange', ...(sources ? ['observedAt'] : [])], `${label} observation`);
363
390
  text(observation.label, `${label} observation label`, 120);
364
391
  text(observation.source, `${label} observation source`, 240);
365
- const state = enumeration(observation.state, ['observed', 'partial', 'unavailable'], `${label} observation state`);
392
+ const state = enumeration(observation.state, ['observed', 'partial', 'unavailable', ...(queryCoverage ? ['not_queried'] : [])], `${label} observation state`);
366
393
  const values = strings(observation.values, `${label} observation values`, 500, 16_384);
394
+ if (sources) {
395
+ const name = String(observation.label).toLowerCase();
396
+ if (names.has(name))
397
+ fail(`${label} duplicate observation`);
398
+ names.add(name);
399
+ const source = sources.find((item) => item.label === observation.label);
400
+ const provenance = queryCoverage ? ['label', 'source', 'observedAt'] : ['label', 'source', 'state', 'observedAt'];
401
+ if (!source || provenance.some((key) => observation[key] !== source[key]))
402
+ fail(`${label} source provenance`);
403
+ if (queryCoverage) {
404
+ const query = source.queries.find((item) => item.owner === row.owner && item.type === row.type);
405
+ if (state !== (query?.state ?? 'not_queried'))
406
+ fail(`${label} query provenance`);
407
+ if (['not_queried', 'unavailable'].includes(state) && (values.length || observation.ttlRange !== null))
408
+ fail(`${label} unavailable values`);
409
+ }
410
+ iso(observation.observedAt, `${label} observation time`);
411
+ }
367
412
  if (state === 'observed') {
368
413
  complete += 1;
369
414
  signatures.add(JSON.stringify(values));
@@ -376,20 +421,28 @@ function validateReviewMatrix(value, label) {
376
421
  fail(`${label} TTL range`);
377
422
  }
378
423
  }
379
- const expected = complete < 2 ? 'insufficient' : signatures.size === 1 ? 'aligned' : 'different';
424
+ const expected = complete < 2 || (queryCoverage && complete !== observations.length) ? 'insufficient' : signatures.size === 1 ? 'aligned' : 'different';
380
425
  if (row.state !== expected)
381
426
  fail(`${label} state`);
382
427
  }
428
+ if (queryCoverage && sources) {
429
+ const expectedKeys = new Set(sources.flatMap((source) => source.queries.map((item) => `${item.owner}\u0000${item.type}`)));
430
+ if (rowKeys.size !== expectedKeys.size || [...rowKeys].some((key) => !expectedKeys.has(key)))
431
+ fail(`${label} query coverage`);
432
+ }
383
433
  }
384
- function validateDomainChangeReview(value, label) {
385
- const review = exact(value, ['schema', 'version', 'generatedAt', 'domain', 'state', 'authoritativeRecordMatrix', 'resolverDivergenceMatrix', 'dnssecAutomation', 'acmeDependencies', 'certificate', 'services', 'hsts', 'gate', 'limitations'], label);
386
- if (review.schema !== 'whoisleuth.domain-change.review' || review.version !== 1)
434
+ function validateDomainChangeReview(value, label, version) {
435
+ const retainedTimes = version >= 2;
436
+ const queryCoverage = version >= 3;
437
+ const review = exact(value, ['schema', 'version', 'generatedAt', 'domain', 'state', 'authoritativeRecordMatrix', 'resolverDivergenceMatrix', 'dnssecAutomation', 'acmeDependencies', 'certificate', 'services', 'hsts', 'gate', 'limitations', ...(retainedTimes ? ['sourceObservations'] : [])], label);
438
+ if (review.schema !== DOMAIN_CHANGE_REVIEW_SCHEMA || review.version !== version)
387
439
  fail(label);
388
440
  iso(review.generatedAt, `${label} generatedAt`);
389
441
  domain(review.domain, `${label} domain`);
390
442
  enumeration(review.state, ['ready', 'review'], `${label} state`);
391
- validateReviewMatrix(review.authoritativeRecordMatrix, `${label} authority matrix`);
392
- validateReviewMatrix(review.resolverDivergenceMatrix, `${label} resolver matrix`);
443
+ const sources = retainedTimes ? exact(review.sourceObservations, ['authorities', 'resolvers'], `${label} source observations`) : null;
444
+ validateReviewMatrix(review.authoritativeRecordMatrix, `${label} authority matrix`, sources ? validateSourceObservations(sources.authorities, `${label} authority sources`, queryCoverage) : undefined, queryCoverage);
445
+ validateReviewMatrix(review.resolverDivergenceMatrix, `${label} resolver matrix`, sources ? validateSourceObservations(sources.resolvers, `${label} resolver sources`, queryCoverage) : undefined, queryCoverage);
393
446
  const automation = exact(review.dnssecAutomation, ['state', 'cdsObserved', 'cdnskeyObserved', 'csyncObserved', 'conflictingTypes', 'detail'], `${label} DNSSEC automation`);
394
447
  enumeration(automation.state, ['not_observed', 'conflict', 'partial', 'review_ready'], `${label} DNSSEC automation`);
395
448
  boolean(automation.cdsObserved, `${label} CDS observed`);
@@ -405,8 +458,13 @@ function validateDomainChangeReview(value, label) {
405
458
  optionalText(dependency.provider, `${label} ACME provider`, 120);
406
459
  enumeration(dependency.state, ['confirmed', 'partial', 'unknown'], `${label} ACME state`);
407
460
  }
408
- const certificate = exact(review.certificate, ['state', 'continuity', 'findings'], `${label} certificate`);
461
+ const certificate = exact(review.certificate, ['state', 'continuity', 'findings', ...(retainedTimes ? ['observedAt'] : [])], `${label} certificate`);
409
462
  enumeration(certificate.state, ['not_supplied', 'observed', 'partial', 'unavailable'], `${label} certificate state`);
463
+ if (retainedTimes) {
464
+ iso(certificate.observedAt, `${label} certificate observedAt`, true);
465
+ if ((certificate.state === 'not_supplied') !== (certificate.observedAt === null))
466
+ fail(`${label} certificate observation time`);
467
+ }
410
468
  enumeration(certificate.continuity, ['unknown', 'retained', 'changes'], `${label} certificate continuity`);
411
469
  strings(certificate.findings, `${label} certificate findings`, 8, 600);
412
470
  for (const candidate of array(review.services, `${label} services`, 1_000)) {
@@ -496,7 +554,7 @@ function validatePlannedAssurance(value, label) {
496
554
  strings(assurance.limitations, `${label} limitations`, 8, 600);
497
555
  return assurance;
498
556
  }
499
- function expectedDomainChangeSummary(before, after) {
557
+ function expectedDomainChangeSummary(before, after, queryCoverage) {
500
558
  const rows = (review) => new Map(review.authoritativeRecordMatrix.map((row) => [`${row.owner}\u0000${row.type}`, row]));
501
559
  const beforeRows = rows(before);
502
560
  const afterRows = rows(after);
@@ -508,7 +566,7 @@ function expectedDomainChangeSummary(before, after) {
508
566
  };
509
567
  const rowEvidence = (review, row) => {
510
568
  if (!row)
511
- return { state: collectionComplete(review) ? 'complete' : 'unavailable', values: [] };
569
+ return { state: !queryCoverage && collectionComplete(review) ? 'complete' : 'unavailable', values: [] };
512
570
  const observations = row.observations;
513
571
  if (observations.some((item) => item.state !== 'observed'))
514
572
  return { state: 'partial', values: [] };
@@ -519,21 +577,28 @@ function expectedDomainChangeSummary(before, after) {
519
577
  return { state: 'complete', values: [...new Set(observations.flatMap((item) => item.values))].sort() };
520
578
  };
521
579
  const changed = [];
580
+ const reasons = queryCoverage && keys.length > 500 ? ['The comparison exceeds 500 owner and record-type pairs.'] : [];
522
581
  for (const key of keys.slice(0, 500)) {
523
582
  const left = rowEvidence(before, beforeRows.get(key));
524
583
  const right = rowEvidence(after, afterRows.get(key));
525
584
  const [owner, type] = key.split('\u0000');
526
585
  if (left.state !== 'complete' || right.state !== 'complete') {
586
+ if (queryCoverage)
587
+ reasons.push(`Comparison evidence is incomplete for ${owner} ${type}.`);
527
588
  continue;
528
589
  }
529
590
  else if (!sameValues(left.values, right.values)) {
530
591
  changed.push({ owner: owner ?? '', type: type ?? '', beforeValues: left.values, afterValues: right.values });
531
592
  }
532
593
  }
533
- return changed;
594
+ return { changed, reasons };
534
595
  }
535
596
  function validateDomainChangePacket(value) {
536
597
  const root = exact(value, ['schema', 'version', 'generatedAt', 'domain', 'reference', 'state', 'gate', 'summary', 'evidence', 'limitations', 'integrity'], 'Domain change packet');
598
+ const version = integer(root.version, 'Domain change packet version', 1, 1_000);
599
+ const reviewVersion = DOMAIN_CHANGE_PACKET_REVIEW_VERSIONS[version];
600
+ if (reviewVersion === undefined)
601
+ fail('Domain change packet version');
537
602
  iso(root.generatedAt, 'Domain change packet generatedAt');
538
603
  domain(root.domain, 'Domain change packet domain');
539
604
  text(root.reference, 'Domain change packet reference', 200);
@@ -556,8 +621,8 @@ function validateDomainChangePacket(value) {
556
621
  enumeration(summary.postChangeState, ['ready', 'review'], 'Domain change packet post-change state');
557
622
  enumeration(summary.assuranceState, ['incomplete', 'needs_review', 'ready'], 'Domain change packet assurance state');
558
623
  const evidence = exact(root.evidence, ['preChange', 'postChange', 'assurance'], 'Domain change packet evidence');
559
- const pre = validateDomainChangeReview(evidence.preChange, 'Domain change packet pre-change review');
560
- const post = validateDomainChangeReview(evidence.postChange, 'Domain change packet post-change review');
624
+ const pre = validateDomainChangeReview(evidence.preChange, 'Domain change packet pre-change review', reviewVersion);
625
+ const post = validateDomainChangeReview(evidence.postChange, 'Domain change packet post-change review', reviewVersion);
561
626
  const assurance = validatePlannedAssurance(evidence.assurance, 'Domain change packet assurance');
562
627
  const assuranceResult = assurance.result;
563
628
  if (pre.generatedAt !== root.generatedAt || post.generatedAt !== root.generatedAt || assurance.generatedAt !== root.generatedAt
@@ -568,13 +633,14 @@ function validateDomainChangePacket(value) {
568
633
  const expectedReasons = [
569
634
  ...pre.gate.reasons,
570
635
  ].map((reason) => `Pre-change evidence: ${reason}`);
571
- expectedReasons.push(...post.gate.reasons.map((reason) => `Post-change evidence: ${reason}`), ...(assuranceResult.review.reasons.map((reason) => `Change plan: ${reason}`)));
636
+ const comparison = expectedDomainChangeSummary(pre, post, reviewVersion >= 3);
637
+ expectedReasons.push(...post.gate.reasons.map((reason) => `Post-change evidence: ${reason}`), ...(assuranceResult.review.reasons.map((reason) => `Change plan: ${reason}`)), ...comparison.reasons);
572
638
  const boundedReasons = expectedReasons.slice(0, 100);
573
639
  if (!sameValues(gateReasons, boundedReasons)
574
640
  || gate.pass !== (boundedReasons.length === 0)
575
641
  || root.state !== (boundedReasons.length === 0 ? 'ready' : 'review'))
576
642
  fail('Domain change packet gate');
577
- const expectedChanges = expectedDomainChangeSummary(pre, post);
643
+ const expectedChanges = comparison.changed;
578
644
  if (changed.length !== expectedChanges.length || changed.some((candidate, index) => {
579
645
  const item = candidate;
580
646
  const expected = expectedChanges[index];
@@ -584,13 +650,15 @@ function validateDomainChangePacket(value) {
584
650
  }))
585
651
  fail('Domain change packet summary');
586
652
  strings(root.limitations, 'Domain change packet limitations', 8, 600);
587
- validateIntegrity(root.integrity, 'Domain change packet integrity', root.version, DOMAIN_CHANGE_PACKET_VERSION);
653
+ validateIntegrity(root.integrity, 'Domain change packet integrity', root.version, version);
588
654
  }
589
655
  export function validateSignedDigestArtifactStructure(schema, value) {
590
656
  if (value.schema !== schema)
591
657
  fail('Signed review artefact');
592
658
  if (schema === ACQUISITION_DECISION_PACKET_SCHEMA)
593
659
  validateAcquisition(value);
660
+ else if (schema === MANAGED_INDICATOR_SET_SCHEMA)
661
+ validateManagedIndicatorSet(value);
594
662
  else if (schema === LOOKUP_CLAIM_PASSPORT_SCHEMA)
595
663
  validateLookupClaimPassport(value);
596
664
  else if (schema === BULK_DOMAIN_COMPARISON_SCHEMA)
@@ -1,105 +1 @@
1
- const CONTROL_RE = /[\u0000-\u001f\u007f]/u;
2
- const DIGEST_RE = /^sha256:[a-f0-9]{64}$/u;
3
- const DOMAIN_RE = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/u;
4
- export const HEX_DIGEST_RE = /^[a-f0-9]{64}$/u;
5
- export const SEMANTIC_VERSION_RE = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/u;
6
- export function fail(label) {
7
- throw new TypeError(`${label} has an unsupported or malformed structure.`);
8
- }
9
- export function record(value, label) {
10
- if (!value || typeof value !== 'object' || Array.isArray(value))
11
- fail(label);
12
- return value;
13
- }
14
- export function exact(value, keys, label) {
15
- const source = record(value, label);
16
- const actual = Object.keys(source);
17
- if (actual.length !== keys.length || actual.some((key) => !keys.includes(key)))
18
- fail(label);
19
- return source;
20
- }
21
- export function exactOptional(value, required, optional, label) {
22
- const source = record(value, label);
23
- const actual = Object.keys(source);
24
- if (required.some((key) => !actual.includes(key))
25
- || actual.some((key) => !required.includes(key) && !optional.includes(key)))
26
- fail(label);
27
- return source;
28
- }
29
- export function text(value, label, maximum = 2_000, allowEmpty = false) {
30
- if (typeof value !== 'string' || value.length > maximum || CONTROL_RE.test(value) || (!allowEmpty && !value))
31
- fail(label);
32
- return value;
33
- }
34
- export function optionalText(value, label, maximum = 2_000) {
35
- if (value !== null)
36
- text(value, label, maximum);
37
- }
38
- export function iso(value, label, nullable = false) {
39
- if (nullable && value === null)
40
- return;
41
- const candidate = text(value, label, 64);
42
- const parsed = Date.parse(candidate);
43
- if (!Number.isFinite(parsed) || new Date(parsed).toISOString() !== candidate)
44
- fail(label);
45
- }
46
- export function integer(value, label, minimum = 0, maximum = Number.MAX_SAFE_INTEGER) {
47
- if (!Number.isSafeInteger(value) || Number(value) < minimum || Number(value) > maximum)
48
- fail(label);
49
- return Number(value);
50
- }
51
- export function boolean(value, label) {
52
- if (typeof value !== 'boolean')
53
- fail(label);
54
- }
55
- export function enumeration(value, allowed, label) {
56
- if (typeof value !== 'string' || !allowed.includes(value))
57
- fail(label);
58
- return value;
59
- }
60
- export function array(value, label, maximum, minimum = 0) {
61
- if (!Array.isArray(value) || value.length < minimum || value.length > maximum)
62
- fail(label);
63
- return value;
64
- }
65
- export function strings(value, label, maximum, textMaximum = 2_000) {
66
- const values = array(value, label, maximum);
67
- for (const item of values)
68
- text(item, label, textMaximum);
69
- return values;
70
- }
71
- export function digest(value, label) {
72
- if (typeof value !== 'string' || !DIGEST_RE.test(value))
73
- fail(label);
74
- }
75
- export function sameValues(left, right) {
76
- return left.length === right.length && left.every((value, index) => value === right[index]);
77
- }
78
- export function domain(value, label) {
79
- if (typeof value !== 'string' || !DOMAIN_RE.test(value))
80
- fail(label);
81
- }
82
- export function absoluteUrl(value, label, protocols) {
83
- const candidate = text(value, label, 2_048);
84
- try {
85
- const parsed = new URL(candidate);
86
- if (!protocols.includes(parsed.protocol) || parsed.username || parsed.password || parsed.search || parsed.hash)
87
- fail(label);
88
- return parsed;
89
- }
90
- catch {
91
- return fail(label);
92
- }
93
- }
94
- export function nullableRecord(value, label) {
95
- if (value !== null)
96
- record(value, label);
97
- }
98
- export function validateIntegrity(value, label, version, currentVersion) {
99
- if (version !== currentVersion)
100
- fail(label);
101
- const integrity = exact(value, ['algorithm', 'canonicalization', 'digestSha256'], label);
102
- if (integrity.algorithm !== 'SHA-256' || integrity.canonicalization !== 'sorted-json-v2')
103
- fail(label);
104
- digest(integrity.digestSha256, label);
105
- }
1
+ export * from "../../packages/evidence/artifact-structure.mjs";