@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
@@ -0,0 +1,72 @@
1
+ // Shared collision-resistant identities for bounded, already normalised records.
2
+ // Platform cryptography remains the owner of signatures and authentication.
3
+ export const MAX_IDENTITY_DIGEST_BYTES = 1024 * 1024;
4
+ const SHA256_CONSTANTS = Object.freeze([
5
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
6
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
7
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
8
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
9
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
10
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
11
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
12
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
13
+ ]);
14
+ function rotateRight(value, bits) {
15
+ return (value >>> bits) | (value << (32 - bits));
16
+ }
17
+ export function sha256IdentityHex(bytes) {
18
+ if (bytes.byteLength > MAX_IDENTITY_DIGEST_BYTES)
19
+ throw new RangeError('Record identity exceeds its byte limit.');
20
+ // This digest is a collision-resistant record identity, not a password,
21
+ // signature, MAC, or substitute for the platform cryptography used there.
22
+ const paddedLength = Math.ceil((bytes.byteLength + 9) / 64) * 64;
23
+ const padded = new Uint8Array(paddedLength);
24
+ padded.set(bytes);
25
+ padded[bytes.byteLength] = 0x80;
26
+ const view = new DataView(padded.buffer);
27
+ const bitLength = bytes.byteLength * 8;
28
+ view.setUint32(paddedLength - 8, Math.floor(bitLength / 0x1_0000_0000), false);
29
+ view.setUint32(paddedLength - 4, bitLength >>> 0, false);
30
+ const state = new Uint32Array([
31
+ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
32
+ 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
33
+ ]);
34
+ const words = new Uint32Array(64);
35
+ for (let offset = 0; offset < paddedLength; offset += 64) {
36
+ for (let index = 0; index < 16; index += 1)
37
+ words[index] = view.getUint32(offset + index * 4, false);
38
+ for (let index = 16; index < 64; index += 1) {
39
+ const left = words[index - 15];
40
+ const right = words[index - 2];
41
+ const sigma0 = rotateRight(left, 7) ^ rotateRight(left, 18) ^ (left >>> 3);
42
+ const sigma1 = rotateRight(right, 17) ^ rotateRight(right, 19) ^ (right >>> 10);
43
+ words[index] = (words[index - 16] + sigma0 + words[index - 7] + sigma1) >>> 0;
44
+ }
45
+ let [a, b, c, d, e, f, g, h] = state;
46
+ for (let index = 0; index < 64; index += 1) {
47
+ const sum1 = rotateRight(e, 6) ^ rotateRight(e, 11) ^ rotateRight(e, 25);
48
+ const choice = (e & f) ^ (~e & g);
49
+ const first = (h + sum1 + choice + SHA256_CONSTANTS[index] + words[index]) >>> 0;
50
+ const sum0 = rotateRight(a, 2) ^ rotateRight(a, 13) ^ rotateRight(a, 22);
51
+ const majority = (a & b) ^ (a & c) ^ (b & c);
52
+ const second = (sum0 + majority) >>> 0;
53
+ h = g;
54
+ g = f;
55
+ f = e;
56
+ e = (d + first) >>> 0;
57
+ d = c;
58
+ c = b;
59
+ b = a;
60
+ a = (first + second) >>> 0;
61
+ }
62
+ state[0] = (state[0] + a) >>> 0;
63
+ state[1] = (state[1] + b) >>> 0;
64
+ state[2] = (state[2] + c) >>> 0;
65
+ state[3] = (state[3] + d) >>> 0;
66
+ state[4] = (state[4] + e) >>> 0;
67
+ state[5] = (state[5] + f) >>> 0;
68
+ state[6] = (state[6] + g) >>> 0;
69
+ state[7] = (state[7] + h) >>> 0;
70
+ }
71
+ return [...state].map((value) => value.toString(16).padStart(8, '0')).join('');
72
+ }
@@ -0,0 +1,190 @@
1
+ // Cross-runtime deterministic normalization for lifecycle dates published by RDAP and
2
+ // WHOIS services. Raw upstream strings remain the provenance source; these
3
+ // helpers only produce additive Date/ISO companions for comparison, storage,
4
+ // and presentation.
5
+ const REGISTRY_MONTHS = Object.freeze({
6
+ jan: 1, feb: 2, mar: 3, apr: 4, may: 5, jun: 6,
7
+ jul: 7, aug: 8, sep: 9, oct: 10, nov: 11, dec: 12,
8
+ });
9
+ const REGISTRY_MONTH_NAMES = Object.freeze({
10
+ january: 1, february: 2, march: 3, april: 4, may: 5, june: 6,
11
+ july: 7, august: 8, september: 9, october: 10, november: 11, december: 12,
12
+ });
13
+ function utcDateFromParts(year, month, day, hour = 0, minute = 0, second = 0, millisecond = 0) {
14
+ if (![year, month, day, hour, minute, second, millisecond].every(Number.isInteger))
15
+ return null;
16
+ if (year < 1000 || year > 9999 || month < 1 || month > 12 || day < 1
17
+ || hour < 0 || hour > 23 || minute < 0 || minute > 59
18
+ || second < 0 || second > 59 || millisecond < 0 || millisecond > 999)
19
+ return null;
20
+ const date = new Date(Date.UTC(year, month - 1, day, hour, minute, second, millisecond));
21
+ if (date.getUTCFullYear() !== year || date.getUTCMonth() !== month - 1 || date.getUTCDate() !== day
22
+ || date.getUTCHours() !== hour || date.getUTCMinutes() !== minute
23
+ || date.getUTCSeconds() !== second || date.getUTCMilliseconds() !== millisecond)
24
+ return null;
25
+ return date;
26
+ }
27
+ // Parse the explicit non-ISO formats observed across supported ccTLD WHOIS
28
+ // services before considering ISO-shaped input. Runtime Date parsing treats
29
+ // dotted dates ambiguously and timezone-less timestamps in the host's local
30
+ // timezone, so every accepted shape is validated and normalized through UTC.
31
+ function parseRegistryDate(input) {
32
+ if (typeof input !== 'string')
33
+ return null;
34
+ // The documented .nz WHOIS examples place one space before the RFC3339
35
+ // `T` separator. Canonicalize that registry-specific presentation quirk
36
+ // before applying the existing strict ISO-shaped parser.
37
+ const value = input.trim()
38
+ .replace(/^(\d{4}-\d{2}-\d{2})[ ]+[Tt](?=\d{2}:)/, '$1T')
39
+ // The .ee WHOIS service separates an otherwise ISO-shaped numeric
40
+ // timezone from its clock with one space.
41
+ .replace(/^(\d{4}-\d{2}-\d{2}[Tt ]\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?)[ ]+([+-]\d{2}:?\d{2})$/, '$1$2');
42
+ if (!value)
43
+ return null;
44
+ // DD.MM.YYYY[ HH:MM:SS] - e.g. 14.03.2024 10:46:48
45
+ let match = value.match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})(?:\s+(\d{1,2}):(\d{2}):(\d{2}))?$/);
46
+ if (match) {
47
+ const [, day = '', month = '', year = '', hour = '0', minute = '0', second = '0'] = match;
48
+ return utcDateFromParts(+year, +month, +day, +(hour || 0), +(minute || 0), +(second || 0));
49
+ }
50
+ // YYYY. MM. DD. - e.g. 2006. 09. 18.
51
+ match = value.match(/^(\d{4})\.\s*(\d{1,2})\.\s*(\d{1,2})\.?$/);
52
+ if (match) {
53
+ const [, year = '', month = '', day = ''] = match;
54
+ return utcDateFromParts(+year, +month, +day);
55
+ }
56
+ // YYYY.MM.DD HH:MM:SS - used by NASK's .pl WHOIS service.
57
+ match = value.match(/^(\d{4})\.(\d{1,2})\.(\d{1,2})\s+(\d{1,2}):(\d{2}):(\d{2})$/);
58
+ if (match) {
59
+ const [, year = '', month = '', day = '', hour = '', minute = '', second = ''] = match;
60
+ return utcDateFromParts(+year, +month, +day, +hour, +minute, +second);
61
+ }
62
+ // YYYY/MM/DD - an unambiguous year-first form used by CIRA WHOIS.
63
+ match = value.match(/^(\d{4})\/(\d{1,2})\/(\d{1,2})$/);
64
+ if (match) {
65
+ const [, year = '', month = '', day = ''] = match;
66
+ return utcDateFromParts(+year, +month, +day);
67
+ }
68
+ // YYYYMMDD with an optional time or Registro.br contact-reference suffix.
69
+ // The raw value remains available as provenance while the validated date
70
+ // and optional clock time are projected into the additive ISO companion.
71
+ match = value.match(/^(\d{4})(\d{2})(\d{2})(?:\s+(\d{1,2}):(\d{2}):(\d{2})|\s+#[0-9]{1,20})?$/);
72
+ if (match) {
73
+ const [, year = '', month = '', day = '', hour = '0', minute = '0', second = '0'] = match;
74
+ return utcDateFromParts(+year, +month, +day, +(hour || 0), +(minute || 0), +(second || 0));
75
+ }
76
+ // YYYY-Mon-DD. - e.g. 1999-Feb-16.
77
+ match = value.match(/^(\d{4})-([A-Za-z]{3})-(\d{1,2})\.?$/);
78
+ if (match) {
79
+ const month = REGISTRY_MONTHS[(match[2] ?? '').toLowerCase()];
80
+ return month ? utcDateFromParts(+(match[1] ?? ''), month, +(match[3] ?? '')) : null;
81
+ }
82
+ // DD-Mon-YYYY[ HH:MM:SS] - used by several ICANN-style ccTLD services.
83
+ match = value.match(/^(\d{1,2})-([A-Za-z]{3})-(\d{4})(?:\s+(\d{1,2}):(\d{2}):(\d{2}))?$/);
84
+ if (match) {
85
+ const month = REGISTRY_MONTHS[(match[2] ?? '').toLowerCase()];
86
+ return month
87
+ ? utcDateFromParts(+(match[3] ?? ''), month, +(match[1] ?? ''), +(match[4] || 0), +(match[5] || 0), +(match[6] || 0))
88
+ : null;
89
+ }
90
+ // DD-MM-YYYY - the day-first form published by the currently profiled
91
+ // registries that use bare numeric hyphen dates. A future month-first
92
+ // source must receive its own marker-gated parser rather than reusing this
93
+ // global shape. The four-digit final year remains distinct from ISO below.
94
+ match = value.match(/^(\d{1,2})-(\d{1,2})-(\d{4})$/);
95
+ if (match) {
96
+ const [, day = '', month = '', year = ''] = match;
97
+ return utcDateFromParts(+year, +month, +day);
98
+ }
99
+ // YYYY-MM-DD HH:MM:SS (GMT+H:MM) - the explicit offset form published by
100
+ // the Kazakhstan registry. Parentheses and the GMT marker are required so
101
+ // unrelated prose is not accepted as a lifecycle timestamp.
102
+ match = value.match(/^(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2}):(\d{2})\s+\(GMT([+-])(\d{1,2}):(\d{2})\)$/i);
103
+ if (match) {
104
+ const [, year = '', month = '', day = '', hour = '', minute = '', second = '', sign = '', offsetHours = '', offsetMinutes = '',] = match;
105
+ const local = utcDateFromParts(+year, +month, +day, +hour, +minute, +second);
106
+ if (!local || +offsetHours > 23 || +offsetMinutes > 59)
107
+ return null;
108
+ const offsetMs = (+offsetHours * 60 + +offsetMinutes) * 60000;
109
+ return new Date(local.getTime() + (sign === '+' ? -offsetMs : offsetMs));
110
+ }
111
+ // DD-MM-YYYY HH:MM:SS GMT+H - the Tunisian registry's bounded local-time
112
+ // presentation. Accept an optional minute component but require the GMT
113
+ // marker and validate the calendar and offset before normalization.
114
+ match = value.match(/^(\d{1,2})-(\d{1,2})-(\d{4})\s+(\d{2}):(\d{2}):(\d{2})\s+GMT([+-])(\d{1,2})(?::(\d{2}))?$/i);
115
+ if (match) {
116
+ const [, day = '', month = '', year = '', hour = '', minute = '', second = '', sign = '', offsetHours = '', offsetMinutes = '0',] = match;
117
+ const local = utcDateFromParts(+year, +month, +day, +hour, +minute, +second);
118
+ if (!local || +offsetHours > 23 || +offsetMinutes > 59)
119
+ return null;
120
+ const offsetMs = (+offsetHours * 60 + +offsetMinutes) * 60000;
121
+ return new Date(local.getTime() + (sign === '+' ? -offsetMs : offsetMs));
122
+ }
123
+ // DD Mon YYYY - the compact English month form published by THNIC.
124
+ match = value.match(/^(\d{1,2})\s+([A-Za-z]{3})\s+(\d{4})$/);
125
+ if (match) {
126
+ const month = REGISTRY_MONTHS[(match[2] ?? '').toLowerCase()];
127
+ return month ? utcDateFromParts(+(match[3] ?? ''), month, +(match[1] ?? '')) : null;
128
+ }
129
+ // Ordinal-day Month YYYY[ at HH:MM:SS.sss] - the English form published
130
+ // by the Channel Islands registry. The suffix and `at` token are required
131
+ // so ordinary prose containing a month name is not accepted as lifecycle
132
+ // evidence.
133
+ match = value.match(/^(\d{1,2})(st|nd|rd|th)\s+([A-Za-z]{3,9})\s+(\d{4})(?:\s+at\s+(\d{1,2}):(\d{2}):(\d{2})(?:\.(\d{1,3}))?)?$/i);
134
+ if (match) {
135
+ const day = +(match[1] ?? '');
136
+ const suffix = (match[2] ?? '').toLowerCase();
137
+ const expectedSuffix = day % 100 >= 11 && day % 100 <= 13
138
+ ? 'th'
139
+ : ({ 1: 'st', 2: 'nd', 3: 'rd' }[day % 10] || 'th');
140
+ if (suffix !== expectedSuffix)
141
+ return null;
142
+ const monthText = (match[3] ?? '').toLowerCase();
143
+ const month = REGISTRY_MONTH_NAMES[monthText] || REGISTRY_MONTHS[monthText];
144
+ const millisecond = match[8] ? Number(`${match[8]}00`.slice(0, 3)) : 0;
145
+ return month
146
+ ? utcDateFromParts(+(match[4] ?? ''), month, day, +(match[5] || 0), +(match[6] || 0), +(match[7] || 0), millisecond)
147
+ : null;
148
+ }
149
+ // Ddd Mon DD YYYY[ HH:MM:SS] - the English textual form used by the
150
+ // Belgian registry. Weekday text is presentation-only; the validated
151
+ // calendar components determine the canonical UTC companion.
152
+ match = value.match(/^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s+([A-Za-z]{3})\s+(\d{1,2})\s+(\d{4})(?:\s+(\d{1,2}):(\d{2}):(\d{2}))?$/i);
153
+ if (match) {
154
+ const month = REGISTRY_MONTHS[(match[1] ?? '').toLowerCase()];
155
+ return month
156
+ ? utcDateFromParts(+(match[3] ?? ''), month, +(match[2] ?? ''), +(match[4] || 0), +(match[5] || 0), +(match[6] || 0))
157
+ : null;
158
+ }
159
+ // Month DD YYYY - ISNIC publishes English month names with variable
160
+ // spacing and no timezone. Keep the accepted shape narrow and accept only
161
+ // the twelve exact English names from a fixed table.
162
+ match = value.match(/^([A-Za-z]{3,9})\s+(\d{1,2})\s+(\d{4})$/);
163
+ if (match) {
164
+ const month = REGISTRY_MONTH_NAMES[(match[1] ?? '').toLowerCase()];
165
+ return month ? utcDateFromParts(+(match[3] ?? ''), month, +(match[2] ?? '')) : null;
166
+ }
167
+ // ISO 8601-shaped dates and timestamps. A missing timezone is deliberately
168
+ // interpreted as UTC so Express and function deployments agree.
169
+ match = value.match(/^(\d{4})-(\d{2})-(\d{2})(?:[Tt ](\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{1,9}))?)?(?:([Zz])|([+-])(\d{2}):?(\d{2}))?)?$/);
170
+ if (!match)
171
+ return null;
172
+ const [, year = '', month = '', day = '', hour = '0', minute = '0', second = '0', fraction, zulu, offsetSign, offsetHours = '0', offsetMinutes = '0',] = match;
173
+ const millisecond = fraction ? Number(`${fraction}000`.slice(0, 3)) : 0;
174
+ const local = utcDateFromParts(+year, +month, +day, +(hour || 0), +(minute || 0), +(second || 0), millisecond);
175
+ if (!local)
176
+ return null;
177
+ if (!offsetSign || zulu)
178
+ return local;
179
+ const offsetHour = +offsetHours;
180
+ const offsetMinute = +offsetMinutes;
181
+ if (offsetHour > 23 || offsetMinute > 59)
182
+ return null;
183
+ const offsetMs = (offsetHour * 60 + offsetMinute) * 60000;
184
+ return new Date(local.getTime() + (offsetSign === '+' ? -offsetMs : offsetMs));
185
+ }
186
+ function registryDateIso(value) {
187
+ const date = parseRegistryDate(value);
188
+ return date ? date.toISOString() : null;
189
+ }
190
+ export { parseRegistryDate, registryDateIso };
@@ -0,0 +1,69 @@
1
+ import { MAX_SELECTED_FILES, MAX_SELECTED_FILE_BYTES, MAX_SELECTED_FILE_TOTAL_BYTES } from "../contracts/selected-file-limits.mjs";
2
+ import { digest, exact, integer } from "./artifact-structure.mjs";
3
+ import { sha256ArtifactBytes } from "./artifact-integrity.mjs";
4
+ export function readRetainedFileReference(value) {
5
+ const record = exact(value, ['digestSha256', 'byteLength'], 'Retained file reference');
6
+ digest(record.digestSha256, 'Retained file digest');
7
+ const byteLength = integer(record.byteLength, 'Retained file bytes', 1, MAX_SELECTED_FILE_BYTES);
8
+ return Object.freeze({ digestSha256: record.digestSha256, byteLength });
9
+ }
10
+ /** Partition already bounded collection references without capping the entire workspace at one batch. */
11
+ export function retainedFileGroups(references) {
12
+ const unique = new Map();
13
+ for (const item of references) {
14
+ const checked = readRetainedFileReference({ digestSha256: item.digestSha256, byteLength: item.byteLength });
15
+ const previous = unique.get(checked.digestSha256);
16
+ if (previous && previous.byteLength !== checked.byteLength)
17
+ throw new Error('File references have conflicting byte lengths.');
18
+ if (!previous)
19
+ unique.set(checked.digestSha256, item);
20
+ }
21
+ const groups = [];
22
+ let group = [], bytes = 0;
23
+ for (const item of unique.values()) {
24
+ if (group.length === MAX_SELECTED_FILES || item.byteLength > MAX_SELECTED_FILE_TOTAL_BYTES - bytes) {
25
+ groups.push(group);
26
+ group = [];
27
+ bytes = 0;
28
+ }
29
+ group.push(item);
30
+ bytes += item.byteLength;
31
+ }
32
+ if (group.length)
33
+ groups.push(group);
34
+ return groups;
35
+ }
36
+ /** Capture declarations synchronously; Blob contents are immutable. */
37
+ export function captureRetainedFiles(input) {
38
+ if (!Array.isArray(input) || input.length > MAX_SELECTED_FILES)
39
+ throw new TypeError(`Select at most ${MAX_SELECTED_FILES} files per operation.`);
40
+ let total = 0;
41
+ return Object.freeze(input.map(item => {
42
+ const reference = readRetainedFileReference(item?.reference);
43
+ if (!(item.file instanceof Blob) || item.file.size !== reference.byteLength)
44
+ throw new TypeError('Selected file bytes do not match their declared length.');
45
+ total += reference.byteLength;
46
+ if (total > MAX_SELECTED_FILE_TOTAL_BYTES)
47
+ throw new TypeError('Selected files exceed the combined byte limit.');
48
+ return Object.freeze({ reference, file: item.file.slice() });
49
+ }));
50
+ }
51
+ /** Check the complete selected bytes, never a prefix or a canonicalised copy. */
52
+ export async function readVerifiedRetainedFileBytes(reference, file) {
53
+ const expected = readRetainedFileReference(reference);
54
+ if (!(file instanceof Blob) || file.size !== expected.byteLength)
55
+ throw new TypeError('Retained file bytes do not match their declared length.');
56
+ const bytes = new Uint8Array(await file.arrayBuffer());
57
+ try {
58
+ if (bytes.byteLength !== expected.byteLength || await sha256ArtifactBytes(bytes) !== expected.digestSha256)
59
+ throw new TypeError('Retained file bytes do not match their declared digest.');
60
+ return bytes;
61
+ }
62
+ catch (cause) {
63
+ bytes.fill(0);
64
+ throw cause;
65
+ }
66
+ }
67
+ export async function verifyRetainedFile(reference, file) {
68
+ (await readVerifiedRetainedFileBytes(reference, file)).fill(0);
69
+ }
@@ -0,0 +1,14 @@
1
+ import { normalizeExplicitIsoTimestamp } from "./observation.mjs";
2
+ export const ANALYST_REVIEW_AGING_AFTER_DAYS = 7;
3
+ export const ANALYST_REVIEW_STALE_AFTER_DAYS = 30;
4
+ export function analystReviewAgeAt(observedAt, now) {
5
+ const observed = normalizeExplicitIsoTimestamp(observedAt);
6
+ const reviewed = normalizeExplicitIsoTimestamp(now);
7
+ if (!observed || !reviewed)
8
+ return 'unknown';
9
+ const age = Date.parse(reviewed) - Date.parse(observed);
10
+ if (age < 0)
11
+ return 'unknown';
12
+ const days = age / 86_400_000;
13
+ return days > ANALYST_REVIEW_STALE_AFTER_DAYS ? 'stale' : days > ANALYST_REVIEW_AGING_AFTER_DAYS ? 'aging' : 'current';
14
+ }