@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,22 +1,25 @@
1
1
  import { WORKSPACE_ARCHIVE_SECTION_IDS } from "./case-portability.mjs";
2
+ export { PAGE_FINGERPRINT_VERSION, PAGE_FINGERPRINT_PARSERS } from "./page-fingerprints.mjs";
2
3
  import { defineSchemaCompatibility } from "./schema-compatibility.mjs";
3
4
  import { defineSchemaLifecycleFamily, } from "./schema-lifecycle.mjs";
4
5
  export const WORKSPACE_CONTRACT_OWNER = 'packages/contracts/workspace-portability.mts';
5
6
  export const BRAND_PROFILE_SCHEMA = 'whoisleuth.brand-profiles';
6
7
  export const PUBLIC_BRAND_PROFILE_SCHEMA_VERSION = 6;
7
- export const LATEST_PUBLIC_BRAND_PROFILE_SCHEMA_VERSION = 7;
8
- export const BRAND_PROFILE_SCHEMA_VERSION = 8;
8
+ // Historical readers retain a fixed published identity when the writer moves.
9
+ export const PUBLISHED_V2_2_BRAND_PROFILE_SCHEMA_VERSION = 7;
10
+ export const BRAND_PROFILE_SCHEMA_VERSION = 9;
9
11
  export const BRAND_PROFILE_BROWSER_SUPPORTED_VERSIONS = Object.freeze([
10
12
  PUBLIC_BRAND_PROFILE_SCHEMA_VERSION,
11
- LATEST_PUBLIC_BRAND_PROFILE_SCHEMA_VERSION,
13
+ PUBLISHED_V2_2_BRAND_PROFILE_SCHEMA_VERSION,
14
+ 8,
12
15
  BRAND_PROFILE_SCHEMA_VERSION,
13
16
  ]);
14
17
  export const SUPPORTED_BRAND_PROFILE_SCHEMA_VERSIONS = BRAND_PROFILE_BROWSER_SUPPORTED_VERSIONS;
15
18
  export const MAX_PROFILES = 100;
16
19
  export const MAX_PROFILE_VALUES = 200;
17
20
  export const MAX_PROFILE_VALUE_INPUTS = MAX_PROFILE_VALUES * 4;
18
- export const MAX_PROFILE_STORE_BYTES = 1024 * 1024;
19
- export const MAX_PROFILE_IMPORT_BYTES = 2 * 1024 * 1024;
21
+ export const MAX_PROFILE_STORE_BYTES = 4 * 1024 * 1024;
22
+ export const MAX_PROFILE_IMPORT_BYTES = MAX_PROFILE_STORE_BYTES * 8;
20
23
  export const MAX_PROFILE_NAME_LENGTH = 100;
21
24
  export const MAX_PROFILE_TEXT_LENGTH = 200;
22
25
  export const MAX_PROFILE_DOMAIN_LENGTH = 253;
@@ -44,9 +47,9 @@ export const MAX_CAMPAIGN_IMPORT_BYTES = 2 * 1024 * 1024;
44
47
  export const MAX_CAMPAIGN_INPUT_RECORDS = 500;
45
48
  export const MAX_CAMPAIGN_STORE_BYTES = 512 * 1024;
46
49
  export const WATCHLIST_SCHEMA = 'whoisleuth.watchlists';
47
- export const WATCHLIST_SCHEMA_VERSION = 2;
48
- export const WATCHLIST_BROWSER_SUPPORTED_VERSIONS = Object.freeze([WATCHLIST_SCHEMA_VERSION]);
49
- export const WATCHLIST_EXPORT_SUPPORTED_VERSIONS = Object.freeze([WATCHLIST_SCHEMA_VERSION]);
50
+ export const WATCHLIST_SCHEMA_VERSION = 4;
51
+ export const WATCHLIST_BROWSER_SUPPORTED_VERSIONS = Object.freeze([2, 3, WATCHLIST_SCHEMA_VERSION]);
52
+ export const WATCHLIST_EXPORT_SUPPORTED_VERSIONS = WATCHLIST_BROWSER_SUPPORTED_VERSIONS;
50
53
  export const MAX_WATCHLISTS = 100;
51
54
  export const MAX_WATCHLIST_INPUTS = MAX_WATCHLISTS * 4;
52
55
  export const MAX_WATCHLIST_NAME_LENGTH = 100;
@@ -93,9 +96,9 @@ export const MAX_CONDITION_VALUE_LENGTH = 200;
93
96
  export const MAX_RULE_RISK_DELTA = 25;
94
97
  export const MAX_CUSTOM_RISK_TOTAL = 50;
95
98
  export const RELATIONSHIP_OBSERVATION_SCHEMA = 'whoisleuth.relationship-observations';
96
- export const RELATIONSHIP_OBSERVATION_SCHEMA_VERSION = 1;
97
- export const RELATIONSHIP_OBSERVATION_BROWSER_SUPPORTED_VERSIONS = Object.freeze([RELATIONSHIP_OBSERVATION_SCHEMA_VERSION]);
98
- export const RELATIONSHIP_OBSERVATION_EXPORT_SUPPORTED_VERSIONS = Object.freeze([RELATIONSHIP_OBSERVATION_SCHEMA_VERSION]);
99
+ export const RELATIONSHIP_OBSERVATION_SCHEMA_VERSION = 2;
100
+ export const RELATIONSHIP_OBSERVATION_BROWSER_SUPPORTED_VERSIONS = Object.freeze([1, RELATIONSHIP_OBSERVATION_SCHEMA_VERSION]);
101
+ export const RELATIONSHIP_OBSERVATION_EXPORT_SUPPORTED_VERSIONS = RELATIONSHIP_OBSERVATION_BROWSER_SUPPORTED_VERSIONS;
99
102
  export const MAX_RELATIONSHIP_OBSERVATIONS = 300;
100
103
  export const MAX_RELATIONSHIP_OBSERVATION_INPUTS = 1_200;
101
104
  export const MAX_RELATIONSHIP_OBSERVATION_DOMAINS = 50;
@@ -103,16 +106,16 @@ export const MAX_RELATIONSHIP_OBSERVATION_LIMITATIONS = 8;
103
106
  export const MAX_RELATIONSHIP_OBSERVATION_VALUE_LENGTH = 20_000;
104
107
  export const MAX_RELATIONSHIP_OBSERVATION_STORE_BYTES = 768 * 1024;
105
108
  export const WEBSITE_SNAPSHOT_SCHEMA = 'whoisleuth.website-profile-snapshots';
106
- export const WEBSITE_SNAPSHOT_SCHEMA_VERSION = 5;
107
- export const WEBSITE_SNAPSHOT_BROWSER_SUPPORTED_VERSIONS = Object.freeze([4, WEBSITE_SNAPSHOT_SCHEMA_VERSION]);
109
+ export const WEBSITE_SNAPSHOT_SCHEMA_VERSION = 6;
110
+ export const WEBSITE_SNAPSHOT_BROWSER_SUPPORTED_VERSIONS = Object.freeze([4, 5, WEBSITE_SNAPSHOT_SCHEMA_VERSION]);
108
111
  export const SUPPORTED_WEBSITE_SNAPSHOT_SCHEMA_VERSIONS = WEBSITE_SNAPSHOT_BROWSER_SUPPORTED_VERSIONS;
109
112
  export const MAX_WEBSITE_SNAPSHOTS = 60;
110
113
  export const MAX_WEBSITE_SNAPSHOTS_PER_DOMAIN = 12;
111
114
  export const MAX_WEBSITE_SNAPSHOT_STORE_BYTES = 512 * 1024;
112
115
  export const MAX_WEBSITE_SNAPSHOT_IMPORT_BYTES = 768 * 1024;
113
116
  export const BULK_SESSION_SCHEMA = 'whoisleuth.bulk-sessions';
114
- export const BULK_SESSION_SCHEMA_VERSION = 4;
115
- export const BULK_SESSION_BROWSER_SUPPORTED_VERSIONS = Object.freeze([BULK_SESSION_SCHEMA_VERSION]);
117
+ export const BULK_SESSION_SCHEMA_VERSION = 6;
118
+ export const BULK_SESSION_BROWSER_SUPPORTED_VERSIONS = Object.freeze([4, 5, BULK_SESSION_SCHEMA_VERSION]);
116
119
  export const SUPPORTED_BULK_SESSION_SCHEMA_VERSIONS = BULK_SESSION_BROWSER_SUPPORTED_VERSIONS;
117
120
  export const MAX_BULK_SESSIONS = 10;
118
121
  export const MAX_BULK_SESSION_ROWS = 2_000;
@@ -123,15 +126,15 @@ export const MAX_BULK_SESSION_ARRAY_VALUES = 100;
123
126
  export const MAX_BULK_SESSION_SOURCES = 12;
124
127
  export const MAX_BULK_PROFILE_CONTEXT_LIMITATION_LENGTH = 300;
125
128
  export const INVESTIGATION_TEMPLATE_SCHEMA = 'whoisleuth.investigation-templates';
126
- export const INVESTIGATION_TEMPLATE_VERSION = 2;
127
- export const INVESTIGATION_TEMPLATE_SUPPORTED_VERSIONS = Object.freeze([INVESTIGATION_TEMPLATE_VERSION]);
129
+ export const INVESTIGATION_TEMPLATE_VERSION = 3;
130
+ export const INVESTIGATION_TEMPLATE_SUPPORTED_VERSIONS = Object.freeze([2, INVESTIGATION_TEMPLATE_VERSION]);
128
131
  export const MAX_INVESTIGATION_TEMPLATES = 20;
129
132
  export const MAX_INVESTIGATION_TEMPLATE_STORE_BYTES = 256 * 1024;
130
133
  export const MAX_INVESTIGATION_TEMPLATE_IMPORT_BYTES = 384 * 1024;
131
134
  export const BULK_REVIEW_SCHEMA = 'whoisleuth.bulk-review';
132
- export const BULK_REVIEW_SCHEMA_VERSION = 1;
133
- export const BULK_REVIEW_BROWSER_SUPPORTED_VERSIONS = Object.freeze([BULK_REVIEW_SCHEMA_VERSION]);
134
- export const BULK_REVIEW_EXPORT_SUPPORTED_VERSIONS = Object.freeze([BULK_REVIEW_SCHEMA_VERSION]);
135
+ export const BULK_REVIEW_SCHEMA_VERSION = 2;
136
+ export const BULK_REVIEW_BROWSER_SUPPORTED_VERSIONS = Object.freeze([1, BULK_REVIEW_SCHEMA_VERSION]);
137
+ export const BULK_REVIEW_EXPORT_SUPPORTED_VERSIONS = Object.freeze([1, BULK_REVIEW_SCHEMA_VERSION]);
135
138
  export const MAX_BULK_REVIEW_PRESETS = 24;
136
139
  export const MAX_BULK_REVIEW_ROWS = 1_900;
137
140
  export const MAX_BULK_REVIEW_STORE_BYTES = 512 * 1024;
@@ -143,7 +146,6 @@ export const MAX_WORKSPACE_INPUT_OBJECT_KEYS = 4_096;
143
146
  export const MAX_WORKSPACE_INPUT_STRING_CODE_UNITS = 8 * 1024 * 1024;
144
147
  export const PAGE_BASELINE_VERSION = 1;
145
148
  export const PAGE_IDENTITY_VERSION = 3;
146
- export const PAGE_FINGERPRINT_VERSION = 1;
147
149
  export const MAX_BASELINE_TITLE_LENGTH = 200;
148
150
  export const MAX_BASELINE_RESOURCE_HOSTS = 30;
149
151
  export const MAX_BASELINE_IDENTIFIERS = 30;
@@ -159,7 +161,7 @@ export const BULK_SESSION_BROWSER_STORE_LIFECYCLE_SCHEMA = 'whoisleuth.browser.b
159
161
  export const INVESTIGATION_TEMPLATE_BROWSER_STORE_LIFECYCLE_SCHEMA = 'whoisleuth.browser.investigation-template-store';
160
162
  export const BULK_REVIEW_BROWSER_STORE_LIFECYCLE_SCHEMA = 'whoisleuth.browser.bulk-review-store';
161
163
  export const WORKSPACE_PORTABILITY_IDENTITY_CONSTANTS = Object.freeze([
162
- 'BRAND_PROFILE_SCHEMA', 'PUBLIC_BRAND_PROFILE_SCHEMA_VERSION', 'LATEST_PUBLIC_BRAND_PROFILE_SCHEMA_VERSION', 'BRAND_PROFILE_SCHEMA_VERSION', 'BRAND_PROFILE_BROWSER_SUPPORTED_VERSIONS', 'SUPPORTED_BRAND_PROFILE_SCHEMA_VERSIONS',
164
+ 'BRAND_PROFILE_SCHEMA', 'PUBLIC_BRAND_PROFILE_SCHEMA_VERSION', 'PUBLISHED_V2_2_BRAND_PROFILE_SCHEMA_VERSION', 'BRAND_PROFILE_SCHEMA_VERSION', 'BRAND_PROFILE_BROWSER_SUPPORTED_VERSIONS', 'SUPPORTED_BRAND_PROFILE_SCHEMA_VERSIONS',
163
165
  'CAMPAIGN_SCHEMA', 'CAMPAIGN_SCHEMA_VERSION', 'CAMPAIGN_BROWSER_SUPPORTED_VERSIONS', 'CAMPAIGN_EXPORT_SUPPORTED_VERSIONS',
164
166
  'WATCHLIST_SCHEMA', 'WATCHLIST_SCHEMA_VERSION', 'WATCHLIST_BROWSER_SUPPORTED_VERSIONS', 'WATCHLIST_EXPORT_SUPPORTED_VERSIONS',
165
167
  'SHORTLIST_SCHEMA', 'SHORTLIST_SCHEMA_VERSION', 'SHORTLIST_BROWSER_SUPPORTED_VERSIONS', 'SUPPORTED_SHORTLIST_SCHEMA_VERSIONS',
@@ -200,7 +202,7 @@ export const WORKSPACE_PORTABILITY_BOUND_CONSTANTS = Object.freeze([
200
202
  'MAX_BULK_REVIEW_PRESETS', 'MAX_BULK_REVIEW_ROWS', 'MAX_BULK_REVIEW_STORE_BYTES', 'MAX_BULK_REVIEW_NAME_LENGTH',
201
203
  'MAX_WORKSPACE_INPUT_GRAPH_DEPTH', 'MAX_WORKSPACE_INPUT_GRAPH_NODES', 'MAX_WORKSPACE_INPUT_ARRAY_LENGTH',
202
204
  'MAX_WORKSPACE_INPUT_OBJECT_KEYS', 'MAX_WORKSPACE_INPUT_STRING_CODE_UNITS',
203
- 'PAGE_BASELINE_VERSION', 'PAGE_IDENTITY_VERSION', 'PAGE_FINGERPRINT_VERSION', 'MAX_BASELINE_TITLE_LENGTH',
205
+ 'PAGE_BASELINE_VERSION', 'PAGE_IDENTITY_VERSION', 'PAGE_FINGERPRINT_VERSION', 'PAGE_FINGERPRINT_PARSERS', 'MAX_BASELINE_TITLE_LENGTH',
204
206
  'MAX_BASELINE_RESOURCE_HOSTS', 'MAX_BASELINE_IDENTIFIERS',
205
207
  ]);
206
208
  export const WORKSPACE_DOMAIN_COMPATIBILITY_FACADES = Object.freeze([
@@ -227,7 +229,7 @@ export const BRAND_PROFILE_BROWSER_COMPATIBILITY = defineSchemaCompatibility({
227
229
  supportedVersions: BRAND_PROFILE_BROWSER_SUPPORTED_VERSIONS, acceptsUnversionedLegacy: false,
228
230
  futureVersionBehavior: 'preserve_without_write', migration: 'normalize_to_current', writeSemantics: 'normalized_rewrite',
229
231
  byteBudget: MAX_PROFILE_STORE_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
230
- note: 'Supported version 6 and 7 profiles migrate directly to version 8. Version 7 gives each approved change window a durable opaque identity; version 8 adds bounded official-channel and rights-reference records.',
232
+ note: 'Published versions 6, 7 and 8 migrate directly to the current profile envelope. Version 9 preserves earlier page baselines while admitting independently versioned native-tree fingerprints; incompatible fingerprint algorithms are not compared.',
231
233
  });
232
234
  export const CAMPAIGN_BROWSER_COMPATIBILITY = defineSchemaCompatibility({
233
235
  id: 'browser.campaigns', kind: 'browser_store', schema: null, currentVersion: CAMPAIGN_SCHEMA_VERSION,
@@ -239,9 +241,9 @@ export const CAMPAIGN_BROWSER_COMPATIBILITY = defineSchemaCompatibility({
239
241
  export const WATCHLIST_BROWSER_COMPATIBILITY = defineSchemaCompatibility({
240
242
  id: 'browser.watchlists', kind: 'browser_store', schema: null, currentVersion: WATCHLIST_SCHEMA_VERSION,
241
243
  supportedVersions: WATCHLIST_BROWSER_SUPPORTED_VERSIONS, acceptsUnversionedLegacy: false,
242
- futureVersionBehavior: 'preserve_without_write', migration: 'exact_current_only', writeSemantics: 'normalized_rewrite',
244
+ futureVersionBehavior: 'preserve_without_write', migration: 'normalize_to_current', writeSemantics: 'normalized_rewrite',
243
245
  byteBudget: MAX_WATCHLIST_STORE_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
244
- note: 'The unchanged public version 2 envelope is the browser baseline; unversioned maps are outside the compatibility boundary.',
246
+ note: 'Public version 2 remains readable without inferred collection outcomes. Version 3 retains explicit page and favicon collection quality; unversioned maps are outside the compatibility boundary.',
245
247
  });
246
248
  export const SHORTLIST_BROWSER_COMPATIBILITY = defineSchemaCompatibility({
247
249
  id: 'browser.shortlist', kind: 'browser_store', schema: null, currentVersion: SHORTLIST_SCHEMA_VERSION,
@@ -267,35 +269,35 @@ export const DETECTION_RULE_BROWSER_COMPATIBILITY = defineSchemaCompatibility({
267
269
  export const RELATIONSHIP_OBSERVATION_BROWSER_COMPATIBILITY = defineSchemaCompatibility({
268
270
  id: 'browser.relationship-observations', kind: 'browser_store', schema: null,
269
271
  currentVersion: RELATIONSHIP_OBSERVATION_SCHEMA_VERSION, supportedVersions: RELATIONSHIP_OBSERVATION_BROWSER_SUPPORTED_VERSIONS,
270
- acceptsUnversionedLegacy: false, futureVersionBehavior: 'preserve_without_write', migration: 'exact_current_only',
272
+ acceptsUnversionedLegacy: false, futureVersionBehavior: 'preserve_without_write', migration: 'normalize_to_current',
271
273
  writeSemantics: 'normalized_rewrite', byteBudget: MAX_RELATIONSHIP_OBSERVATION_STORE_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
272
- note: 'Only explicit analyst selections from bounded Bulk relationship evidence are retained; identities are re-derived from normalised values and members.',
274
+ note: 'Public version 1 observations migrate with unknown contributing-source provenance. Version 2 retains bounded per-domain source states and times; identities remain derived from normalised values and members.',
273
275
  });
274
276
  export const WEBSITE_SNAPSHOT_BROWSER_COMPATIBILITY = defineSchemaCompatibility({
275
277
  id: 'browser.website-snapshots', kind: 'browser_store', schema: null,
276
278
  currentVersion: WEBSITE_SNAPSHOT_SCHEMA_VERSION, supportedVersions: WEBSITE_SNAPSHOT_BROWSER_SUPPORTED_VERSIONS,
277
279
  acceptsUnversionedLegacy: false, futureVersionBehavior: 'preserve_without_write', migration: 'normalize_to_current',
278
280
  writeSemantics: 'normalized_rewrite', byteBudget: MAX_WEBSITE_SNAPSHOT_STORE_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
279
- note: 'Public version 4 records normalise to version 5 with explicit unknown profile provenance; raw lookup payloads, contact data, certificate bytes, and fetched bodies remain excluded.',
281
+ note: 'Public versions 4 and 5 retain their original fingerprint algorithm on migration. Current snapshots record fingerprint provenance so parser changes cannot become target changes; raw lookup payloads, contact data, certificate bytes and fetched bodies remain excluded.',
280
282
  });
281
283
  export const BULK_SESSION_BROWSER_COMPATIBILITY = defineSchemaCompatibility({
282
284
  id: 'browser.bulk-sessions', kind: 'browser_store', schema: null, currentVersion: BULK_SESSION_SCHEMA_VERSION,
283
285
  supportedVersions: BULK_SESSION_BROWSER_SUPPORTED_VERSIONS, acceptsUnversionedLegacy: false,
284
- futureVersionBehavior: 'preserve_without_write', migration: 'exact_current_only', writeSemantics: 'normalized_rewrite',
286
+ futureVersionBehavior: 'preserve_without_write', migration: 'normalize_to_current', writeSemantics: 'normalized_rewrite',
285
287
  byteBudget: MAX_BULK_SESSION_STORE_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
286
- note: 'The unchanged public schema 4 writer is the exact browser baseline with bounded Brand Profile provenance; unsupported local checkpoints are preserved without rewrite.',
288
+ note: 'Public schemas 4 and 5 remain readable with their original observation provenance. Schema 6 retains explicit page and favicon collection quality for saved evidence handoffs; future versions are preserved without rewrite.',
287
289
  });
288
290
  export const INVESTIGATION_TEMPLATE_BROWSER_COMPATIBILITY = defineSchemaCompatibility({
289
291
  id: 'browser.investigation-templates', kind: 'browser_store', schema: null,
290
292
  currentVersion: INVESTIGATION_TEMPLATE_VERSION, supportedVersions: INVESTIGATION_TEMPLATE_SUPPORTED_VERSIONS,
291
- acceptsUnversionedLegacy: false, futureVersionBehavior: 'preserve_without_write', migration: 'exact_current_only',
293
+ acceptsUnversionedLegacy: false, futureVersionBehavior: 'preserve_without_write', migration: 'normalize_to_current',
292
294
  writeSemantics: 'normalized_rewrite', byteBudget: MAX_INVESTIGATION_TEMPLATE_STORE_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
293
- note: 'Version 2 adds the fixed response-playbook recipe identifiers. Analyst-authored guidance remains bound to allowlisted built-in stages and cannot run code, start collection, submit evidence, or remove mandatory request gates.',
295
+ note: 'Public version 2 remains readable. Version 3 retains optional lesson-revision provenance without Case identities or note text. Guidance remains bound to built-in stages and cannot execute actions or remove mandatory request gates.',
294
296
  });
295
297
  export const BULK_REVIEW_BROWSER_COMPATIBILITY = defineSchemaCompatibility({
296
298
  id: 'browser.bulk-review', kind: 'browser_store', schema: null, currentVersion: BULK_REVIEW_SCHEMA_VERSION,
297
299
  supportedVersions: BULK_REVIEW_BROWSER_SUPPORTED_VERSIONS, acceptsUnversionedLegacy: false,
298
- futureVersionBehavior: 'preserve_without_write', migration: 'exact_current_only', writeSemantics: 'normalized_rewrite',
300
+ futureVersionBehavior: 'preserve_without_write', migration: 'normalize_to_current', writeSemantics: 'normalized_rewrite',
299
301
  byteBudget: MAX_BULK_REVIEW_STORE_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
300
302
  note: 'Bounded saved Bulk filter views and per-domain review states only; scan results, contacts, notes, and case disposition remain separate.',
301
303
  });
@@ -316,9 +318,9 @@ export const CAMPAIGN_EXPORT_COMPATIBILITY = defineSchemaCompatibility({
316
318
  export const WATCHLIST_EXPORT_COMPATIBILITY = defineSchemaCompatibility({
317
319
  id: 'export.watchlists', kind: 'export', schema: WATCHLIST_SCHEMA, currentVersion: WATCHLIST_SCHEMA_VERSION,
318
320
  supportedVersions: WATCHLIST_EXPORT_SUPPORTED_VERSIONS, acceptsUnversionedLegacy: false,
319
- futureVersionBehavior: 'reject', migration: 'exact_current_only', writeSemantics: 'non_destructive_merge',
321
+ futureVersionBehavior: 'reject', migration: 'normalize_to_current', writeSemantics: 'non_destructive_merge',
320
322
  byteBudget: MAX_WATCHLIST_IMPORT_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
321
- note: 'Non-destructive collection merge with current schema required.',
323
+ note: 'Public version 2 and current version 3 merge non-destructively. Historical records have unknown collection quality; incomplete current checks cannot erase a usable baseline.',
322
324
  });
323
325
  export const SHORTLIST_EXPORT_COMPATIBILITY = defineSchemaCompatibility({
324
326
  id: 'export.shortlist', kind: 'export', schema: SHORTLIST_SCHEMA, currentVersion: SHORTLIST_SCHEMA_VERSION,
@@ -337,35 +339,35 @@ export const DETECTION_RULE_EXPORT_COMPATIBILITY = defineSchemaCompatibility({
337
339
  export const RELATIONSHIP_OBSERVATION_EXPORT_COMPATIBILITY = defineSchemaCompatibility({
338
340
  id: 'export.relationship-observations', kind: 'export', schema: RELATIONSHIP_OBSERVATION_SCHEMA,
339
341
  currentVersion: RELATIONSHIP_OBSERVATION_SCHEMA_VERSION, supportedVersions: RELATIONSHIP_OBSERVATION_EXPORT_SUPPORTED_VERSIONS,
340
- acceptsUnversionedLegacy: false, futureVersionBehavior: 'reject', migration: 'exact_current_only',
342
+ acceptsUnversionedLegacy: false, futureVersionBehavior: 'reject', migration: 'normalize_to_current',
341
343
  writeSemantics: 'non_destructive_merge', byteBudget: MAX_RELATIONSHIP_OBSERVATION_STORE_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
342
- note: 'Workspace-archive section for bounded analyst-selected derived pivots; raw scan and lookup responses are excluded.',
344
+ note: 'Version 1 archive sections migrate to version 2 without inventing source provenance. Bounded contributing-source states and times survive current export; raw lookup responses remain excluded.',
343
345
  });
344
346
  export const WEBSITE_SNAPSHOT_EXPORT_COMPATIBILITY = defineSchemaCompatibility({
345
347
  id: 'export.website-snapshots', kind: 'export', schema: WEBSITE_SNAPSHOT_SCHEMA,
346
348
  currentVersion: WEBSITE_SNAPSHOT_SCHEMA_VERSION, supportedVersions: SUPPORTED_WEBSITE_SNAPSHOT_SCHEMA_VERSIONS,
347
349
  acceptsUnversionedLegacy: false, futureVersionBehavior: 'reject', migration: 'normalize_to_current',
348
350
  writeSemantics: 'non_destructive_merge', byteBudget: MAX_WEBSITE_SNAPSHOT_IMPORT_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
349
- note: 'Public schema 4 archive sections normalise to version 5 with explicit unknown profile provenance and merge non-destructively without retaining fetched bodies.',
351
+ note: 'Public schema 4 and 5 sections migrate non-destructively with preserved fingerprint provenance. Unknown detector versions remain unknown; fetched bodies remain excluded.',
350
352
  });
351
353
  export const BULK_SESSION_EXPORT_COMPATIBILITY = defineSchemaCompatibility({
352
354
  id: 'export.bulk-sessions', kind: 'export', schema: BULK_SESSION_SCHEMA, currentVersion: BULK_SESSION_SCHEMA_VERSION,
353
355
  supportedVersions: SUPPORTED_BULK_SESSION_SCHEMA_VERSIONS, acceptsUnversionedLegacy: false,
354
- futureVersionBehavior: 'reject', migration: 'exact_current_only', writeSemantics: 'non_destructive_merge',
356
+ futureVersionBehavior: 'reject', migration: 'normalize_to_current', writeSemantics: 'non_destructive_merge',
355
357
  byteBudget: MAX_BULK_SESSION_STORE_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
356
- note: 'The unchanged public schema 4 portable sessions retain explicit source states, comparison limitations, and bounded profile-context provenance.',
358
+ note: 'Public schemas 4 and 5 migrate without invented observation times or collection outcomes. Current exports retain source-qualified relationship evidence, bounded profile context and collection quality.',
357
359
  });
358
360
  export const INVESTIGATION_TEMPLATE_EXPORT_COMPATIBILITY = defineSchemaCompatibility({
359
361
  id: 'export.investigation-templates', kind: 'export', schema: INVESTIGATION_TEMPLATE_SCHEMA,
360
362
  currentVersion: INVESTIGATION_TEMPLATE_VERSION, supportedVersions: INVESTIGATION_TEMPLATE_SUPPORTED_VERSIONS,
361
- acceptsUnversionedLegacy: false, futureVersionBehavior: 'reject', migration: 'exact_current_only',
363
+ acceptsUnversionedLegacy: false, futureVersionBehavior: 'reject', migration: 'normalize_to_current',
362
364
  writeSemantics: 'non_destructive_merge', byteBudget: MAX_INVESTIGATION_TEMPLATE_IMPORT_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
363
- note: 'The unchanged public version 2 writer remains the exact bounded analyst-authored guide baseline with allowlisted stage identities and request gates.',
365
+ note: 'Version 3 exports authored guidance and optional content-hash lesson provenance. Public version 2 remains readable; future versions fail closed.',
364
366
  });
365
367
  export const BULK_REVIEW_EXPORT_COMPATIBILITY = defineSchemaCompatibility({
366
368
  id: 'export.bulk-review', kind: 'export', schema: BULK_REVIEW_SCHEMA, currentVersion: BULK_REVIEW_SCHEMA_VERSION,
367
369
  supportedVersions: BULK_REVIEW_EXPORT_SUPPORTED_VERSIONS, acceptsUnversionedLegacy: false,
368
- futureVersionBehavior: 'reject', migration: 'exact_current_only', writeSemantics: 'non_destructive_merge',
370
+ futureVersionBehavior: 'reject', migration: 'normalize_to_current', writeSemantics: 'non_destructive_merge',
369
371
  byteBudget: MAX_BULK_REVIEW_STORE_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
370
372
  note: 'Workspace-archive section for saved Bulk views and explicit review states; network collection and case disposition are never changed by import.',
371
373
  });
@@ -647,383 +649,347 @@ const WORKSPACE_LIFECYCLE_DEFINITIONS = Object.freeze([
647
649
  },
648
650
  ]);
649
651
  const WORKSPACE_LIFECYCLE_FIXTURE_SOURCE = Object.freeze([
652
+ {
653
+ id: 'workspace.browser.watchlist.v4', path: 'test/fixtures/workspace-lifecycle/browser-watchlist-v4.json',
654
+ bytes: 76, sha256: '059930ee54d1bc65ac68ef6d3a8ac8cd8e53f682fe9a857512e2b6c57e93d5fe',
655
+ schema: 'whoisleuth.browser.watchlist-store', version: 4,
656
+ },
657
+ {
658
+ id: 'workspace.portable.watchlist.v4', path: 'test/fixtures/workspace-lifecycle/portable-watchlist-v4.json',
659
+ bytes: 120, sha256: '496f1ec39add1d3fe080ad8f9f991639cfbbe659ddb1c58932ac6ff563ce32aa',
660
+ schema: 'whoisleuth.watchlists', version: 4,
661
+ },
662
+ {
663
+ id: 'workspace.browser.watchlist.v3', path: 'test/fixtures/workspace-lifecycle/browser-watchlist-v3.json',
664
+ bytes: 76, sha256: '740246c1666c332ae907a5b40364e03a4f008484fce7b1c6dce8082a6ac7b654',
665
+ schema: 'whoisleuth.browser.watchlist-store', version: 3,
666
+ },
667
+ {
668
+ id: 'workspace.portable.watchlist.v3', path: 'test/fixtures/workspace-lifecycle/portable-watchlist-v3.json',
669
+ bytes: 120, sha256: 'ad07c8a530c369b49192bd3d8dde1b17c58b737da4e3d00a834a64be6aa429bf',
670
+ schema: 'whoisleuth.watchlists', version: 3,
671
+ },
672
+ {
673
+ id: 'workspace.browser.bulk.v6', path: 'test/fixtures/workspace-lifecycle/browser-bulk-v6.json',
674
+ bytes: 77, sha256: '811ef0b05415f19dc7bd940a4786d53a6398121cbb7cd060fcd6d21ed562487b',
675
+ schema: 'whoisleuth.browser.bulk-session-store', version: 6,
676
+ },
677
+ {
678
+ id: 'workspace.portable.bulk.v6', path: 'test/fixtures/workspace-lifecycle/portable-bulk-v6.json',
679
+ bytes: 338, sha256: 'ec7f6445cf42c013163ea835d5f919213b69978124d6883c1f10d62a0868235f',
680
+ schema: 'whoisleuth.bulk-sessions', version: 6,
681
+ },
650
682
  {
651
683
  "id": "workspace.browser.brand.v6",
652
684
  "path": "test/fixtures/workspace-lifecycle/browser-brand-v6.json",
653
685
  "bytes": 37,
654
686
  "sha256": "624d0b889df19b0f1abfd4026a4dd868003188ed4f4294f6e912589255847df9",
655
- "contentDigestSha256": null,
656
687
  "schema": "whoisleuth.browser.brand-profile-store",
657
- "version": 6,
658
- "role": "historical",
659
- "expectation": "normalises_to_current_output",
660
- "expectedOutputFixtureId": "workspace.browser.brand.v8",
661
- "scope": "repository",
662
- "shapeId": "workspace.browser.brand.shape"
688
+ "version": 6
663
689
  },
664
690
  {
665
691
  "id": "workspace.browser.brand.v7",
666
692
  "path": "test/fixtures/workspace-lifecycle/browser-brand-v7.json",
667
693
  "bytes": 37,
668
694
  "sha256": "c866b2aff3dad05236114e96d3997a1c6af571658b38faa6aeb3c224d125a46b",
669
- "contentDigestSha256": null,
670
695
  "schema": "whoisleuth.browser.brand-profile-store",
671
- "version": 7,
672
- "role": "historical",
673
- "expectation": "normalises_to_current_output",
674
- "expectedOutputFixtureId": "workspace.browser.brand.v8",
675
- "scope": "repository",
676
- "shapeId": "workspace.browser.brand.shape"
696
+ "version": 7
677
697
  },
678
698
  {
679
699
  "id": "workspace.browser.brand.v8",
680
700
  "path": "test/fixtures/workspace-lifecycle/browser-brand-v8.json",
681
701
  "bytes": 37,
682
702
  "sha256": "6c2310d9da626cb8acd88a47ccb60fa46051a63b23675cdc4801794ec5cc52e3",
683
- "contentDigestSha256": null,
684
703
  "schema": "whoisleuth.browser.brand-profile-store",
685
- "version": 8,
686
- "role": "current",
687
- "expectation": "accepted_exact",
688
- "expectedOutputFixtureId": null,
689
- "scope": "repository",
690
- "shapeId": "workspace.browser.brand.shape"
704
+ "version": 8
691
705
  },
692
706
  {
693
707
  "id": "workspace.browser.bulk.v4",
694
708
  "path": "test/fixtures/workspace-lifecycle/browser-bulk-v4.json",
695
709
  "bytes": 77,
696
710
  "sha256": "605442d181a35a1809edb2ff0b5be17920d52f04276cacaf1786dcc41d3c96ec",
697
- "contentDigestSha256": null,
698
711
  "schema": "whoisleuth.browser.bulk-session-store",
699
- "version": 4,
700
- "role": "current",
701
- "expectation": "accepted_exact",
702
- "expectedOutputFixtureId": null,
703
- "scope": "repository",
704
- "shapeId": "workspace.browser.bulk.shape"
712
+ "version": 4
713
+ },
714
+ {
715
+ "id": "workspace.browser.bulk.v5",
716
+ "path": "test/fixtures/workspace-lifecycle/browser-bulk-v5.json",
717
+ "bytes": 77,
718
+ "sha256": "fca31e3190ff54669c06e14e9785f6d2e504ccbf837eb57caa881b5a8874ebfe",
719
+ "schema": "whoisleuth.browser.bulk-session-store",
720
+ "version": 5
705
721
  },
706
722
  {
707
723
  "id": "workspace.browser.campaign.v1",
708
724
  "path": "test/fixtures/workspace-lifecycle/browser-campaign-v1.json",
709
725
  "bytes": 38,
710
726
  "sha256": "c9f2a2f619c6237230e609a68c895c819472eba644ac4185b3eac3af256c7709",
711
- "contentDigestSha256": null,
712
727
  "schema": "whoisleuth.browser.campaign-store",
713
- "version": 1,
714
- "role": "current",
715
- "expectation": "accepted_exact",
716
- "expectedOutputFixtureId": null,
717
- "scope": "repository",
718
- "shapeId": "workspace.browser.campaign.shape"
728
+ "version": 1
719
729
  },
720
730
  {
721
731
  "id": "workspace.browser.ct.v3",
722
732
  "path": "test/fixtures/workspace-lifecycle/browser-ct-v3.json",
723
733
  "bytes": 36,
724
734
  "sha256": "48e7de97a1b64878f450f12238f7c9f5a41b85e07db3b86b0bc95ca5a5630685",
725
- "contentDigestSha256": null,
726
735
  "schema": "whoisleuth.browser.ct-history-store",
727
- "version": 3,
728
- "role": "current",
729
- "expectation": "accepted_exact",
730
- "expectedOutputFixtureId": null,
731
- "scope": "repository",
732
- "shapeId": "workspace.browser.ct.shape"
736
+ "version": 3
733
737
  },
734
738
  {
735
739
  "id": "workspace.browser.detection.v1",
736
740
  "path": "test/fixtures/workspace-lifecycle/browser-detection-v1.json",
737
741
  "bytes": 34,
738
742
  "sha256": "8772df49af2416bdf2f4881dd6f70452b3a0a8c31cf93f4aa1d7cb40ba0537a4",
739
- "contentDigestSha256": null,
740
743
  "schema": "whoisleuth.browser.detection-rule-store",
741
- "version": 1,
742
- "role": "current",
743
- "expectation": "accepted_exact",
744
- "expectedOutputFixtureId": null,
745
- "scope": "repository",
746
- "shapeId": "workspace.browser.detection.shape"
744
+ "version": 1
747
745
  },
748
746
  {
749
747
  "id": "workspace.browser.relationship.v1",
750
748
  "path": "test/fixtures/workspace-lifecycle/browser-relationship-v1.json",
751
749
  "bytes": 41,
752
750
  "sha256": "d26f28d5d0a654c2b2eab004f2e1a72c05845f433e8b47716f416b7271142668",
753
- "contentDigestSha256": null,
754
751
  "schema": "whoisleuth.browser.relationship-observation-store",
755
- "version": 1,
756
- "role": "current",
757
- "expectation": "accepted_exact",
758
- "expectedOutputFixtureId": null,
759
- "scope": "repository",
760
- "shapeId": "workspace.browser.relationship.shape"
752
+ "version": 1
753
+ },
754
+ {
755
+ "id": "workspace.browser.relationship.v2",
756
+ "path": "test/fixtures/workspace-lifecycle/browser-relationship-v2.json",
757
+ "bytes": 41,
758
+ "sha256": "951dbf0861636b4cf1389e954eb55e81e19dd94a6bf83236ed127f288788f564",
759
+ "schema": "whoisleuth.browser.relationship-observation-store",
760
+ "version": 2
761
761
  },
762
762
  {
763
763
  "id": "workspace.browser.review.v1",
764
764
  "path": "test/fixtures/workspace-lifecycle/browser-review-v1.json",
765
765
  "bytes": 88,
766
766
  "sha256": "9da86165eee183509ad3770b4f7bcf5296040c16ac108024e10b97c54c653609",
767
- "contentDigestSha256": null,
768
767
  "schema": "whoisleuth.browser.bulk-review-store",
769
- "version": 1,
770
- "role": "current",
771
- "expectation": "accepted_exact",
772
- "expectedOutputFixtureId": null,
773
- "scope": "repository",
774
- "shapeId": "workspace.browser.review.shape"
768
+ "version": 1
769
+ },
770
+ {
771
+ "id": "workspace.browser.review.v2",
772
+ "path": "test/fixtures/workspace-lifecycle/browser-review-v2.json",
773
+ "bytes": 88,
774
+ "sha256": "2e22d70101443016b12803835d29ce35cd61a513c70976af1fca1c5de18e222d",
775
+ "schema": "whoisleuth.browser.bulk-review-store",
776
+ "version": 2
775
777
  },
776
778
  {
777
779
  "id": "workspace.browser.shortlist.v3",
778
780
  "path": "test/fixtures/workspace-lifecycle/browser-shortlist-v3.json",
779
781
  "bytes": 72,
780
782
  "sha256": "8ee7f00e2cdd899893549823df9699cf800a392e8a74cbe2907d2a8b2340ebca",
781
- "contentDigestSha256": null,
782
783
  "schema": "whoisleuth.browser.shortlist-store",
783
- "version": 3,
784
- "role": "current",
785
- "expectation": "accepted_exact",
786
- "expectedOutputFixtureId": null,
787
- "scope": "repository",
788
- "shapeId": "workspace.browser.shortlist.shape"
784
+ "version": 3
789
785
  },
790
786
  {
791
787
  "id": "workspace.browser.template.v2",
792
788
  "path": "test/fixtures/workspace-lifecycle/browser-template-v2.json",
793
789
  "bytes": 88,
794
790
  "sha256": "52c1a5a671bf3bd0d052fb7c75d746ed3527898b18ba6532fa9103bd240920aa",
795
- "contentDigestSha256": null,
796
791
  "schema": "whoisleuth.browser.investigation-template-store",
797
- "version": 2,
798
- "role": "current",
799
- "expectation": "accepted_exact",
800
- "expectedOutputFixtureId": null,
801
- "scope": "repository",
802
- "shapeId": "workspace.browser.template.shape"
792
+ "version": 2
793
+ },
794
+ {
795
+ "id": "workspace.browser.template.v3",
796
+ "path": "test/fixtures/workspace-lifecycle/browser-template-v3.json",
797
+ "bytes": 88,
798
+ "sha256": "70a6dfe2c27c75d86c74a9dfe05148c3186be68191002cbea8c18816a64792c9",
799
+ "schema": "whoisleuth.browser.investigation-template-store",
800
+ "version": 3
803
801
  },
804
802
  {
805
803
  "id": "workspace.browser.watchlist.v2",
806
804
  "path": "test/fixtures/workspace-lifecycle/browser-watchlist-v2.json",
807
805
  "bytes": 76,
808
806
  "sha256": "230e7b6c83659a096e2ccd0da251558088f38b24e438c713bba1012fea788b2c",
809
- "contentDigestSha256": null,
810
807
  "schema": "whoisleuth.browser.watchlist-store",
811
- "version": 2,
812
- "role": "current",
813
- "expectation": "accepted_exact",
814
- "expectedOutputFixtureId": null,
815
- "scope": "repository",
816
- "shapeId": "workspace.browser.watchlist.shape"
808
+ "version": 2
817
809
  },
818
810
  {
819
811
  "id": "workspace.browser.website.v4",
820
812
  "path": "test/fixtures/workspace-lifecycle/browser-website-v4.json",
821
813
  "bytes": 90,
822
814
  "sha256": "b6f1aa0f80fa902c085d831c7bb3334b7c95db49906ffc8dd452173f423b85a4",
823
- "contentDigestSha256": null,
824
815
  "schema": "whoisleuth.browser.website-snapshot-store",
825
- "version": 4,
826
- "role": "historical",
827
- "expectation": "normalises_to_current_output",
828
- "expectedOutputFixtureId": "workspace.browser.website.v5",
829
- "scope": "repository",
830
- "shapeId": "workspace.browser.website.shape"
816
+ "version": 4
831
817
  },
832
818
  {
833
819
  "id": "workspace.browser.website.v5",
834
820
  "path": "test/fixtures/workspace-lifecycle/browser-website-v5.json",
835
821
  "bytes": 90,
836
822
  "sha256": "16550a8c24d5c7bcfe271086071fee7f3392279990cc6fe91e3b9c95f07d0759",
837
- "contentDigestSha256": null,
838
823
  "schema": "whoisleuth.browser.website-snapshot-store",
839
- "version": 5,
840
- "role": "current",
841
- "expectation": "accepted_exact",
842
- "expectedOutputFixtureId": null,
843
- "scope": "repository",
844
- "shapeId": "workspace.browser.website.shape"
824
+ "version": 5
845
825
  },
846
826
  {
847
827
  "id": "workspace.portable.brand.v6",
848
828
  "path": "test/fixtures/workspace-lifecycle/portable-brand-v6.json",
849
829
  "bytes": 122,
850
830
  "sha256": "19e8ab9772bcb07d989249576feab1d71a1a62d401a1a1a7cecf1c65cd7d5f80",
851
- "contentDigestSha256": null,
852
831
  "schema": "whoisleuth.brand-profiles",
853
- "version": 6,
854
- "role": "historical",
855
- "expectation": "normalises_to_current_output",
856
- "expectedOutputFixtureId": "workspace.portable.brand.v8",
857
- "scope": "repository",
858
- "shapeId": "workspace.portable.brand.shape"
832
+ "version": 6
859
833
  },
860
834
  {
861
835
  "id": "workspace.portable.brand.v7",
862
836
  "path": "test/fixtures/workspace-lifecycle/portable-brand-v7.json",
863
837
  "bytes": 122,
864
838
  "sha256": "49db0b0a2d5c00da9dc55fd5b03702d7412550e17329b917567acf6c4ec3ebb9",
865
- "contentDigestSha256": null,
866
839
  "schema": "whoisleuth.brand-profiles",
867
- "version": 7,
868
- "role": "historical",
869
- "expectation": "normalises_to_current_output",
870
- "expectedOutputFixtureId": "workspace.portable.brand.v8",
871
- "scope": "repository",
872
- "shapeId": "workspace.portable.brand.shape"
840
+ "version": 7
873
841
  },
874
842
  {
875
843
  "id": "workspace.portable.brand.v8",
876
844
  "path": "test/fixtures/workspace-lifecycle/portable-brand-v8.json",
877
845
  "bytes": 122,
878
846
  "sha256": "4e4af17a0e19cb841ea50fe150caa3de79b978311b51a334ed7f6f3d2e1c6a1c",
879
- "contentDigestSha256": null,
880
847
  "schema": "whoisleuth.brand-profiles",
881
- "version": 8,
882
- "role": "current",
883
- "expectation": "accepted_exact",
884
- "expectedOutputFixtureId": null,
885
- "scope": "repository",
886
- "shapeId": "workspace.portable.brand.shape"
848
+ "version": 8
887
849
  },
888
850
  {
889
851
  "id": "workspace.portable.bulk.v4",
890
852
  "path": "test/fixtures/workspace-lifecycle/portable-bulk-v4.json",
891
853
  "bytes": 338,
892
854
  "sha256": "5b3adc2e58c2e47399cf8c0634225dd6cad18d6189d55520603b914a6e719785",
893
- "contentDigestSha256": null,
894
855
  "schema": "whoisleuth.bulk-sessions",
895
- "version": 4,
896
- "role": "current",
897
- "expectation": "accepted_exact",
898
- "expectedOutputFixtureId": null,
899
- "scope": "repository",
900
- "shapeId": "workspace.portable.bulk.shape"
856
+ "version": 4
857
+ },
858
+ {
859
+ "id": "workspace.portable.bulk.v5",
860
+ "path": "test/fixtures/workspace-lifecycle/portable-bulk-v5.json",
861
+ "bytes": 338,
862
+ "sha256": "c58b685ef707b713a08c5ae5986128d2ed1162646f84d926316de194b2b67232",
863
+ "schema": "whoisleuth.bulk-sessions",
864
+ "version": 5
901
865
  },
902
866
  {
903
867
  "id": "workspace.portable.campaign.v1",
904
868
  "path": "test/fixtures/workspace-lifecycle/portable-campaign-v1.json",
905
869
  "bytes": 281,
906
870
  "sha256": "2a0618a9f639cd8375eb46f9bdd5fc9fbb5d8f9ce347f1b93c2d760a91162ccd",
907
- "contentDigestSha256": null,
908
871
  "schema": "whoisleuth.campaigns",
909
- "version": 1,
910
- "role": "current",
911
- "expectation": "accepted_exact",
912
- "expectedOutputFixtureId": null,
913
- "scope": "repository",
914
- "shapeId": "workspace.portable.campaign.shape"
872
+ "version": 1
915
873
  },
916
874
  {
917
875
  "id": "workspace.portable.detection.v1",
918
876
  "path": "test/fixtures/workspace-lifecycle/portable-detection-v1.json",
919
877
  "bytes": 293,
920
878
  "sha256": "0c1306057547a5bf4a5c8c37f5bfaf9195a15a4971adc8e5c9085e8e87946508",
921
- "contentDigestSha256": null,
922
879
  "schema": "whoisleuth.detection-rules",
923
- "version": 1,
924
- "role": "current",
925
- "expectation": "accepted_exact",
926
- "expectedOutputFixtureId": null,
927
- "scope": "repository",
928
- "shapeId": "workspace.portable.detection.shape"
880
+ "version": 1
929
881
  },
930
882
  {
931
883
  "id": "workspace.portable.relationship.v1",
932
884
  "path": "test/fixtures/workspace-lifecycle/portable-relationship-v1.json",
933
885
  "bytes": 363,
934
886
  "sha256": "d16411297ec5be4eb0c87dbc36e1ed34e55ef3699084af2d5f18d76b042d398a",
935
- "contentDigestSha256": null,
936
887
  "schema": "whoisleuth.relationship-observations",
937
- "version": 1,
938
- "role": "current",
939
- "expectation": "accepted_exact",
940
- "expectedOutputFixtureId": null,
941
- "scope": "repository",
942
- "shapeId": "workspace.portable.relationship.shape"
888
+ "version": 1
889
+ },
890
+ {
891
+ "id": "workspace.portable.relationship.v2",
892
+ "path": "test/fixtures/workspace-lifecycle/portable-relationship-v2.json",
893
+ "bytes": 363,
894
+ "sha256": "28322f41c059a060e77673e4aad0d9d5b7b1f6021eefcf5bfed9426588345fdf",
895
+ "schema": "whoisleuth.relationship-observations",
896
+ "version": 2
943
897
  },
944
898
  {
945
899
  "id": "workspace.portable.review.v1",
946
900
  "path": "test/fixtures/workspace-lifecycle/portable-review-v1.json",
947
901
  "bytes": 88,
948
902
  "sha256": "9da86165eee183509ad3770b4f7bcf5296040c16ac108024e10b97c54c653609",
949
- "contentDigestSha256": null,
950
903
  "schema": "whoisleuth.bulk-review",
951
- "version": 1,
952
- "role": "current",
953
- "expectation": "accepted_exact",
954
- "expectedOutputFixtureId": null,
955
- "scope": "repository",
956
- "shapeId": "workspace.portable.review.shape"
904
+ "version": 1
905
+ },
906
+ {
907
+ "id": "workspace.portable.review.v2",
908
+ "path": "test/fixtures/workspace-lifecycle/portable-review-v2.json",
909
+ "bytes": 88,
910
+ "sha256": "2e22d70101443016b12803835d29ce35cd61a513c70976af1fca1c5de18e222d",
911
+ "schema": "whoisleuth.bulk-review",
912
+ "version": 2
957
913
  },
958
914
  {
959
915
  "id": "workspace.portable.shortlist.v3",
960
916
  "path": "test/fixtures/workspace-lifecycle/portable-shortlist-v3.json",
961
917
  "bytes": 116,
962
918
  "sha256": "282569f92d9dc0cd0ef3fdca985ba066e7003477398c2aea64149a036e00e627",
963
- "contentDigestSha256": null,
964
919
  "schema": "whoisleuth.shortlist",
965
- "version": 3,
966
- "role": "current",
967
- "expectation": "accepted_exact",
968
- "expectedOutputFixtureId": null,
969
- "scope": "repository",
970
- "shapeId": "workspace.portable.shortlist.shape"
920
+ "version": 3
971
921
  },
972
922
  {
973
923
  "id": "workspace.portable.template.v2",
974
924
  "path": "test/fixtures/workspace-lifecycle/portable-template-v2.json",
975
925
  "bytes": 471,
976
926
  "sha256": "827aaa27e86cc2e22247f5709fb0df8fa92226ae0691750d7bfdb9a85ca6e6b5",
977
- "contentDigestSha256": null,
978
927
  "schema": "whoisleuth.investigation-templates",
979
- "version": 2,
980
- "role": "current",
981
- "expectation": "accepted_exact",
982
- "expectedOutputFixtureId": null,
983
- "scope": "repository",
984
- "shapeId": "workspace.portable.template.shape"
928
+ "version": 2
929
+ },
930
+ {
931
+ "id": "workspace.portable.template.v3",
932
+ "path": "test/fixtures/workspace-lifecycle/portable-template-v3.json",
933
+ "bytes": 471,
934
+ "sha256": "6b211af44661594337731767cd212beae305d81e03e090a783633eebaa62d03e",
935
+ "schema": "whoisleuth.investigation-templates",
936
+ "version": 3
985
937
  },
986
938
  {
987
939
  "id": "workspace.portable.watchlist.v2",
988
940
  "path": "test/fixtures/workspace-lifecycle/portable-watchlist-v2.json",
989
941
  "bytes": 120,
990
942
  "sha256": "1176a691294968af187a383839212a75dedbc0089b7a23bbb3d29a079f91e29b",
991
- "contentDigestSha256": null,
992
943
  "schema": "whoisleuth.watchlists",
993
- "version": 2,
994
- "role": "current",
995
- "expectation": "accepted_exact",
996
- "expectedOutputFixtureId": null,
997
- "scope": "repository",
998
- "shapeId": "workspace.portable.watchlist.shape"
944
+ "version": 2
999
945
  },
1000
946
  {
1001
947
  "id": "workspace.portable.website.v4",
1002
948
  "path": "test/fixtures/workspace-lifecycle/portable-website-v4.json",
1003
949
  "bytes": 135,
1004
950
  "sha256": "08eef9ee031d5226464a0da5624c444c69eb48ca9dc2cb2fd2f1551018c4b11c",
1005
- "contentDigestSha256": null,
1006
951
  "schema": "whoisleuth.website-profile-snapshots",
1007
- "version": 4,
1008
- "role": "historical",
1009
- "expectation": "normalises_to_current_output",
1010
- "expectedOutputFixtureId": "workspace.portable.website.v5",
1011
- "scope": "repository",
1012
- "shapeId": "workspace.portable.website.shape"
952
+ "version": 4
1013
953
  },
1014
954
  {
1015
955
  "id": "workspace.portable.website.v5",
1016
956
  "path": "test/fixtures/workspace-lifecycle/portable-website-v5.json",
1017
957
  "bytes": 135,
1018
958
  "sha256": "0cab1675a632793141107fac330a130e7d300a7726e9963f8409199902bf0d23",
1019
- "contentDigestSha256": null,
1020
959
  "schema": "whoisleuth.website-profile-snapshots",
1021
- "version": 5,
1022
- "role": "current",
1023
- "expectation": "accepted_exact",
1024
- "expectedOutputFixtureId": null,
1025
- "scope": "repository",
1026
- "shapeId": "workspace.portable.website.shape"
960
+ "version": 5
961
+ },
962
+ {
963
+ "id": "workspace.browser.brand.v9",
964
+ "path": "test/fixtures/workspace-lifecycle/browser-brand-v9.json",
965
+ "bytes": 37,
966
+ "sha256": "d03cf3bad923ffa3411d218c31a910933c5bb314cc99b9df77f8ae22324c68dc",
967
+ "schema": "whoisleuth.browser.brand-profile-store",
968
+ "version": 9
969
+ },
970
+ {
971
+ "id": "workspace.browser.website.v6",
972
+ "path": "test/fixtures/workspace-lifecycle/browser-website-v6.json",
973
+ "bytes": 90,
974
+ "sha256": "89a4ae5103d848798d77c29b293316284dcf5c2b38417471ad4d55404a8a2110",
975
+ "schema": "whoisleuth.browser.website-snapshot-store",
976
+ "version": 6
977
+ },
978
+ {
979
+ "id": "workspace.portable.brand.v9",
980
+ "path": "test/fixtures/workspace-lifecycle/portable-brand-v9.json",
981
+ "bytes": 122,
982
+ "sha256": "d1ad5a1c56613b31f3bf6195bbda01f4ee9efd528c5fe0cfa5011a6549324853",
983
+ "schema": "whoisleuth.brand-profiles",
984
+ "version": 9
985
+ },
986
+ {
987
+ "id": "workspace.portable.website.v6",
988
+ "path": "test/fixtures/workspace-lifecycle/portable-website-v6.json",
989
+ "bytes": 135,
990
+ "sha256": "a9013e750b7feadbf5dc5d3de5794c27e03d73ba886ad752600df4f0c0cfed35",
991
+ "schema": "whoisleuth.website-profile-snapshots",
992
+ "version": 6
1027
993
  }
1028
994
  ]);
1029
995
  function workspaceByteBudget(descriptor) {
@@ -1038,10 +1004,26 @@ function workspaceByteBoundId(descriptor) {
1038
1004
  function workspaceShapeId(plane, slug, lifecycle) {
1039
1005
  return 'workspace.' + plane + '.' + slug + '.' + lifecycle + '.shape';
1040
1006
  }
1041
- const WORKSPACE_LIFECYCLE_FIXTURES = Object.freeze(WORKSPACE_LIFECYCLE_FIXTURE_SOURCE.map((fixture) => ({
1042
- ...fixture,
1043
- shapeId: workspaceShapeId(fixture.id.includes('.browser.') ? 'browser' : 'portable', fixture.id.split('.')[2] ?? '', fixture.role === 'historical' ? 'historical' : 'current'),
1044
- })));
1007
+ const WORKSPACE_LIFECYCLE_FIXTURES = Object.freeze(WORKSPACE_LIFECYCLE_FIXTURE_SOURCE.map((fixture) => {
1008
+ const definition = WORKSPACE_LIFECYCLE_DEFINITIONS.find((definition) => definition.browserSchema === fixture.schema || definition.portable?.schema === fixture.schema);
1009
+ if (!definition)
1010
+ throw new TypeError(`Workspace fixture ${fixture.id} has no collection owner.`);
1011
+ const browser = definition.browserSchema === fixture.schema;
1012
+ const descriptor = browser ? definition.browserDescriptor : definition.portable.descriptor;
1013
+ const current = fixture.version === descriptor.currentVersion;
1014
+ const target = WORKSPACE_LIFECYCLE_FIXTURE_SOURCE.find((candidate) => candidate.schema === fixture.schema && candidate.version === descriptor.currentVersion);
1015
+ if (!target)
1016
+ throw new TypeError(`Workspace schema ${fixture.schema} needs an immutable version ${descriptor.currentVersion} fixture.`);
1017
+ return {
1018
+ ...fixture,
1019
+ contentDigestSha256: null,
1020
+ role: current ? 'current' : 'historical',
1021
+ expectation: current ? 'accepted_exact' : 'normalises_to_current_output',
1022
+ expectedOutputFixtureId: current ? null : target.id,
1023
+ scope: 'repository',
1024
+ shapeId: workspaceShapeId(browser ? 'browser' : 'portable', definition.slug, current ? 'current' : 'historical'),
1025
+ };
1026
+ }));
1045
1027
  function workspaceFixtureIds(schema, version) {
1046
1028
  return WORKSPACE_LIFECYCLE_FIXTURES
1047
1029
  .filter((fixture) => fixture.schema === schema && fixture.version === version)
@@ -1398,7 +1380,6 @@ export const WORKSPACE_PORTABILITY_LIFECYCLE_FAMILY = defineSchemaLifecycleFamil
1398
1380
  contracts: WORKSPACE_LIFECYCLE_CONTRACTS,
1399
1381
  fixtures: WORKSPACE_LIFECYCLE_FIXTURES,
1400
1382
  metadata: {
1401
- metadataVersion: 4,
1402
1383
  enforcement: 'declarative_only',
1403
1384
  shapes: WORKSPACE_LIFECYCLE_SHAPES,
1404
1385
  boundProfiles: WORKSPACE_LIFECYCLE_BOUNDS,