@slicedearth/whoisleuth-cli 1.47.4 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (235) hide show
  1. package/README.md +74 -177
  2. package/SECURITY.md +6 -3
  3. package/bin/whoisleuth.mjs +2 -1
  4. package/cli/archive-inspect.mjs +28 -10
  5. package/cli/arguments.mjs +204 -174
  6. package/cli/artifact-structure.mjs +837 -163
  7. package/cli/artifact-verify.mjs +90 -61
  8. package/cli/bounded-json.mjs +1 -1
  9. package/cli/bulk-checkpoint.mjs +33 -20
  10. package/cli/bulk.mjs +9 -2
  11. package/cli/case-pack.mjs +180 -576
  12. package/cli/ci-report.mjs +6 -1
  13. package/cli/collection-preflight.mjs +2 -2
  14. package/cli/command-catalogue.mjs +12 -3
  15. package/cli/command-reference.mjs +910 -92
  16. package/cli/completion.mjs +504 -226
  17. package/cli/config-profile.mjs +13 -3
  18. package/cli/discover.mjs +7 -1
  19. package/cli/discovery-observation-snapshot.mjs +24 -28
  20. package/cli/discovery-scan-command-runner.mjs +9 -9
  21. package/cli/discovery-scan.mjs +17 -3
  22. package/cli/discovery-snapshot.mjs +15 -7
  23. package/cli/domain-control-monitor.mjs +203 -30
  24. package/cli/domain-control-observations.mjs +15 -21
  25. package/cli/errors.mjs +8 -3
  26. package/cli/evidence-signing.mjs +10 -20
  27. package/cli/external-observation-mapping.mjs +3 -3
  28. package/cli/formatters/json.mjs +20 -32
  29. package/cli/formatters/terminal.mjs +583 -22
  30. package/cli/input.mjs +7 -2
  31. package/cli/interactive-launcher.mjs +226 -0
  32. package/cli/interchange-report.mjs +19 -19
  33. package/cli/investigation-manifest.mjs +1 -1
  34. package/cli/investigation-plan.mjs +167 -16
  35. package/cli/investigation-run.mjs +9 -1
  36. package/cli/lookalike-calibration.mjs +3 -1
  37. package/cli/lookup-brief.mjs +13 -8
  38. package/cli/lookup-browser.mjs +844 -0
  39. package/cli/lookup-command-runner.mjs +94 -31
  40. package/cli/lookup-diff.mjs +2 -2
  41. package/cli/lookup-plan.mjs +1 -0
  42. package/cli/mail-review.mjs +206 -94
  43. package/cli/manual.mjs +5 -5
  44. package/cli/offline-evidence-review.mjs +18 -7
  45. package/cli/open-asset-model-bridge.mjs +1 -1
  46. package/cli/output-file.mjs +70 -14
  47. package/cli/page-compare.mjs +11 -6
  48. package/cli/progress-events.mjs +2 -1
  49. package/cli/progress.mjs +3 -3
  50. package/cli/registry-cohort.mjs +9 -51
  51. package/cli/registry-doctor.mjs +67 -28
  52. package/cli/retained-artifact-diff.mjs +7 -8
  53. package/cli/risk-calibration.mjs +85 -38
  54. package/cli/runner.mjs +189 -75
  55. package/cli/saved-lookup.mjs +424 -57
  56. package/cli/sharing-review.mjs +6 -1
  57. package/cli/source-reliability.mjs +75 -36
  58. package/cli/terminal-presentation.mjs +43 -18
  59. package/docs/cli-reference.md +125 -1708
  60. package/docs/cli.md +86 -254
  61. package/frontend/src/lib/analysis/domain-control-manifest-core.js +3 -145
  62. package/frontend/src/lib/analysis/domain-control-records.js +3 -88
  63. package/lib/availability.mjs +58 -40
  64. package/lib/bounded-contract-normalizers.mjs +2 -4
  65. package/lib/bounded-file.mjs +78 -4
  66. package/lib/bounded-json.mjs +51 -13
  67. package/lib/browser-library-profile.mjs +1 -1
  68. package/lib/classify.mjs +56 -5
  69. package/lib/client-behavior-profile.mjs +1 -1
  70. package/lib/credential-surface-profile.mjs +1 -1
  71. package/lib/cryptographic-assurance.mjs +35 -17
  72. package/lib/ct-query.mjs +3 -3
  73. package/lib/ct-search.mjs +37 -21
  74. package/lib/distributed-operation-budget.mjs +4 -2
  75. package/lib/dns-delegation-health.mjs +9 -6
  76. package/lib/dns-intelligence.mjs +1 -1
  77. package/lib/dnssec-chain-validation.mjs +2 -3
  78. package/lib/dnssec-evidence-validation.mjs +13 -7
  79. package/lib/domain-assurance.mjs +12 -12
  80. package/lib/domain-change-packet.mjs +1 -1
  81. package/lib/domain-control-flight-recorder.mjs +442 -78
  82. package/lib/domain-control-manifest.mjs +314 -59
  83. package/lib/domain-posture-analysis.mjs +5 -2
  84. package/lib/encrypted-dns-contract.mjs +2 -3
  85. package/lib/evidence-export.mjs +518 -36
  86. package/lib/external-intelligence-risk.mjs +26 -10
  87. package/lib/feature-policy.mjs +1 -1
  88. package/lib/homepage-metadata-contract.mjs +290 -0
  89. package/lib/hostname.mjs +1 -1
  90. package/lib/html-signals.mjs +89 -5
  91. package/lib/http-intelligence.mjs +331 -2
  92. package/lib/interchange-fidelity-registry.mjs +28 -26
  93. package/lib/lookup-cache.mjs +6 -4
  94. package/lib/lookup.mjs +25 -2
  95. package/lib/observed-network-context.mjs +1 -1
  96. package/lib/page-fingerprints.mjs +2 -2
  97. package/lib/page-role-profile.mjs +1 -1
  98. package/lib/portable-generator.mjs +8 -0
  99. package/lib/provider-policy-admission.mjs +3 -1
  100. package/lib/rdap-bootstrap.mjs +76 -16
  101. package/lib/rdap-client.mjs +20 -12
  102. package/lib/rdap-normalization.mjs +306 -75
  103. package/lib/rdap-transport.mjs +2 -2
  104. package/lib/rdap-validation.mjs +65 -14
  105. package/lib/rdap.mjs +2 -2
  106. package/lib/registrable-domain.mjs +36 -0
  107. package/lib/registry-insights.mjs +7 -0
  108. package/lib/response-policy.mjs +27 -4
  109. package/lib/risk-calibration-summary.mjs +35 -33
  110. package/lib/risk-scoring.mjs +4 -3
  111. package/lib/safe-fetch.mjs +62 -40
  112. package/lib/security-txt.mjs +1 -1
  113. package/lib/smtp-transport-review.mjs +8 -5
  114. package/lib/sslbl-intelligence.mjs +6 -8
  115. package/lib/static-html-analysis.mjs +301 -4
  116. package/lib/structured-data-identity.mjs +51 -19
  117. package/lib/threat-intelligence-fixtures.mjs +2 -4
  118. package/lib/threat-intelligence-manifests.mjs +2 -7
  119. package/lib/threat-intelligence-runtime.mjs +3 -5
  120. package/lib/threat-intelligence-types.mjs +3 -0
  121. package/lib/threatfox-intelligence.mjs +7 -3
  122. package/lib/tls-intelligence.mjs +4 -4
  123. package/lib/urlhaus-intelligence.mjs +4 -2
  124. package/lib/urlscan-intelligence.mjs +6 -5
  125. package/lib/website-security-posture.mjs +1 -1
  126. package/lib/website-technology.mjs +1 -1
  127. package/lib/whois-authority.mjs +43 -8
  128. package/lib/whois-chain.mjs +25 -3
  129. package/lib/whois-normalization.mjs +3 -1
  130. package/lib/whois-transport.mjs +12 -3
  131. package/lib/zone-intent-review.mjs +1 -1
  132. package/package.json +12 -4
  133. package/{frontend/src/lib/analysis/case-brand-profile-references.js → packages/cases/case-brand-profile-references.mjs} +2 -2
  134. package/{frontend/src/lib/analysis/case-evidence-model.js → packages/cases/case-evidence-model.mjs} +35 -14
  135. package/{frontend/src/lib/analysis/case-investigation-branch-model.js → packages/cases/case-investigation-branch-model.mjs} +10 -12
  136. package/packages/cases/case-migration-model.mjs +476 -0
  137. package/{frontend/src/lib/analysis/case-model.js → packages/cases/case-model.mjs} +3 -3
  138. package/{frontend/src/lib/analysis/case-record-contracts.js → packages/cases/case-record-contracts.mjs} +3 -21
  139. package/{frontend/src/lib/analysis/case-record-core.js → packages/cases/case-record-core.mjs} +58 -31
  140. package/packages/cases/case-record-model.mjs +9 -0
  141. package/{frontend/src/lib/analysis/case-record-operations.js → packages/cases/case-record-operations.mjs} +72 -29
  142. package/{frontend/src/lib/analysis/case-report.js → packages/cases/case-report.mjs} +108 -14
  143. package/packages/cases/case-response-model.mjs +1604 -0
  144. package/packages/cases/case-response-packet.mjs +1312 -0
  145. package/{frontend/src/lib/analysis/case-storage-model.js → packages/cases/case-storage-model.mjs} +2 -2
  146. package/{frontend/src/lib/analysis/http-summary.js → packages/cases/http-summary.mjs} +1 -1
  147. package/{frontend/src/lib/analysis/comparison-ledger-bulk.js → packages/comparison/comparison-ledger-bulk.mjs} +2 -2
  148. package/{frontend/src/lib/analysis/comparison-ledger-contract.js → packages/comparison/comparison-ledger-contract.mjs} +4 -4
  149. package/{frontend/src/lib/analysis/comparison-ledger-json.js → packages/comparison/comparison-ledger-json.mjs} +1 -0
  150. package/{frontend/src/lib/analysis/comparison-ledger-serialization.js → packages/comparison/comparison-ledger-serialization.mjs} +1 -0
  151. package/packages/comparison/favicon-similarity.mjs +78 -0
  152. package/{frontend/src/lib/analysis/page-similarity.js → packages/comparison/page-similarity.mjs} +4 -3
  153. package/{frontend/src/lib/analysis/relationship-evidence.js → packages/comparison/relationship-evidence.mjs} +6 -7
  154. package/packages/contracts/analyst-review-state-contract.mjs +61 -0
  155. package/packages/contracts/analyst-review-state.mjs +50 -0
  156. package/packages/contracts/case-portability.mjs +1310 -0
  157. package/packages/contracts/cli-command-catalogue.mjs +2 -0
  158. package/packages/contracts/cli-lookup.mjs +394 -0
  159. package/packages/contracts/domain-control-flight-recorder.mjs +461 -0
  160. package/packages/contracts/domain-control-manifest.mjs +1027 -0
  161. package/packages/contracts/domain-control-monitor.mjs +310 -0
  162. package/packages/contracts/domain-control-review.mjs +828 -0
  163. package/packages/contracts/domain-name.mjs +4 -0
  164. package/packages/contracts/external-observation-interchange.mjs +224 -0
  165. package/packages/contracts/extracted-domain-lifecycle.mjs +218 -0
  166. package/packages/contracts/investigation-portability.mjs +331 -0
  167. package/packages/contracts/offline-comparison.mjs +160 -0
  168. package/packages/contracts/risk-calibration.mjs +1288 -0
  169. package/packages/contracts/schema-compatibility.mjs +71 -0
  170. package/packages/contracts/schema-lifecycle.mjs +1702 -0
  171. package/packages/contracts/sslbl-snapshot.mjs +17 -0
  172. package/packages/contracts/workspace-portability.mjs +1347 -0
  173. package/{frontend/src/lib/analysis/artifact-integrity.js → packages/evidence/artifact-integrity.mjs} +13 -11
  174. package/packages/evidence/decision-fact.mjs +735 -0
  175. package/packages/evidence/domain-control-runtime.mjs +547 -0
  176. package/packages/evidence/domain-name.mjs +41 -0
  177. package/packages/evidence/observation.mjs +384 -0
  178. package/{frontend/src/lib/analysis/external-findings-import.js → packages/interchange/external-findings-import.mjs} +24 -22
  179. package/packages/interchange/retained-text.mjs +8 -0
  180. package/{frontend/src/lib/analysis/acquisition-decision-packet.js → packages/investigation/acquisition-decision-packet.mjs} +9 -6
  181. package/{frontend/src/lib/analysis/bulk-domain-comparison.js → packages/investigation/bulk-domain-comparison.mjs} +5 -6
  182. package/{frontend/src/lib/analysis/bulk-mail-exposure.js → packages/investigation/bulk-mail-exposure.mjs} +4 -6
  183. package/{frontend/src/lib/analysis/bulk-review-export.js → packages/investigation/bulk-review-export.mjs} +5 -5
  184. package/packages/investigation/bulk-review-policy.mjs +1 -0
  185. package/{frontend/src/lib/analysis/investigation-capsule.js → packages/investigation/investigation-capsule.mjs} +30 -32
  186. package/packages/investigation/lookup-artefact-inputs.mjs +1 -0
  187. package/{frontend/src/lib/analysis/lookup-asset-graph.js → packages/investigation/lookup-asset-graph.mjs} +4 -2
  188. package/{frontend/src/lib/analysis/lookup-claim-passport.js → packages/investigation/lookup-claim-passport.mjs} +6 -10
  189. package/{frontend/src/lib/analysis/lookup-claim-readiness.js → packages/investigation/lookup-claim-readiness.mjs} +9 -2
  190. package/packages/investigation/lookup-investigation-brief.mjs +172 -0
  191. package/packages/monitoring/analyst-review-state.mjs +680 -0
  192. package/{frontend/src/lib/analysis/brand-profile-model.js → packages/workspace/brand-profile-model.mjs} +70 -47
  193. package/{frontend/src/lib/analysis/bulk-review-model.js → packages/workspace/bulk-review-model.mjs} +13 -14
  194. package/{frontend/src/lib/analysis/bulk-session-model.js → packages/workspace/bulk-session-model.mjs} +33 -59
  195. package/{frontend/src/lib/analysis/bulk-sort.js → packages/workspace/bulk-sort.mjs} +8 -5
  196. package/{frontend/src/lib/analysis/campaign-model.js → packages/workspace/campaign-model.mjs} +20 -22
  197. package/{frontend/src/lib/analysis/detection-rule-model.js → packages/workspace/detection-rule-model.mjs} +15 -17
  198. package/packages/workspace/hostile-input.mjs +172 -0
  199. package/{frontend/src/lib/analysis/investigation-guide.js → packages/workspace/investigation-guide.mjs} +8 -45
  200. package/{frontend/src/lib/analysis/investigation-template-model.js → packages/workspace/investigation-template-model.mjs} +18 -21
  201. package/{frontend/src/lib/analysis/page-baseline.js → packages/workspace/page-baseline.mjs} +6 -13
  202. package/{frontend/src/lib/analysis/relationship-observation-model.js → packages/workspace/relationship-observation-model.mjs} +13 -17
  203. package/{frontend/src/lib/analysis/shortlist-model.js → packages/workspace/shortlist-model.mjs} +20 -18
  204. package/{frontend/src/lib/analysis/watchlist-history.js → packages/workspace/watchlist-history.mjs} +9 -16
  205. package/{frontend/src/lib/analysis/watchlist-store.js → packages/workspace/watchlist-store.mjs} +24 -17
  206. package/{frontend/src/lib/analysis/website-snapshot-model.js → packages/workspace/website-snapshot-model.mjs} +13 -13
  207. package/{frontend/src/lib/analysis/workspace-archive-crypto.js → packages/workspace/workspace-archive-crypto.mjs} +49 -19
  208. package/{frontend/src/lib/analysis/workspace-archive.js → packages/workspace/workspace-archive.mjs} +110 -89
  209. package/third-party-notices.txt +14 -14
  210. package/frontend/src/lib/analysis/bulk-retry-plan.js +0 -79
  211. package/frontend/src/lib/analysis/bulk-source-coverage.js +0 -76
  212. package/frontend/src/lib/analysis/case-migration-model.js +0 -305
  213. package/frontend/src/lib/analysis/case-record-model.js +0 -9
  214. package/frontend/src/lib/analysis/case-response-model.js +0 -728
  215. package/frontend/src/lib/analysis/case-response-packet.js +0 -583
  216. package/frontend/src/lib/analysis/lookup-decision-support.js +0 -542
  217. package/frontend/src/lib/analysis/lookup-investigation-brief.js +0 -153
  218. package/frontend/src/lib/analysis/lookup-presentation.js +0 -120
  219. package/frontend/src/lib/analysis/lookup-response.js +0 -2
  220. package/frontend/src/lib/analysis/lookup-source-refresh.js +0 -277
  221. package/frontend/src/lib/analysis/lookup-summary-model.js +0 -398
  222. package/frontend/src/lib/analysis/scoring.js +0 -55
  223. package/frontend/src/lib/analysis/utils.js +0 -255
  224. package/frontend/src/lib/analysis/visualization-bounds.js +0 -26
  225. package/frontend/src/lib/analysis/visualization-force-graph.js +0 -352
  226. package/frontend/src/lib/analysis/visualization-matrix.js +0 -131
  227. package/frontend/src/lib/analysis/visualization-models.js +0 -338
  228. package/frontend/src/lib/analysis/visualization-time-series.js +0 -185
  229. package/lib/bounded-json-response.mjs +0 -170
  230. package/lib/lookup-response-contract.mjs +0 -1034
  231. package/lib/observation.mjs +0 -115
  232. /package/{frontend/src/lib/analysis/opaque-reference-id.js → packages/cases/opaque-reference-id.mjs} +0 -0
  233. /package/{frontend/src/lib/analysis/acquisition-due-diligence.js → packages/investigation/acquisition-due-diligence.mjs} +0 -0
  234. /package/{frontend/src/lib/analysis/evidence-coverage-ledger.js → packages/investigation/evidence-coverage-ledger.mjs} +0 -0
  235. /package/{frontend/src/lib/analysis/dns-record-normalization.js → packages/workspace/dns-record-normalization.mjs} +0 -0
@@ -0,0 +1,1347 @@
1
+ import { WORKSPACE_ARCHIVE_SECTION_IDS } from "./case-portability.mjs";
2
+ import { defineSchemaCompatibility } from "./schema-compatibility.mjs";
3
+ import { defineSchemaLifecycleFamily, } from "./schema-lifecycle.mjs";
4
+ export const WORKSPACE_CONTRACT_OWNER = 'packages/contracts/workspace-portability.mts';
5
+ export const BRAND_PROFILE_SCHEMA = 'whoisleuth.brand-profiles';
6
+ export const PUBLIC_BRAND_PROFILE_SCHEMA_VERSION = 6;
7
+ export const BRAND_PROFILE_SCHEMA_VERSION = 7;
8
+ export const BRAND_PROFILE_BROWSER_SUPPORTED_VERSIONS = Object.freeze([PUBLIC_BRAND_PROFILE_SCHEMA_VERSION, BRAND_PROFILE_SCHEMA_VERSION]);
9
+ export const SUPPORTED_BRAND_PROFILE_SCHEMA_VERSIONS = BRAND_PROFILE_BROWSER_SUPPORTED_VERSIONS;
10
+ export const MAX_PROFILES = 100;
11
+ export const MAX_PROFILE_VALUES = 200;
12
+ export const MAX_PROFILE_VALUE_INPUTS = MAX_PROFILE_VALUES * 4;
13
+ export const MAX_PROFILE_STORE_BYTES = 1024 * 1024;
14
+ export const MAX_PROFILE_IMPORT_BYTES = 2 * 1024 * 1024;
15
+ export const MAX_PROFILE_NAME_LENGTH = 100;
16
+ export const MAX_PROFILE_TEXT_LENGTH = 200;
17
+ export const MAX_PROFILE_DOMAIN_LENGTH = 253;
18
+ export const MAX_PROFILE_TLD_LENGTH = 63;
19
+ export const MAX_DKIM_SELECTOR_LENGTH = 253;
20
+ export const MAX_DKIM_SELECTORS = 10;
21
+ export const MAX_PROTECTION_ATTESTATIONS = 6;
22
+ export const MAX_DESIRED_POSTURE_BASELINES = 20;
23
+ export const MAX_DESIRED_POSTURE_RECORDS = 32;
24
+ export const MAX_DESIRED_POSTURE_SUPPRESSIONS = 12;
25
+ export const MAX_DESIRED_POSTURE_OBSERVATIONS = 12;
26
+ export const MAX_DESIRED_POSTURE_CHANGE_WINDOWS = 8;
27
+ export const CAMPAIGN_SCHEMA = 'whoisleuth.campaigns';
28
+ export const CAMPAIGN_SCHEMA_VERSION = 1;
29
+ export const CAMPAIGN_BROWSER_SUPPORTED_VERSIONS = Object.freeze([CAMPAIGN_SCHEMA_VERSION]);
30
+ export const CAMPAIGN_EXPORT_SUPPORTED_VERSIONS = Object.freeze([CAMPAIGN_SCHEMA_VERSION]);
31
+ export const MAX_CAMPAIGNS = 50;
32
+ export const MAX_CAMPAIGN_DOMAINS = 50;
33
+ export const MAX_CAMPAIGN_NAME_LENGTH = 100;
34
+ export const MAX_CAMPAIGN_DESCRIPTION_LENGTH = 1000;
35
+ export const MAX_CAMPAIGN_IMPORT_BYTES = 2 * 1024 * 1024;
36
+ export const MAX_CAMPAIGN_INPUT_RECORDS = 500;
37
+ export const MAX_CAMPAIGN_STORE_BYTES = 512 * 1024;
38
+ export const WATCHLIST_SCHEMA = 'whoisleuth.watchlists';
39
+ export const WATCHLIST_SCHEMA_VERSION = 2;
40
+ export const WATCHLIST_BROWSER_SUPPORTED_VERSIONS = Object.freeze([WATCHLIST_SCHEMA_VERSION]);
41
+ export const WATCHLIST_EXPORT_SUPPORTED_VERSIONS = Object.freeze([WATCHLIST_SCHEMA_VERSION]);
42
+ export const MAX_WATCHLISTS = 100;
43
+ export const MAX_WATCHLIST_INPUTS = MAX_WATCHLISTS * 4;
44
+ export const MAX_WATCHLIST_NAME_LENGTH = 100;
45
+ export const MAX_WATCHLIST_STORE_BYTES = 2 * 1024 * 1024;
46
+ export const MAX_WATCHLIST_IMPORT_BYTES = 2 * 1024 * 1024;
47
+ export const MAX_WATCHLIST_HISTORY_EVENTS = 12;
48
+ export const MAX_WATCHLIST_CHANGES_PER_EVENT = 500;
49
+ export const MAX_WATCHLIST_DOMAINS = 2_000;
50
+ export const MAX_WATCHLIST_INPUT_RECORDS = MAX_WATCHLIST_DOMAINS * 2;
51
+ export const MAX_WATCHLIST_NAMESERVERS = 12;
52
+ export const MAX_WATCHLIST_MUTATION_TYPES = 30;
53
+ export const MAX_WATCHLIST_HISTORY_DOMAIN_OPTIONS = MAX_WATCHLIST_DOMAINS;
54
+ export const SHORTLIST_SCHEMA = 'whoisleuth.shortlist';
55
+ export const SHORTLIST_SCHEMA_VERSION = 3;
56
+ export const SHORTLIST_BROWSER_SUPPORTED_VERSIONS = Object.freeze([SHORTLIST_SCHEMA_VERSION]);
57
+ export const SUPPORTED_SHORTLIST_SCHEMA_VERSIONS = SHORTLIST_BROWSER_SUPPORTED_VERSIONS;
58
+ export const MAX_SHORTLIST_ENTRIES = 500;
59
+ export const MAX_SHORTLIST_INPUTS = MAX_SHORTLIST_ENTRIES * 4;
60
+ export const MAX_SHORTLIST_STORE_BYTES = 1024 * 1024;
61
+ export const MAX_SHORTLIST_IMPORT_BYTES = 2 * 1024 * 1024;
62
+ export const MAX_SHORTLIST_FACTORS = 20;
63
+ export const CT_HISTORY_SCHEMA_VERSION = 3;
64
+ export const CT_HISTORY_BROWSER_SUPPORTED_VERSIONS = Object.freeze([CT_HISTORY_SCHEMA_VERSION]);
65
+ export const MAX_CT_HISTORY_SEARCHES = 30;
66
+ export const MAX_CT_HISTORY_EVENTS = 20;
67
+ export const MAX_CT_HISTORY_DISCARDED_CHECKS = 1_000_000;
68
+ export const MAX_CT_HISTORY_DOMAINS = 500;
69
+ export const MAX_CT_HISTORY_NEW_DOMAINS = 100;
70
+ export const MAX_CT_HISTORY_EVER_SEEN_DOMAINS = 1_000;
71
+ export const MAX_CT_HISTORY_QUERY_LENGTH = 200;
72
+ export const MAX_CT_HISTORY_STORE_BYTES = 1024 * 1024;
73
+ export const DETECTION_RULE_SCHEMA = 'whoisleuth.detection-rules';
74
+ export const DETECTION_RULE_SCHEMA_VERSION = 1;
75
+ export const DETECTION_RULE_BROWSER_SUPPORTED_VERSIONS = Object.freeze([DETECTION_RULE_SCHEMA_VERSION]);
76
+ export const DETECTION_RULE_EXPORT_SUPPORTED_VERSIONS = Object.freeze([DETECTION_RULE_SCHEMA_VERSION]);
77
+ export const MAX_DETECTION_RULES = 50;
78
+ export const MAX_RULE_CONDITIONS = 8;
79
+ export const MAX_RULE_INPUT_RECORDS = 250;
80
+ export const MAX_RULE_IMPORT_BYTES = 2 * 1024 * 1024;
81
+ export const MAX_RULE_STORE_BYTES = 256 * 1024;
82
+ export const MAX_RULE_NAME_LENGTH = 100;
83
+ export const MAX_RULE_TAG_LENGTH = 40;
84
+ export const MAX_CONDITION_VALUE_LENGTH = 200;
85
+ export const MAX_RULE_RISK_DELTA = 25;
86
+ export const MAX_CUSTOM_RISK_TOTAL = 50;
87
+ export const RELATIONSHIP_OBSERVATION_SCHEMA = 'whoisleuth.relationship-observations';
88
+ export const RELATIONSHIP_OBSERVATION_SCHEMA_VERSION = 1;
89
+ export const RELATIONSHIP_OBSERVATION_BROWSER_SUPPORTED_VERSIONS = Object.freeze([RELATIONSHIP_OBSERVATION_SCHEMA_VERSION]);
90
+ export const RELATIONSHIP_OBSERVATION_EXPORT_SUPPORTED_VERSIONS = Object.freeze([RELATIONSHIP_OBSERVATION_SCHEMA_VERSION]);
91
+ export const MAX_RELATIONSHIP_OBSERVATIONS = 300;
92
+ export const MAX_RELATIONSHIP_OBSERVATION_INPUTS = 1_200;
93
+ export const MAX_RELATIONSHIP_OBSERVATION_DOMAINS = 50;
94
+ export const MAX_RELATIONSHIP_OBSERVATION_LIMITATIONS = 8;
95
+ export const MAX_RELATIONSHIP_OBSERVATION_VALUE_LENGTH = 20_000;
96
+ export const MAX_RELATIONSHIP_OBSERVATION_STORE_BYTES = 768 * 1024;
97
+ export const WEBSITE_SNAPSHOT_SCHEMA = 'whoisleuth.website-profile-snapshots';
98
+ export const WEBSITE_SNAPSHOT_SCHEMA_VERSION = 4;
99
+ export const WEBSITE_SNAPSHOT_BROWSER_SUPPORTED_VERSIONS = Object.freeze([WEBSITE_SNAPSHOT_SCHEMA_VERSION]);
100
+ export const SUPPORTED_WEBSITE_SNAPSHOT_SCHEMA_VERSIONS = WEBSITE_SNAPSHOT_BROWSER_SUPPORTED_VERSIONS;
101
+ export const MAX_WEBSITE_SNAPSHOTS = 60;
102
+ export const MAX_WEBSITE_SNAPSHOTS_PER_DOMAIN = 12;
103
+ export const MAX_WEBSITE_SNAPSHOT_STORE_BYTES = 512 * 1024;
104
+ export const MAX_WEBSITE_SNAPSHOT_IMPORT_BYTES = 768 * 1024;
105
+ export const BULK_SESSION_SCHEMA = 'whoisleuth.bulk-sessions';
106
+ export const BULK_SESSION_SCHEMA_VERSION = 4;
107
+ export const BULK_SESSION_BROWSER_SUPPORTED_VERSIONS = Object.freeze([BULK_SESSION_SCHEMA_VERSION]);
108
+ export const SUPPORTED_BULK_SESSION_SCHEMA_VERSIONS = BULK_SESSION_BROWSER_SUPPORTED_VERSIONS;
109
+ export const MAX_BULK_SESSIONS = 10;
110
+ export const MAX_BULK_SESSION_ROWS = 2_000;
111
+ export const MAX_BULK_SESSION_STORE_BYTES = 4 * 1024 * 1024;
112
+ export const MAX_BULK_SESSION_NAME_LENGTH = 100;
113
+ export const MAX_BULK_SESSION_TEXT_LENGTH = 500;
114
+ export const MAX_BULK_SESSION_ARRAY_VALUES = 100;
115
+ export const MAX_BULK_SESSION_SOURCES = 12;
116
+ export const MAX_BULK_PROFILE_CONTEXT_LIMITATION_LENGTH = 300;
117
+ export const INVESTIGATION_TEMPLATE_SCHEMA = 'whoisleuth.investigation-templates';
118
+ export const INVESTIGATION_TEMPLATE_VERSION = 2;
119
+ export const INVESTIGATION_TEMPLATE_SUPPORTED_VERSIONS = Object.freeze([INVESTIGATION_TEMPLATE_VERSION]);
120
+ export const MAX_INVESTIGATION_TEMPLATES = 20;
121
+ export const MAX_INVESTIGATION_TEMPLATE_STORE_BYTES = 256 * 1024;
122
+ export const MAX_INVESTIGATION_TEMPLATE_IMPORT_BYTES = 384 * 1024;
123
+ export const BULK_REVIEW_SCHEMA = 'whoisleuth.bulk-review';
124
+ export const BULK_REVIEW_SCHEMA_VERSION = 1;
125
+ export const BULK_REVIEW_BROWSER_SUPPORTED_VERSIONS = Object.freeze([BULK_REVIEW_SCHEMA_VERSION]);
126
+ export const BULK_REVIEW_EXPORT_SUPPORTED_VERSIONS = Object.freeze([BULK_REVIEW_SCHEMA_VERSION]);
127
+ export const MAX_BULK_REVIEW_PRESETS = 24;
128
+ export const MAX_BULK_REVIEW_ROWS = 1_900;
129
+ export const MAX_BULK_REVIEW_STORE_BYTES = 512 * 1024;
130
+ export const MAX_BULK_REVIEW_NAME_LENGTH = 80;
131
+ export const MAX_WORKSPACE_INPUT_GRAPH_DEPTH = 32;
132
+ export const MAX_WORKSPACE_INPUT_GRAPH_NODES = 25_000;
133
+ export const MAX_WORKSPACE_INPUT_ARRAY_LENGTH = 10_000;
134
+ export const MAX_WORKSPACE_INPUT_OBJECT_KEYS = 4_096;
135
+ export const MAX_WORKSPACE_INPUT_STRING_CODE_UNITS = 8 * 1024 * 1024;
136
+ export const PAGE_BASELINE_VERSION = 1;
137
+ export const PAGE_IDENTITY_VERSION = 3;
138
+ export const PAGE_FINGERPRINT_VERSION = 1;
139
+ export const MAX_BASELINE_TITLE_LENGTH = 200;
140
+ export const MAX_BASELINE_RESOURCE_HOSTS = 30;
141
+ export const MAX_BASELINE_IDENTIFIERS = 30;
142
+ export const BRAND_PROFILE_BROWSER_STORE_LIFECYCLE_SCHEMA = 'whoisleuth.browser.brand-profile-store';
143
+ export const CAMPAIGN_BROWSER_STORE_LIFECYCLE_SCHEMA = 'whoisleuth.browser.campaign-store';
144
+ export const WATCHLIST_BROWSER_STORE_LIFECYCLE_SCHEMA = 'whoisleuth.browser.watchlist-store';
145
+ export const SHORTLIST_BROWSER_STORE_LIFECYCLE_SCHEMA = 'whoisleuth.browser.shortlist-store';
146
+ export const CT_HISTORY_BROWSER_STORE_LIFECYCLE_SCHEMA = 'whoisleuth.browser.ct-history-store';
147
+ export const DETECTION_RULE_BROWSER_STORE_LIFECYCLE_SCHEMA = 'whoisleuth.browser.detection-rule-store';
148
+ export const RELATIONSHIP_OBSERVATION_BROWSER_STORE_LIFECYCLE_SCHEMA = 'whoisleuth.browser.relationship-observation-store';
149
+ export const WEBSITE_SNAPSHOT_BROWSER_STORE_LIFECYCLE_SCHEMA = 'whoisleuth.browser.website-snapshot-store';
150
+ export const BULK_SESSION_BROWSER_STORE_LIFECYCLE_SCHEMA = 'whoisleuth.browser.bulk-session-store';
151
+ export const INVESTIGATION_TEMPLATE_BROWSER_STORE_LIFECYCLE_SCHEMA = 'whoisleuth.browser.investigation-template-store';
152
+ export const BULK_REVIEW_BROWSER_STORE_LIFECYCLE_SCHEMA = 'whoisleuth.browser.bulk-review-store';
153
+ export const WORKSPACE_PORTABILITY_IDENTITY_CONSTANTS = Object.freeze([
154
+ 'BRAND_PROFILE_SCHEMA', 'BRAND_PROFILE_SCHEMA_VERSION', 'BRAND_PROFILE_BROWSER_SUPPORTED_VERSIONS', 'SUPPORTED_BRAND_PROFILE_SCHEMA_VERSIONS',
155
+ 'CAMPAIGN_SCHEMA', 'CAMPAIGN_SCHEMA_VERSION', 'CAMPAIGN_BROWSER_SUPPORTED_VERSIONS', 'CAMPAIGN_EXPORT_SUPPORTED_VERSIONS',
156
+ 'WATCHLIST_SCHEMA', 'WATCHLIST_SCHEMA_VERSION', 'WATCHLIST_BROWSER_SUPPORTED_VERSIONS', 'WATCHLIST_EXPORT_SUPPORTED_VERSIONS',
157
+ 'SHORTLIST_SCHEMA', 'SHORTLIST_SCHEMA_VERSION', 'SHORTLIST_BROWSER_SUPPORTED_VERSIONS', 'SUPPORTED_SHORTLIST_SCHEMA_VERSIONS',
158
+ 'CT_HISTORY_SCHEMA_VERSION', 'CT_HISTORY_BROWSER_SUPPORTED_VERSIONS',
159
+ 'DETECTION_RULE_SCHEMA', 'DETECTION_RULE_SCHEMA_VERSION', 'DETECTION_RULE_BROWSER_SUPPORTED_VERSIONS', 'DETECTION_RULE_EXPORT_SUPPORTED_VERSIONS',
160
+ 'RELATIONSHIP_OBSERVATION_SCHEMA', 'RELATIONSHIP_OBSERVATION_SCHEMA_VERSION', 'RELATIONSHIP_OBSERVATION_BROWSER_SUPPORTED_VERSIONS', 'RELATIONSHIP_OBSERVATION_EXPORT_SUPPORTED_VERSIONS',
161
+ 'WEBSITE_SNAPSHOT_SCHEMA', 'WEBSITE_SNAPSHOT_SCHEMA_VERSION', 'WEBSITE_SNAPSHOT_BROWSER_SUPPORTED_VERSIONS', 'SUPPORTED_WEBSITE_SNAPSHOT_SCHEMA_VERSIONS',
162
+ 'BULK_SESSION_SCHEMA', 'BULK_SESSION_SCHEMA_VERSION', 'BULK_SESSION_BROWSER_SUPPORTED_VERSIONS', 'SUPPORTED_BULK_SESSION_SCHEMA_VERSIONS',
163
+ 'INVESTIGATION_TEMPLATE_SCHEMA', 'INVESTIGATION_TEMPLATE_VERSION', 'INVESTIGATION_TEMPLATE_SUPPORTED_VERSIONS',
164
+ 'BULK_REVIEW_SCHEMA', 'BULK_REVIEW_SCHEMA_VERSION', 'BULK_REVIEW_BROWSER_SUPPORTED_VERSIONS', 'BULK_REVIEW_EXPORT_SUPPORTED_VERSIONS',
165
+ 'BRAND_PROFILE_BROWSER_STORE_LIFECYCLE_SCHEMA', 'CAMPAIGN_BROWSER_STORE_LIFECYCLE_SCHEMA', 'WATCHLIST_BROWSER_STORE_LIFECYCLE_SCHEMA',
166
+ 'SHORTLIST_BROWSER_STORE_LIFECYCLE_SCHEMA', 'CT_HISTORY_BROWSER_STORE_LIFECYCLE_SCHEMA', 'DETECTION_RULE_BROWSER_STORE_LIFECYCLE_SCHEMA',
167
+ 'RELATIONSHIP_OBSERVATION_BROWSER_STORE_LIFECYCLE_SCHEMA', 'WEBSITE_SNAPSHOT_BROWSER_STORE_LIFECYCLE_SCHEMA',
168
+ 'BULK_SESSION_BROWSER_STORE_LIFECYCLE_SCHEMA', 'INVESTIGATION_TEMPLATE_BROWSER_STORE_LIFECYCLE_SCHEMA', 'BULK_REVIEW_BROWSER_STORE_LIFECYCLE_SCHEMA',
169
+ ]);
170
+ export const WORKSPACE_PORTABILITY_BOUND_CONSTANTS = Object.freeze([
171
+ 'MAX_PROFILES', 'MAX_PROFILE_VALUES', 'MAX_PROFILE_VALUE_INPUTS', 'MAX_PROFILE_STORE_BYTES', 'MAX_PROFILE_IMPORT_BYTES',
172
+ 'MAX_PROFILE_NAME_LENGTH', 'MAX_PROFILE_TEXT_LENGTH', 'MAX_PROFILE_DOMAIN_LENGTH', 'MAX_PROFILE_TLD_LENGTH', 'MAX_DKIM_SELECTOR_LENGTH',
173
+ 'MAX_DKIM_SELECTORS', 'MAX_PROTECTION_ATTESTATIONS', 'MAX_DESIRED_POSTURE_BASELINES', 'MAX_DESIRED_POSTURE_RECORDS',
174
+ 'MAX_DESIRED_POSTURE_SUPPRESSIONS', 'MAX_DESIRED_POSTURE_OBSERVATIONS', 'MAX_DESIRED_POSTURE_CHANGE_WINDOWS',
175
+ 'MAX_CAMPAIGNS', 'MAX_CAMPAIGN_DOMAINS', 'MAX_CAMPAIGN_NAME_LENGTH', 'MAX_CAMPAIGN_DESCRIPTION_LENGTH',
176
+ 'MAX_CAMPAIGN_IMPORT_BYTES', 'MAX_CAMPAIGN_INPUT_RECORDS', 'MAX_CAMPAIGN_STORE_BYTES',
177
+ 'MAX_WATCHLISTS', 'MAX_WATCHLIST_INPUTS', 'MAX_WATCHLIST_NAME_LENGTH', 'MAX_WATCHLIST_STORE_BYTES', 'MAX_WATCHLIST_IMPORT_BYTES',
178
+ 'MAX_WATCHLIST_HISTORY_EVENTS', 'MAX_WATCHLIST_CHANGES_PER_EVENT', 'MAX_WATCHLIST_DOMAINS', 'MAX_WATCHLIST_INPUT_RECORDS',
179
+ 'MAX_WATCHLIST_NAMESERVERS', 'MAX_WATCHLIST_MUTATION_TYPES', 'MAX_WATCHLIST_HISTORY_DOMAIN_OPTIONS',
180
+ 'MAX_SHORTLIST_ENTRIES', 'MAX_SHORTLIST_INPUTS', 'MAX_SHORTLIST_STORE_BYTES', 'MAX_SHORTLIST_IMPORT_BYTES', 'MAX_SHORTLIST_FACTORS',
181
+ 'MAX_CT_HISTORY_SEARCHES', 'MAX_CT_HISTORY_EVENTS', 'MAX_CT_HISTORY_DISCARDED_CHECKS', 'MAX_CT_HISTORY_DOMAINS',
182
+ 'MAX_CT_HISTORY_NEW_DOMAINS', 'MAX_CT_HISTORY_EVER_SEEN_DOMAINS', 'MAX_CT_HISTORY_QUERY_LENGTH', 'MAX_CT_HISTORY_STORE_BYTES',
183
+ 'MAX_DETECTION_RULES', 'MAX_RULE_CONDITIONS', 'MAX_RULE_INPUT_RECORDS', 'MAX_RULE_IMPORT_BYTES', 'MAX_RULE_STORE_BYTES',
184
+ 'MAX_RULE_NAME_LENGTH', 'MAX_RULE_TAG_LENGTH', 'MAX_CONDITION_VALUE_LENGTH', 'MAX_RULE_RISK_DELTA', 'MAX_CUSTOM_RISK_TOTAL',
185
+ 'MAX_RELATIONSHIP_OBSERVATIONS', 'MAX_RELATIONSHIP_OBSERVATION_INPUTS', 'MAX_RELATIONSHIP_OBSERVATION_DOMAINS',
186
+ 'MAX_RELATIONSHIP_OBSERVATION_LIMITATIONS', 'MAX_RELATIONSHIP_OBSERVATION_VALUE_LENGTH', 'MAX_RELATIONSHIP_OBSERVATION_STORE_BYTES',
187
+ 'MAX_WEBSITE_SNAPSHOTS', 'MAX_WEBSITE_SNAPSHOTS_PER_DOMAIN', 'MAX_WEBSITE_SNAPSHOT_STORE_BYTES', 'MAX_WEBSITE_SNAPSHOT_IMPORT_BYTES',
188
+ 'MAX_BULK_SESSIONS', 'MAX_BULK_SESSION_ROWS', 'MAX_BULK_SESSION_STORE_BYTES', 'MAX_BULK_SESSION_NAME_LENGTH',
189
+ 'MAX_BULK_SESSION_TEXT_LENGTH', 'MAX_BULK_SESSION_ARRAY_VALUES', 'MAX_BULK_SESSION_SOURCES', 'MAX_BULK_PROFILE_CONTEXT_LIMITATION_LENGTH',
190
+ 'MAX_INVESTIGATION_TEMPLATES', 'MAX_INVESTIGATION_TEMPLATE_STORE_BYTES', 'MAX_INVESTIGATION_TEMPLATE_IMPORT_BYTES',
191
+ 'MAX_BULK_REVIEW_PRESETS', 'MAX_BULK_REVIEW_ROWS', 'MAX_BULK_REVIEW_STORE_BYTES', 'MAX_BULK_REVIEW_NAME_LENGTH',
192
+ 'MAX_WORKSPACE_INPUT_GRAPH_DEPTH', 'MAX_WORKSPACE_INPUT_GRAPH_NODES', 'MAX_WORKSPACE_INPUT_ARRAY_LENGTH',
193
+ 'MAX_WORKSPACE_INPUT_OBJECT_KEYS', 'MAX_WORKSPACE_INPUT_STRING_CODE_UNITS',
194
+ 'PAGE_BASELINE_VERSION', 'PAGE_IDENTITY_VERSION', 'PAGE_FINGERPRINT_VERSION', 'MAX_BASELINE_TITLE_LENGTH',
195
+ 'MAX_BASELINE_RESOURCE_HOSTS', 'MAX_BASELINE_IDENTIFIERS',
196
+ ]);
197
+ export const WORKSPACE_DOMAIN_COMPATIBILITY_FACADES = Object.freeze([
198
+ ['frontend/src/lib/analysis/brand-profile-model.ts', 'packages/workspace/brand-profile-model.mts'],
199
+ ['frontend/src/lib/analysis/campaign-model.ts', 'packages/workspace/campaign-model.mts'],
200
+ ['frontend/src/lib/analysis/watchlist-store.ts', 'packages/workspace/watchlist-store.mts'],
201
+ ['frontend/src/lib/analysis/shortlist-model.ts', 'packages/workspace/shortlist-model.mts'],
202
+ ['frontend/src/lib/analysis/ct-history.ts', 'packages/workspace/ct-history.mts'],
203
+ ['frontend/src/lib/analysis/detection-rule-model.ts', 'packages/workspace/detection-rule-model.mts'],
204
+ ['frontend/src/lib/analysis/relationship-observation-model.ts', 'packages/workspace/relationship-observation-model.mts'],
205
+ ['frontend/src/lib/analysis/website-snapshot-model.ts', 'packages/workspace/website-snapshot-model.mts'],
206
+ ['frontend/src/lib/analysis/bulk-session-model.ts', 'packages/workspace/bulk-session-model.mts'],
207
+ ['frontend/src/lib/analysis/investigation-template-model.ts', 'packages/workspace/investigation-template-model.mts'],
208
+ ['frontend/src/lib/analysis/bulk-review-model.ts', 'packages/workspace/bulk-review-model.mts'],
209
+ ['frontend/src/lib/analysis/watchlist-history.ts', 'packages/workspace/watchlist-history.mts'],
210
+ ['frontend/src/lib/analysis/page-baseline.ts', 'packages/workspace/page-baseline.mts'],
211
+ ['frontend/src/lib/analysis/bulk-sort.ts', 'packages/workspace/bulk-sort.mts'],
212
+ ['frontend/src/lib/analysis/dns-record-normalization.ts', 'packages/workspace/dns-record-normalization.mts'],
213
+ ['frontend/src/lib/analysis/investigation-guide.ts', 'packages/workspace/investigation-guide.mts'],
214
+ ]);
215
+ const [, CAMPAIGNS_SECTION_ID, BRAND_PROFILES_SECTION_ID, WATCHLISTS_SECTION_ID, SHORTLIST_SECTION_ID, DETECTION_RULES_SECTION_ID, RELATIONSHIP_OBSERVATIONS_SECTION_ID, BULK_SESSIONS_SECTION_ID, WEBSITE_SNAPSHOTS_SECTION_ID, INVESTIGATION_TEMPLATES_SECTION_ID, BULK_REVIEW_SECTION_ID] = WORKSPACE_ARCHIVE_SECTION_IDS;
216
+ export const BRAND_PROFILE_BROWSER_COMPATIBILITY = defineSchemaCompatibility({
217
+ id: 'browser.brand-profiles', kind: 'browser_store', schema: null, currentVersion: BRAND_PROFILE_SCHEMA_VERSION,
218
+ supportedVersions: BRAND_PROFILE_BROWSER_SUPPORTED_VERSIONS, acceptsUnversionedLegacy: false,
219
+ futureVersionBehavior: 'preserve_without_write', migration: 'normalize_to_current', writeSemantics: 'normalized_rewrite',
220
+ byteBudget: MAX_PROFILE_STORE_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
221
+ note: 'Exact public version 6 profiles migrate directly to version 7, which gives each approved change window a durable opaque identity; other historical stores are outside the compatibility boundary.',
222
+ });
223
+ export const CAMPAIGN_BROWSER_COMPATIBILITY = defineSchemaCompatibility({
224
+ id: 'browser.campaigns', kind: 'browser_store', schema: null, currentVersion: CAMPAIGN_SCHEMA_VERSION,
225
+ supportedVersions: CAMPAIGN_BROWSER_SUPPORTED_VERSIONS, acceptsUnversionedLegacy: false,
226
+ futureVersionBehavior: 'preserve_without_write', migration: 'exact_current_only', writeSemantics: 'normalized_rewrite',
227
+ byteBudget: MAX_CAMPAIGN_STORE_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
228
+ note: 'The unchanged public version 1 envelope is the browser baseline; unsupported roots and explicit future versions are preserved without write.',
229
+ });
230
+ export const WATCHLIST_BROWSER_COMPATIBILITY = defineSchemaCompatibility({
231
+ id: 'browser.watchlists', kind: 'browser_store', schema: null, currentVersion: WATCHLIST_SCHEMA_VERSION,
232
+ supportedVersions: WATCHLIST_BROWSER_SUPPORTED_VERSIONS, acceptsUnversionedLegacy: false,
233
+ futureVersionBehavior: 'preserve_without_write', migration: 'exact_current_only', writeSemantics: 'normalized_rewrite',
234
+ byteBudget: MAX_WATCHLIST_STORE_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
235
+ note: 'The unchanged public version 2 envelope is the browser baseline; unversioned maps are outside the compatibility boundary.',
236
+ });
237
+ export const SHORTLIST_BROWSER_COMPATIBILITY = defineSchemaCompatibility({
238
+ id: 'browser.shortlist', kind: 'browser_store', schema: null, currentVersion: SHORTLIST_SCHEMA_VERSION,
239
+ supportedVersions: SHORTLIST_BROWSER_SUPPORTED_VERSIONS, acceptsUnversionedLegacy: false,
240
+ futureVersionBehavior: 'preserve_without_write', migration: 'exact_current_only', writeSemantics: 'normalized_rewrite',
241
+ byteBudget: MAX_SHORTLIST_STORE_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
242
+ note: 'The unchanged public version 3 envelope is the browser baseline; unversioned arrays are outside the compatibility boundary.',
243
+ });
244
+ export const CT_HISTORY_BROWSER_COMPATIBILITY = defineSchemaCompatibility({
245
+ id: 'browser.ct-history', kind: 'browser_store', schema: null, currentVersion: CT_HISTORY_SCHEMA_VERSION,
246
+ supportedVersions: CT_HISTORY_BROWSER_SUPPORTED_VERSIONS, acceptsUnversionedLegacy: false,
247
+ futureVersionBehavior: 'preserve_without_write', migration: 'exact_current_only', writeSemantics: 'normalized_rewrite',
248
+ byteBudget: MAX_CT_HISTORY_STORE_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
249
+ note: 'The unchanged public version 3 writer is the v2 browser baseline; future stores are preserved without write.',
250
+ });
251
+ export const DETECTION_RULE_BROWSER_COMPATIBILITY = defineSchemaCompatibility({
252
+ id: 'browser.detection-rules', kind: 'browser_store', schema: null, currentVersion: DETECTION_RULE_SCHEMA_VERSION,
253
+ supportedVersions: DETECTION_RULE_BROWSER_SUPPORTED_VERSIONS, acceptsUnversionedLegacy: false,
254
+ futureVersionBehavior: 'preserve_without_write', migration: 'exact_current_only', writeSemantics: 'normalized_rewrite',
255
+ byteBudget: MAX_RULE_STORE_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
256
+ note: 'Only allowlisted structured rule fields and operators survive normalisation; the schema string belongs to portable exports.',
257
+ });
258
+ export const RELATIONSHIP_OBSERVATION_BROWSER_COMPATIBILITY = defineSchemaCompatibility({
259
+ id: 'browser.relationship-observations', kind: 'browser_store', schema: null,
260
+ currentVersion: RELATIONSHIP_OBSERVATION_SCHEMA_VERSION, supportedVersions: RELATIONSHIP_OBSERVATION_BROWSER_SUPPORTED_VERSIONS,
261
+ acceptsUnversionedLegacy: false, futureVersionBehavior: 'preserve_without_write', migration: 'exact_current_only',
262
+ writeSemantics: 'normalized_rewrite', byteBudget: MAX_RELATIONSHIP_OBSERVATION_STORE_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
263
+ note: 'Only explicit analyst selections from bounded Bulk relationship evidence are retained; identities are re-derived from normalised values and members.',
264
+ });
265
+ export const WEBSITE_SNAPSHOT_BROWSER_COMPATIBILITY = defineSchemaCompatibility({
266
+ id: 'browser.website-snapshots', kind: 'browser_store', schema: null,
267
+ currentVersion: WEBSITE_SNAPSHOT_SCHEMA_VERSION, supportedVersions: WEBSITE_SNAPSHOT_BROWSER_SUPPORTED_VERSIONS,
268
+ acceptsUnversionedLegacy: false, futureVersionBehavior: 'preserve_without_write', migration: 'exact_current_only',
269
+ writeSemantics: 'normalized_rewrite', byteBudget: MAX_WEBSITE_SNAPSHOT_STORE_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
270
+ note: 'The unchanged public version 4 writer remains the exact browser and export baseline; raw lookup payloads, contact data, certificate bytes, and fetched bodies remain excluded.',
271
+ });
272
+ export const BULK_SESSION_BROWSER_COMPATIBILITY = defineSchemaCompatibility({
273
+ id: 'browser.bulk-sessions', kind: 'browser_store', schema: null, currentVersion: BULK_SESSION_SCHEMA_VERSION,
274
+ supportedVersions: BULK_SESSION_BROWSER_SUPPORTED_VERSIONS, acceptsUnversionedLegacy: false,
275
+ futureVersionBehavior: 'preserve_without_write', migration: 'exact_current_only', writeSemantics: 'normalized_rewrite',
276
+ byteBudget: MAX_BULK_SESSION_STORE_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
277
+ note: 'The unchanged public schema 4 writer is the exact browser baseline with bounded Brand Profile provenance; unsupported local checkpoints are preserved without rewrite.',
278
+ });
279
+ export const INVESTIGATION_TEMPLATE_BROWSER_COMPATIBILITY = defineSchemaCompatibility({
280
+ id: 'browser.investigation-templates', kind: 'browser_store', schema: null,
281
+ currentVersion: INVESTIGATION_TEMPLATE_VERSION, supportedVersions: INVESTIGATION_TEMPLATE_SUPPORTED_VERSIONS,
282
+ acceptsUnversionedLegacy: false, futureVersionBehavior: 'preserve_without_write', migration: 'exact_current_only',
283
+ writeSemantics: 'normalized_rewrite', byteBudget: MAX_INVESTIGATION_TEMPLATE_STORE_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
284
+ 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.',
285
+ });
286
+ export const BULK_REVIEW_BROWSER_COMPATIBILITY = defineSchemaCompatibility({
287
+ id: 'browser.bulk-review', kind: 'browser_store', schema: null, currentVersion: BULK_REVIEW_SCHEMA_VERSION,
288
+ supportedVersions: BULK_REVIEW_BROWSER_SUPPORTED_VERSIONS, acceptsUnversionedLegacy: false,
289
+ futureVersionBehavior: 'preserve_without_write', migration: 'exact_current_only', writeSemantics: 'normalized_rewrite',
290
+ byteBudget: MAX_BULK_REVIEW_STORE_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
291
+ note: 'Bounded saved Bulk filter views and per-domain review states only; scan results, contacts, notes, and case disposition remain separate.',
292
+ });
293
+ export const BRAND_PROFILE_EXPORT_COMPATIBILITY = defineSchemaCompatibility({
294
+ id: 'export.brand-profiles', kind: 'export', schema: BRAND_PROFILE_SCHEMA, currentVersion: BRAND_PROFILE_SCHEMA_VERSION,
295
+ supportedVersions: SUPPORTED_BRAND_PROFILE_SCHEMA_VERSIONS, acceptsUnversionedLegacy: false,
296
+ futureVersionBehavior: 'reject', migration: 'normalize_to_current', writeSemantics: 'non_destructive_merge',
297
+ byteBudget: MAX_PROFILE_IMPORT_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
298
+ note: 'Supported exports merge non-destructively by bounded normalised profile identity.',
299
+ });
300
+ export const CAMPAIGN_EXPORT_COMPATIBILITY = defineSchemaCompatibility({
301
+ id: 'export.campaigns', kind: 'export', schema: CAMPAIGN_SCHEMA, currentVersion: CAMPAIGN_SCHEMA_VERSION,
302
+ supportedVersions: CAMPAIGN_EXPORT_SUPPORTED_VERSIONS, acceptsUnversionedLegacy: false,
303
+ futureVersionBehavior: 'reject', migration: 'exact_current_only', writeSemantics: 'non_destructive_merge',
304
+ byteBudget: MAX_CAMPAIGN_IMPORT_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
305
+ note: 'The unchanged public version 1 campaign export merges non-destructively by bounded campaign identity.',
306
+ });
307
+ export const WATCHLIST_EXPORT_COMPATIBILITY = defineSchemaCompatibility({
308
+ id: 'export.watchlists', kind: 'export', schema: WATCHLIST_SCHEMA, currentVersion: WATCHLIST_SCHEMA_VERSION,
309
+ supportedVersions: WATCHLIST_EXPORT_SUPPORTED_VERSIONS, acceptsUnversionedLegacy: false,
310
+ futureVersionBehavior: 'reject', migration: 'exact_current_only', writeSemantics: 'non_destructive_merge',
311
+ byteBudget: MAX_WATCHLIST_IMPORT_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
312
+ note: 'Non-destructive collection merge with current schema required.',
313
+ });
314
+ export const SHORTLIST_EXPORT_COMPATIBILITY = defineSchemaCompatibility({
315
+ id: 'export.shortlist', kind: 'export', schema: SHORTLIST_SCHEMA, currentVersion: SHORTLIST_SCHEMA_VERSION,
316
+ supportedVersions: SUPPORTED_SHORTLIST_SCHEMA_VERSIONS, acceptsUnversionedLegacy: false,
317
+ futureVersionBehavior: 'reject', migration: 'exact_current_only', writeSemantics: 'non_destructive_merge',
318
+ byteBudget: MAX_SHORTLIST_IMPORT_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
319
+ note: 'The unchanged public schema 3 shortlist export merges bounded domain records non-destructively.',
320
+ });
321
+ export const DETECTION_RULE_EXPORT_COMPATIBILITY = defineSchemaCompatibility({
322
+ id: 'export.detection-rules', kind: 'export', schema: DETECTION_RULE_SCHEMA, currentVersion: DETECTION_RULE_SCHEMA_VERSION,
323
+ supportedVersions: DETECTION_RULE_EXPORT_SUPPORTED_VERSIONS, acceptsUnversionedLegacy: false,
324
+ futureVersionBehavior: 'reject', migration: 'exact_current_only', writeSemantics: 'non_destructive_merge',
325
+ byteBudget: MAX_RULE_IMPORT_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
326
+ note: 'Non-destructive rule merge; imported conditions remain allowlisted and non-executable.',
327
+ });
328
+ export const RELATIONSHIP_OBSERVATION_EXPORT_COMPATIBILITY = defineSchemaCompatibility({
329
+ id: 'export.relationship-observations', kind: 'export', schema: RELATIONSHIP_OBSERVATION_SCHEMA,
330
+ currentVersion: RELATIONSHIP_OBSERVATION_SCHEMA_VERSION, supportedVersions: RELATIONSHIP_OBSERVATION_EXPORT_SUPPORTED_VERSIONS,
331
+ acceptsUnversionedLegacy: false, futureVersionBehavior: 'reject', migration: 'exact_current_only',
332
+ writeSemantics: 'non_destructive_merge', byteBudget: MAX_RELATIONSHIP_OBSERVATION_STORE_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
333
+ note: 'Workspace-archive section for bounded analyst-selected derived pivots; raw scan and lookup responses are excluded.',
334
+ });
335
+ export const WEBSITE_SNAPSHOT_EXPORT_COMPATIBILITY = defineSchemaCompatibility({
336
+ id: 'export.website-snapshots', kind: 'export', schema: WEBSITE_SNAPSHOT_SCHEMA,
337
+ currentVersion: WEBSITE_SNAPSHOT_SCHEMA_VERSION, supportedVersions: SUPPORTED_WEBSITE_SNAPSHOT_SCHEMA_VERSIONS,
338
+ acceptsUnversionedLegacy: false, futureVersionBehavior: 'reject', migration: 'exact_current_only',
339
+ writeSemantics: 'non_destructive_merge', byteBudget: MAX_WEBSITE_SNAPSHOT_IMPORT_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
340
+ note: 'The unchanged public schema 4 archive section merges non-destructively without retaining fetched bodies.',
341
+ });
342
+ export const BULK_SESSION_EXPORT_COMPATIBILITY = defineSchemaCompatibility({
343
+ id: 'export.bulk-sessions', kind: 'export', schema: BULK_SESSION_SCHEMA, currentVersion: BULK_SESSION_SCHEMA_VERSION,
344
+ supportedVersions: SUPPORTED_BULK_SESSION_SCHEMA_VERSIONS, acceptsUnversionedLegacy: false,
345
+ futureVersionBehavior: 'reject', migration: 'exact_current_only', writeSemantics: 'non_destructive_merge',
346
+ byteBudget: MAX_BULK_SESSION_STORE_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
347
+ note: 'The unchanged public schema 4 portable sessions retain explicit source states, comparison limitations, and bounded profile-context provenance.',
348
+ });
349
+ export const INVESTIGATION_TEMPLATE_EXPORT_COMPATIBILITY = defineSchemaCompatibility({
350
+ id: 'export.investigation-templates', kind: 'export', schema: INVESTIGATION_TEMPLATE_SCHEMA,
351
+ currentVersion: INVESTIGATION_TEMPLATE_VERSION, supportedVersions: INVESTIGATION_TEMPLATE_SUPPORTED_VERSIONS,
352
+ acceptsUnversionedLegacy: false, futureVersionBehavior: 'reject', migration: 'exact_current_only',
353
+ writeSemantics: 'non_destructive_merge', byteBudget: MAX_INVESTIGATION_TEMPLATE_IMPORT_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
354
+ note: 'The unchanged public version 2 writer remains the exact bounded analyst-authored guide baseline with allowlisted stage identities and request gates.',
355
+ });
356
+ export const BULK_REVIEW_EXPORT_COMPATIBILITY = defineSchemaCompatibility({
357
+ id: 'export.bulk-review', kind: 'export', schema: BULK_REVIEW_SCHEMA, currentVersion: BULK_REVIEW_SCHEMA_VERSION,
358
+ supportedVersions: BULK_REVIEW_EXPORT_SUPPORTED_VERSIONS, acceptsUnversionedLegacy: false,
359
+ futureVersionBehavior: 'reject', migration: 'exact_current_only', writeSemantics: 'non_destructive_merge',
360
+ byteBudget: MAX_BULK_REVIEW_STORE_BYTES, owner: WORKSPACE_CONTRACT_OWNER,
361
+ note: 'Workspace-archive section for saved Bulk views and explicit review states; network collection and case disposition are never changed by import.',
362
+ });
363
+ export const WORKSPACE_PORTABILITY_COMPATIBILITY = Object.freeze([
364
+ BRAND_PROFILE_BROWSER_COMPATIBILITY, CAMPAIGN_BROWSER_COMPATIBILITY, WATCHLIST_BROWSER_COMPATIBILITY,
365
+ SHORTLIST_BROWSER_COMPATIBILITY, CT_HISTORY_BROWSER_COMPATIBILITY, DETECTION_RULE_BROWSER_COMPATIBILITY,
366
+ RELATIONSHIP_OBSERVATION_BROWSER_COMPATIBILITY, WEBSITE_SNAPSHOT_BROWSER_COMPATIBILITY,
367
+ BULK_SESSION_BROWSER_COMPATIBILITY, INVESTIGATION_TEMPLATE_BROWSER_COMPATIBILITY, BULK_REVIEW_BROWSER_COMPATIBILITY,
368
+ BRAND_PROFILE_EXPORT_COMPATIBILITY, CAMPAIGN_EXPORT_COMPATIBILITY, WATCHLIST_EXPORT_COMPATIBILITY,
369
+ SHORTLIST_EXPORT_COMPATIBILITY, DETECTION_RULE_EXPORT_COMPATIBILITY, RELATIONSHIP_OBSERVATION_EXPORT_COMPATIBILITY,
370
+ WEBSITE_SNAPSHOT_EXPORT_COMPATIBILITY, BULK_SESSION_EXPORT_COMPATIBILITY,
371
+ INVESTIGATION_TEMPLATE_EXPORT_COMPATIBILITY, BULK_REVIEW_EXPORT_COMPATIBILITY,
372
+ ]);
373
+ function archiveSectionReference(sectionId, descriptor) {
374
+ if (!descriptor.schema)
375
+ throw new TypeError(`Workspace archive section ${sectionId} must name a portable schema.`);
376
+ return Object.freeze({
377
+ sectionId,
378
+ schema: descriptor.schema,
379
+ version: descriptor.currentVersion,
380
+ supportedVersions: descriptor.supportedVersions,
381
+ });
382
+ }
383
+ export const WORKSPACE_PORTABILITY_ARCHIVE_SECTION_REFERENCES = Object.freeze([
384
+ archiveSectionReference(CAMPAIGNS_SECTION_ID, CAMPAIGN_EXPORT_COMPATIBILITY),
385
+ archiveSectionReference(BRAND_PROFILES_SECTION_ID, BRAND_PROFILE_EXPORT_COMPATIBILITY),
386
+ archiveSectionReference(WATCHLISTS_SECTION_ID, WATCHLIST_EXPORT_COMPATIBILITY),
387
+ archiveSectionReference(SHORTLIST_SECTION_ID, SHORTLIST_EXPORT_COMPATIBILITY),
388
+ archiveSectionReference(DETECTION_RULES_SECTION_ID, DETECTION_RULE_EXPORT_COMPATIBILITY),
389
+ archiveSectionReference(RELATIONSHIP_OBSERVATIONS_SECTION_ID, RELATIONSHIP_OBSERVATION_EXPORT_COMPATIBILITY),
390
+ archiveSectionReference(BULK_SESSIONS_SECTION_ID, BULK_SESSION_EXPORT_COMPATIBILITY),
391
+ archiveSectionReference(WEBSITE_SNAPSHOTS_SECTION_ID, WEBSITE_SNAPSHOT_EXPORT_COMPATIBILITY),
392
+ archiveSectionReference(INVESTIGATION_TEMPLATES_SECTION_ID, INVESTIGATION_TEMPLATE_EXPORT_COMPATIBILITY),
393
+ archiveSectionReference(BULK_REVIEW_SECTION_ID, BULK_REVIEW_EXPORT_COMPATIBILITY),
394
+ ]);
395
+ export function serialiseWorkspacePortableJson(value) {
396
+ return JSON.stringify(value, null, 2);
397
+ }
398
+ export function serialiseWorkspacePortableJsonLine(value) {
399
+ return `${serialiseWorkspacePortableJson(value)}\n`;
400
+ }
401
+ const WORKSPACE_LIFECYCLE_DEFINITIONS = Object.freeze([
402
+ {
403
+ slug: 'brand',
404
+ browserDescriptor: BRAND_PROFILE_BROWSER_COMPATIBILITY,
405
+ browserSchema: BRAND_PROFILE_BROWSER_STORE_LIFECYCLE_SCHEMA,
406
+ collectionKey: 'profiles',
407
+ module: 'packages/workspace/brand-profile-model.mts',
408
+ normalizer: 'normalizeBrandProfileStore',
409
+ serializer: 'serializeBrandProfileStore',
410
+ itemBounds: [
411
+ { id: 'profiles', path: 'profiles', maximum: MAX_PROFILES, handling: 'truncate' },
412
+ { id: 'profile-values', path: 'profiles[].values', maximum: MAX_PROFILE_VALUES, handling: 'truncate' },
413
+ ],
414
+ portable: {
415
+ descriptor: BRAND_PROFILE_EXPORT_COMPATIBILITY,
416
+ schema: BRAND_PROFILE_SCHEMA,
417
+ builder: 'buildBrandProfileExport',
418
+ merger: 'mergeBrandProfiles',
419
+ serializerHookId: 'workspace.portable.json',
420
+ indentSpaces: 2,
421
+ terminalLf: false,
422
+ },
423
+ },
424
+ {
425
+ slug: 'campaign',
426
+ browserDescriptor: CAMPAIGN_BROWSER_COMPATIBILITY,
427
+ browserSchema: CAMPAIGN_BROWSER_STORE_LIFECYCLE_SCHEMA,
428
+ collectionKey: 'campaigns',
429
+ module: 'packages/workspace/campaign-model.mts',
430
+ normalizer: 'normalizeCampaignStore',
431
+ serializer: 'serializeCampaignStore',
432
+ itemBounds: [
433
+ { id: 'campaigns', path: 'campaigns', maximum: MAX_CAMPAIGNS, handling: 'truncate' },
434
+ { id: 'domains', path: 'campaigns[].domains', maximum: MAX_CAMPAIGN_DOMAINS, handling: 'truncate' },
435
+ ],
436
+ portable: {
437
+ descriptor: CAMPAIGN_EXPORT_COMPATIBILITY,
438
+ schema: CAMPAIGN_SCHEMA,
439
+ builder: 'buildCampaignExport',
440
+ merger: 'mergeCampaigns',
441
+ serializerHookId: 'workspace.portable.json',
442
+ indentSpaces: 2,
443
+ terminalLf: false,
444
+ },
445
+ },
446
+ {
447
+ slug: 'watchlist',
448
+ browserDescriptor: WATCHLIST_BROWSER_COMPATIBILITY,
449
+ browserSchema: WATCHLIST_BROWSER_STORE_LIFECYCLE_SCHEMA,
450
+ collectionKey: 'watchlists',
451
+ module: 'packages/workspace/watchlist-store.mts',
452
+ normalizer: 'normalizeWatchlistStore',
453
+ serializer: 'serializeWatchlistStore',
454
+ itemBounds: [
455
+ { id: 'watchlists', path: 'watchlists', maximum: MAX_WATCHLISTS, handling: 'truncate' },
456
+ { id: 'domains', path: 'watchlists[].results', maximum: MAX_WATCHLIST_DOMAINS, handling: 'drop_value' },
457
+ { id: 'history', path: 'watchlists[].history', maximum: MAX_WATCHLIST_HISTORY_EVENTS, handling: 'truncate' },
458
+ ],
459
+ portable: {
460
+ descriptor: WATCHLIST_EXPORT_COMPATIBILITY,
461
+ schema: WATCHLIST_SCHEMA,
462
+ builder: 'buildWatchlistExport',
463
+ merger: 'mergeWatchlistStores',
464
+ serializerHookId: 'workspace.portable.json',
465
+ indentSpaces: 2,
466
+ terminalLf: false,
467
+ },
468
+ },
469
+ {
470
+ slug: 'shortlist',
471
+ browserDescriptor: SHORTLIST_BROWSER_COMPATIBILITY,
472
+ browserSchema: SHORTLIST_BROWSER_STORE_LIFECYCLE_SCHEMA,
473
+ collectionKey: 'entries',
474
+ module: 'packages/workspace/shortlist-model.mts',
475
+ normalizer: 'normalizeShortlistStore',
476
+ serializer: 'serializeShortlistStore',
477
+ itemBounds: [
478
+ { id: 'entries', path: 'entries', maximum: MAX_SHORTLIST_ENTRIES, handling: 'truncate' },
479
+ { id: 'factors', path: 'entries[].riskFactors', maximum: MAX_SHORTLIST_FACTORS, handling: 'truncate' },
480
+ ],
481
+ portable: {
482
+ descriptor: SHORTLIST_EXPORT_COMPATIBILITY,
483
+ schema: SHORTLIST_SCHEMA,
484
+ builder: 'buildShortlistExport',
485
+ merger: 'mergeShortlistStores',
486
+ serializerHookId: 'workspace.portable.json',
487
+ indentSpaces: 2,
488
+ terminalLf: false,
489
+ },
490
+ },
491
+ {
492
+ slug: 'ct',
493
+ browserDescriptor: CT_HISTORY_BROWSER_COMPATIBILITY,
494
+ browserSchema: CT_HISTORY_BROWSER_STORE_LIFECYCLE_SCHEMA,
495
+ collectionKey: 'entries',
496
+ module: 'packages/workspace/ct-history.mts',
497
+ normalizer: 'normalizeCtHistoryStore',
498
+ serializer: 'serializeCtHistoryStore',
499
+ itemBounds: [
500
+ { id: 'searches', path: 'entries', maximum: MAX_CT_HISTORY_SEARCHES, handling: 'truncate' },
501
+ { id: 'events', path: 'entries[].history', maximum: MAX_CT_HISTORY_EVENTS, handling: 'truncate' },
502
+ { id: 'domains', path: 'entries[].domains', maximum: MAX_CT_HISTORY_DOMAINS, handling: 'truncate' },
503
+ { id: 'ever-seen-domains', path: 'entries[].everSeenDomains', maximum: MAX_CT_HISTORY_EVER_SEEN_DOMAINS, handling: 'truncate' },
504
+ ],
505
+ portable: null,
506
+ },
507
+ {
508
+ slug: 'detection',
509
+ browserDescriptor: DETECTION_RULE_BROWSER_COMPATIBILITY,
510
+ browserSchema: DETECTION_RULE_BROWSER_STORE_LIFECYCLE_SCHEMA,
511
+ collectionKey: 'rules',
512
+ module: 'packages/workspace/detection-rule-model.mts',
513
+ normalizer: 'normalizeDetectionRuleStore',
514
+ serializer: 'serializeDetectionRuleStore',
515
+ itemBounds: [
516
+ { id: 'rules', path: 'rules', maximum: MAX_DETECTION_RULES, handling: 'truncate' },
517
+ { id: 'conditions', path: 'rules[].conditions', maximum: MAX_RULE_CONDITIONS, handling: 'truncate' },
518
+ ],
519
+ portable: {
520
+ descriptor: DETECTION_RULE_EXPORT_COMPATIBILITY,
521
+ schema: DETECTION_RULE_SCHEMA,
522
+ builder: 'buildDetectionRuleExport',
523
+ merger: 'mergeDetectionRules',
524
+ serializerHookId: 'workspace.portable.json',
525
+ indentSpaces: 2,
526
+ terminalLf: false,
527
+ },
528
+ },
529
+ {
530
+ slug: 'relationship',
531
+ browserDescriptor: RELATIONSHIP_OBSERVATION_BROWSER_COMPATIBILITY,
532
+ browserSchema: RELATIONSHIP_OBSERVATION_BROWSER_STORE_LIFECYCLE_SCHEMA,
533
+ collectionKey: 'observations',
534
+ module: 'packages/workspace/relationship-observation-model.mts',
535
+ normalizer: 'normalizeRelationshipObservationStore',
536
+ serializer: 'serializeRelationshipObservationStore',
537
+ itemBounds: [
538
+ { id: 'observations', path: 'observations', maximum: MAX_RELATIONSHIP_OBSERVATIONS, handling: 'truncate' },
539
+ { id: 'domains', path: 'observations[].domains', maximum: MAX_RELATIONSHIP_OBSERVATION_DOMAINS, handling: 'truncate' },
540
+ { id: 'limitations', path: 'observations[].limitations', maximum: MAX_RELATIONSHIP_OBSERVATION_LIMITATIONS, handling: 'truncate' },
541
+ ],
542
+ portable: {
543
+ descriptor: RELATIONSHIP_OBSERVATION_EXPORT_COMPATIBILITY,
544
+ schema: RELATIONSHIP_OBSERVATION_SCHEMA,
545
+ builder: 'buildRelationshipObservationExport',
546
+ merger: 'mergeRelationshipObservations',
547
+ serializerHookId: 'workspace.portable.archive-section',
548
+ indentSpaces: 0,
549
+ terminalLf: false,
550
+ },
551
+ },
552
+ {
553
+ slug: 'website',
554
+ browserDescriptor: WEBSITE_SNAPSHOT_BROWSER_COMPATIBILITY,
555
+ browserSchema: WEBSITE_SNAPSHOT_BROWSER_STORE_LIFECYCLE_SCHEMA,
556
+ collectionKey: 'snapshots',
557
+ module: 'packages/workspace/website-snapshot-model.mts',
558
+ normalizer: 'normalizeWebsiteSnapshotStore',
559
+ serializer: 'serializeWebsiteSnapshotStore',
560
+ itemBounds: [
561
+ { id: 'snapshots', path: 'snapshots', maximum: MAX_WEBSITE_SNAPSHOTS, handling: 'truncate' },
562
+ { id: 'snapshots-per-domain', path: 'snapshotsByDomain', maximum: MAX_WEBSITE_SNAPSHOTS_PER_DOMAIN, handling: 'truncate' },
563
+ ],
564
+ portable: {
565
+ descriptor: WEBSITE_SNAPSHOT_EXPORT_COMPATIBILITY,
566
+ schema: WEBSITE_SNAPSHOT_SCHEMA,
567
+ builder: 'buildWebsiteSnapshotExport',
568
+ merger: 'mergeWebsiteSnapshots',
569
+ serializerHookId: 'workspace.portable.json',
570
+ indentSpaces: 2,
571
+ terminalLf: false,
572
+ },
573
+ },
574
+ {
575
+ slug: 'bulk',
576
+ browserDescriptor: BULK_SESSION_BROWSER_COMPATIBILITY,
577
+ browserSchema: BULK_SESSION_BROWSER_STORE_LIFECYCLE_SCHEMA,
578
+ collectionKey: 'sessions',
579
+ module: 'packages/workspace/bulk-session-model.mts',
580
+ normalizer: 'normalizeBulkSessionStore',
581
+ serializer: 'serializeBulkSessionStore',
582
+ itemBounds: [
583
+ { id: 'sessions', path: 'sessions', maximum: MAX_BULK_SESSIONS, handling: 'truncate' },
584
+ { id: 'rows', path: 'sessions[].results', maximum: MAX_BULK_SESSION_ROWS, handling: 'truncate' },
585
+ ],
586
+ portable: {
587
+ descriptor: BULK_SESSION_EXPORT_COMPATIBILITY,
588
+ schema: BULK_SESSION_SCHEMA,
589
+ builder: 'buildBulkSessionExport',
590
+ merger: 'mergeBulkSessions',
591
+ serializerHookId: 'workspace.portable.json-line',
592
+ indentSpaces: 2,
593
+ terminalLf: true,
594
+ },
595
+ },
596
+ {
597
+ slug: 'template',
598
+ browserDescriptor: INVESTIGATION_TEMPLATE_BROWSER_COMPATIBILITY,
599
+ browserSchema: INVESTIGATION_TEMPLATE_BROWSER_STORE_LIFECYCLE_SCHEMA,
600
+ collectionKey: 'templates',
601
+ module: 'packages/workspace/investigation-template-model.mts',
602
+ normalizer: 'normalizeInvestigationTemplateStore',
603
+ serializer: 'serializeInvestigationTemplateStore',
604
+ itemBounds: [
605
+ { id: 'templates', path: 'templates', maximum: MAX_INVESTIGATION_TEMPLATES, handling: 'truncate' },
606
+ ],
607
+ portable: {
608
+ descriptor: INVESTIGATION_TEMPLATE_EXPORT_COMPATIBILITY,
609
+ schema: INVESTIGATION_TEMPLATE_SCHEMA,
610
+ builder: 'buildInvestigationTemplateExport',
611
+ merger: 'mergeInvestigationTemplates',
612
+ serializerHookId: 'workspace.portable.json-line',
613
+ indentSpaces: 2,
614
+ terminalLf: true,
615
+ },
616
+ },
617
+ {
618
+ slug: 'review',
619
+ browserDescriptor: BULK_REVIEW_BROWSER_COMPATIBILITY,
620
+ browserSchema: BULK_REVIEW_BROWSER_STORE_LIFECYCLE_SCHEMA,
621
+ collectionKey: 'presets',
622
+ module: 'packages/workspace/bulk-review-model.mts',
623
+ normalizer: 'normalizeBulkReviewStore',
624
+ serializer: 'serializeBulkReviewStore',
625
+ itemBounds: [
626
+ { id: 'presets', path: 'presets', maximum: MAX_BULK_REVIEW_PRESETS, handling: 'truncate' },
627
+ { id: 'rows', path: 'rows', maximum: MAX_BULK_REVIEW_ROWS, handling: 'truncate' },
628
+ ],
629
+ portable: {
630
+ descriptor: BULK_REVIEW_EXPORT_COMPATIBILITY,
631
+ schema: BULK_REVIEW_SCHEMA,
632
+ builder: 'buildBulkReviewExport',
633
+ merger: 'mergeBulkReviewStores',
634
+ serializerHookId: 'workspace.portable.archive-section',
635
+ indentSpaces: 0,
636
+ terminalLf: false,
637
+ },
638
+ },
639
+ ]);
640
+ const WORKSPACE_LIFECYCLE_FIXTURE_SOURCE = Object.freeze([
641
+ {
642
+ "id": "workspace.browser.brand.v6",
643
+ "path": "test/fixtures/workspace-lifecycle/browser-brand-v6.json",
644
+ "bytes": 37,
645
+ "sha256": "624d0b889df19b0f1abfd4026a4dd868003188ed4f4294f6e912589255847df9",
646
+ "contentDigestSha256": null,
647
+ "schema": "whoisleuth.browser.brand-profile-store",
648
+ "version": 6,
649
+ "role": "historical",
650
+ "expectation": "normalises_to_current_output",
651
+ "expectedOutputFixtureId": "workspace.browser.brand.v7",
652
+ "scope": "repository",
653
+ "shapeId": "workspace.browser.brand.shape"
654
+ },
655
+ {
656
+ "id": "workspace.browser.brand.v7",
657
+ "path": "test/fixtures/workspace-lifecycle/browser-brand-v7.json",
658
+ "bytes": 37,
659
+ "sha256": "c866b2aff3dad05236114e96d3997a1c6af571658b38faa6aeb3c224d125a46b",
660
+ "contentDigestSha256": null,
661
+ "schema": "whoisleuth.browser.brand-profile-store",
662
+ "version": 7,
663
+ "role": "current",
664
+ "expectation": "accepted_exact",
665
+ "expectedOutputFixtureId": null,
666
+ "scope": "repository",
667
+ "shapeId": "workspace.browser.brand.shape"
668
+ },
669
+ {
670
+ "id": "workspace.browser.bulk.v4",
671
+ "path": "test/fixtures/workspace-lifecycle/browser-bulk-v4.json",
672
+ "bytes": 77,
673
+ "sha256": "605442d181a35a1809edb2ff0b5be17920d52f04276cacaf1786dcc41d3c96ec",
674
+ "contentDigestSha256": null,
675
+ "schema": "whoisleuth.browser.bulk-session-store",
676
+ "version": 4,
677
+ "role": "current",
678
+ "expectation": "accepted_exact",
679
+ "expectedOutputFixtureId": null,
680
+ "scope": "repository",
681
+ "shapeId": "workspace.browser.bulk.shape"
682
+ },
683
+ {
684
+ "id": "workspace.browser.campaign.v1",
685
+ "path": "test/fixtures/workspace-lifecycle/browser-campaign-v1.json",
686
+ "bytes": 38,
687
+ "sha256": "c9f2a2f619c6237230e609a68c895c819472eba644ac4185b3eac3af256c7709",
688
+ "contentDigestSha256": null,
689
+ "schema": "whoisleuth.browser.campaign-store",
690
+ "version": 1,
691
+ "role": "current",
692
+ "expectation": "accepted_exact",
693
+ "expectedOutputFixtureId": null,
694
+ "scope": "repository",
695
+ "shapeId": "workspace.browser.campaign.shape"
696
+ },
697
+ {
698
+ "id": "workspace.browser.ct.v3",
699
+ "path": "test/fixtures/workspace-lifecycle/browser-ct-v3.json",
700
+ "bytes": 36,
701
+ "sha256": "48e7de97a1b64878f450f12238f7c9f5a41b85e07db3b86b0bc95ca5a5630685",
702
+ "contentDigestSha256": null,
703
+ "schema": "whoisleuth.browser.ct-history-store",
704
+ "version": 3,
705
+ "role": "current",
706
+ "expectation": "accepted_exact",
707
+ "expectedOutputFixtureId": null,
708
+ "scope": "repository",
709
+ "shapeId": "workspace.browser.ct.shape"
710
+ },
711
+ {
712
+ "id": "workspace.browser.detection.v1",
713
+ "path": "test/fixtures/workspace-lifecycle/browser-detection-v1.json",
714
+ "bytes": 34,
715
+ "sha256": "8772df49af2416bdf2f4881dd6f70452b3a0a8c31cf93f4aa1d7cb40ba0537a4",
716
+ "contentDigestSha256": null,
717
+ "schema": "whoisleuth.browser.detection-rule-store",
718
+ "version": 1,
719
+ "role": "current",
720
+ "expectation": "accepted_exact",
721
+ "expectedOutputFixtureId": null,
722
+ "scope": "repository",
723
+ "shapeId": "workspace.browser.detection.shape"
724
+ },
725
+ {
726
+ "id": "workspace.browser.relationship.v1",
727
+ "path": "test/fixtures/workspace-lifecycle/browser-relationship-v1.json",
728
+ "bytes": 41,
729
+ "sha256": "d26f28d5d0a654c2b2eab004f2e1a72c05845f433e8b47716f416b7271142668",
730
+ "contentDigestSha256": null,
731
+ "schema": "whoisleuth.browser.relationship-observation-store",
732
+ "version": 1,
733
+ "role": "current",
734
+ "expectation": "accepted_exact",
735
+ "expectedOutputFixtureId": null,
736
+ "scope": "repository",
737
+ "shapeId": "workspace.browser.relationship.shape"
738
+ },
739
+ {
740
+ "id": "workspace.browser.review.v1",
741
+ "path": "test/fixtures/workspace-lifecycle/browser-review-v1.json",
742
+ "bytes": 88,
743
+ "sha256": "9da86165eee183509ad3770b4f7bcf5296040c16ac108024e10b97c54c653609",
744
+ "contentDigestSha256": null,
745
+ "schema": "whoisleuth.browser.bulk-review-store",
746
+ "version": 1,
747
+ "role": "current",
748
+ "expectation": "accepted_exact",
749
+ "expectedOutputFixtureId": null,
750
+ "scope": "repository",
751
+ "shapeId": "workspace.browser.review.shape"
752
+ },
753
+ {
754
+ "id": "workspace.browser.shortlist.v3",
755
+ "path": "test/fixtures/workspace-lifecycle/browser-shortlist-v3.json",
756
+ "bytes": 72,
757
+ "sha256": "8ee7f00e2cdd899893549823df9699cf800a392e8a74cbe2907d2a8b2340ebca",
758
+ "contentDigestSha256": null,
759
+ "schema": "whoisleuth.browser.shortlist-store",
760
+ "version": 3,
761
+ "role": "current",
762
+ "expectation": "accepted_exact",
763
+ "expectedOutputFixtureId": null,
764
+ "scope": "repository",
765
+ "shapeId": "workspace.browser.shortlist.shape"
766
+ },
767
+ {
768
+ "id": "workspace.browser.template.v2",
769
+ "path": "test/fixtures/workspace-lifecycle/browser-template-v2.json",
770
+ "bytes": 88,
771
+ "sha256": "52c1a5a671bf3bd0d052fb7c75d746ed3527898b18ba6532fa9103bd240920aa",
772
+ "contentDigestSha256": null,
773
+ "schema": "whoisleuth.browser.investigation-template-store",
774
+ "version": 2,
775
+ "role": "current",
776
+ "expectation": "accepted_exact",
777
+ "expectedOutputFixtureId": null,
778
+ "scope": "repository",
779
+ "shapeId": "workspace.browser.template.shape"
780
+ },
781
+ {
782
+ "id": "workspace.browser.watchlist.v2",
783
+ "path": "test/fixtures/workspace-lifecycle/browser-watchlist-v2.json",
784
+ "bytes": 76,
785
+ "sha256": "230e7b6c83659a096e2ccd0da251558088f38b24e438c713bba1012fea788b2c",
786
+ "contentDigestSha256": null,
787
+ "schema": "whoisleuth.browser.watchlist-store",
788
+ "version": 2,
789
+ "role": "current",
790
+ "expectation": "accepted_exact",
791
+ "expectedOutputFixtureId": null,
792
+ "scope": "repository",
793
+ "shapeId": "workspace.browser.watchlist.shape"
794
+ },
795
+ {
796
+ "id": "workspace.browser.website.v4",
797
+ "path": "test/fixtures/workspace-lifecycle/browser-website-v4.json",
798
+ "bytes": 90,
799
+ "sha256": "b6f1aa0f80fa902c085d831c7bb3334b7c95db49906ffc8dd452173f423b85a4",
800
+ "contentDigestSha256": null,
801
+ "schema": "whoisleuth.browser.website-snapshot-store",
802
+ "version": 4,
803
+ "role": "current",
804
+ "expectation": "accepted_exact",
805
+ "expectedOutputFixtureId": null,
806
+ "scope": "repository",
807
+ "shapeId": "workspace.browser.website.shape"
808
+ },
809
+ {
810
+ "id": "workspace.portable.brand.v6",
811
+ "path": "test/fixtures/workspace-lifecycle/portable-brand-v6.json",
812
+ "bytes": 122,
813
+ "sha256": "19e8ab9772bcb07d989249576feab1d71a1a62d401a1a1a7cecf1c65cd7d5f80",
814
+ "contentDigestSha256": null,
815
+ "schema": "whoisleuth.brand-profiles",
816
+ "version": 6,
817
+ "role": "historical",
818
+ "expectation": "normalises_to_current_output",
819
+ "expectedOutputFixtureId": "workspace.portable.brand.v7",
820
+ "scope": "repository",
821
+ "shapeId": "workspace.portable.brand.shape"
822
+ },
823
+ {
824
+ "id": "workspace.portable.brand.v7",
825
+ "path": "test/fixtures/workspace-lifecycle/portable-brand-v7.json",
826
+ "bytes": 122,
827
+ "sha256": "49db0b0a2d5c00da9dc55fd5b03702d7412550e17329b917567acf6c4ec3ebb9",
828
+ "contentDigestSha256": null,
829
+ "schema": "whoisleuth.brand-profiles",
830
+ "version": 7,
831
+ "role": "current",
832
+ "expectation": "accepted_exact",
833
+ "expectedOutputFixtureId": null,
834
+ "scope": "repository",
835
+ "shapeId": "workspace.portable.brand.shape"
836
+ },
837
+ {
838
+ "id": "workspace.portable.bulk.v4",
839
+ "path": "test/fixtures/workspace-lifecycle/portable-bulk-v4.json",
840
+ "bytes": 338,
841
+ "sha256": "5b3adc2e58c2e47399cf8c0634225dd6cad18d6189d55520603b914a6e719785",
842
+ "contentDigestSha256": null,
843
+ "schema": "whoisleuth.bulk-sessions",
844
+ "version": 4,
845
+ "role": "current",
846
+ "expectation": "accepted_exact",
847
+ "expectedOutputFixtureId": null,
848
+ "scope": "repository",
849
+ "shapeId": "workspace.portable.bulk.shape"
850
+ },
851
+ {
852
+ "id": "workspace.portable.campaign.v1",
853
+ "path": "test/fixtures/workspace-lifecycle/portable-campaign-v1.json",
854
+ "bytes": 281,
855
+ "sha256": "2a0618a9f639cd8375eb46f9bdd5fc9fbb5d8f9ce347f1b93c2d760a91162ccd",
856
+ "contentDigestSha256": null,
857
+ "schema": "whoisleuth.campaigns",
858
+ "version": 1,
859
+ "role": "current",
860
+ "expectation": "accepted_exact",
861
+ "expectedOutputFixtureId": null,
862
+ "scope": "repository",
863
+ "shapeId": "workspace.portable.campaign.shape"
864
+ },
865
+ {
866
+ "id": "workspace.portable.detection.v1",
867
+ "path": "test/fixtures/workspace-lifecycle/portable-detection-v1.json",
868
+ "bytes": 293,
869
+ "sha256": "0c1306057547a5bf4a5c8c37f5bfaf9195a15a4971adc8e5c9085e8e87946508",
870
+ "contentDigestSha256": null,
871
+ "schema": "whoisleuth.detection-rules",
872
+ "version": 1,
873
+ "role": "current",
874
+ "expectation": "accepted_exact",
875
+ "expectedOutputFixtureId": null,
876
+ "scope": "repository",
877
+ "shapeId": "workspace.portable.detection.shape"
878
+ },
879
+ {
880
+ "id": "workspace.portable.relationship.v1",
881
+ "path": "test/fixtures/workspace-lifecycle/portable-relationship-v1.json",
882
+ "bytes": 363,
883
+ "sha256": "d16411297ec5be4eb0c87dbc36e1ed34e55ef3699084af2d5f18d76b042d398a",
884
+ "contentDigestSha256": null,
885
+ "schema": "whoisleuth.relationship-observations",
886
+ "version": 1,
887
+ "role": "current",
888
+ "expectation": "accepted_exact",
889
+ "expectedOutputFixtureId": null,
890
+ "scope": "repository",
891
+ "shapeId": "workspace.portable.relationship.shape"
892
+ },
893
+ {
894
+ "id": "workspace.portable.review.v1",
895
+ "path": "test/fixtures/workspace-lifecycle/portable-review-v1.json",
896
+ "bytes": 88,
897
+ "sha256": "9da86165eee183509ad3770b4f7bcf5296040c16ac108024e10b97c54c653609",
898
+ "contentDigestSha256": null,
899
+ "schema": "whoisleuth.bulk-review",
900
+ "version": 1,
901
+ "role": "current",
902
+ "expectation": "accepted_exact",
903
+ "expectedOutputFixtureId": null,
904
+ "scope": "repository",
905
+ "shapeId": "workspace.portable.review.shape"
906
+ },
907
+ {
908
+ "id": "workspace.portable.shortlist.v3",
909
+ "path": "test/fixtures/workspace-lifecycle/portable-shortlist-v3.json",
910
+ "bytes": 116,
911
+ "sha256": "282569f92d9dc0cd0ef3fdca985ba066e7003477398c2aea64149a036e00e627",
912
+ "contentDigestSha256": null,
913
+ "schema": "whoisleuth.shortlist",
914
+ "version": 3,
915
+ "role": "current",
916
+ "expectation": "accepted_exact",
917
+ "expectedOutputFixtureId": null,
918
+ "scope": "repository",
919
+ "shapeId": "workspace.portable.shortlist.shape"
920
+ },
921
+ {
922
+ "id": "workspace.portable.template.v2",
923
+ "path": "test/fixtures/workspace-lifecycle/portable-template-v2.json",
924
+ "bytes": 471,
925
+ "sha256": "827aaa27e86cc2e22247f5709fb0df8fa92226ae0691750d7bfdb9a85ca6e6b5",
926
+ "contentDigestSha256": null,
927
+ "schema": "whoisleuth.investigation-templates",
928
+ "version": 2,
929
+ "role": "current",
930
+ "expectation": "accepted_exact",
931
+ "expectedOutputFixtureId": null,
932
+ "scope": "repository",
933
+ "shapeId": "workspace.portable.template.shape"
934
+ },
935
+ {
936
+ "id": "workspace.portable.watchlist.v2",
937
+ "path": "test/fixtures/workspace-lifecycle/portable-watchlist-v2.json",
938
+ "bytes": 120,
939
+ "sha256": "1176a691294968af187a383839212a75dedbc0089b7a23bbb3d29a079f91e29b",
940
+ "contentDigestSha256": null,
941
+ "schema": "whoisleuth.watchlists",
942
+ "version": 2,
943
+ "role": "current",
944
+ "expectation": "accepted_exact",
945
+ "expectedOutputFixtureId": null,
946
+ "scope": "repository",
947
+ "shapeId": "workspace.portable.watchlist.shape"
948
+ },
949
+ {
950
+ "id": "workspace.portable.website.v4",
951
+ "path": "test/fixtures/workspace-lifecycle/portable-website-v4.json",
952
+ "bytes": 135,
953
+ "sha256": "08eef9ee031d5226464a0da5624c444c69eb48ca9dc2cb2fd2f1551018c4b11c",
954
+ "contentDigestSha256": null,
955
+ "schema": "whoisleuth.website-profile-snapshots",
956
+ "version": 4,
957
+ "role": "current",
958
+ "expectation": "accepted_exact",
959
+ "expectedOutputFixtureId": null,
960
+ "scope": "repository",
961
+ "shapeId": "workspace.portable.website.shape"
962
+ }
963
+ ]);
964
+ function workspaceByteBudget(descriptor) {
965
+ if (descriptor.byteBudget === null) {
966
+ throw new TypeError('Workspace portability contracts require an explicit byte budget.');
967
+ }
968
+ return descriptor.byteBudget;
969
+ }
970
+ function workspaceByteBoundId(descriptor) {
971
+ return 'workspace.bytes.' + workspaceByteBudget(descriptor);
972
+ }
973
+ function workspaceShapeId(plane, slug, lifecycle) {
974
+ return 'workspace.' + plane + '.' + slug + '.' + lifecycle + '.shape';
975
+ }
976
+ const WORKSPACE_LIFECYCLE_FIXTURES = Object.freeze(WORKSPACE_LIFECYCLE_FIXTURE_SOURCE.map((fixture) => ({
977
+ ...fixture,
978
+ shapeId: workspaceShapeId(fixture.id.includes('.browser.') ? 'browser' : 'portable', fixture.id.split('.')[2] ?? '', fixture.role === 'historical' ? 'historical' : 'current'),
979
+ })));
980
+ function workspaceFixtureIds(schema, version) {
981
+ return WORKSPACE_LIFECYCLE_FIXTURES
982
+ .filter((fixture) => fixture.schema === schema && fixture.version === version)
983
+ .map((fixture) => fixture.id);
984
+ }
985
+ function workspaceFutureVersionBehaviour(descriptor) {
986
+ if (descriptor.futureVersionBehavior === 'discard') {
987
+ throw new TypeError('Workspace portability contracts cannot discard unsupported future data.');
988
+ }
989
+ return descriptor.futureVersionBehavior;
990
+ }
991
+ function workspaceContracts(descriptor, schema) {
992
+ return descriptor.supportedVersions.map((version) => {
993
+ const current = version === descriptor.currentVersion;
994
+ return {
995
+ compatibilityId: descriptor.id,
996
+ schema,
997
+ version,
998
+ role: 'document',
999
+ lifecycle: current ? 'current' : 'legacy',
1000
+ readable: true,
1001
+ emitted: current,
1002
+ exactKeys: false,
1003
+ extensionPolicy: 'discard_bounded',
1004
+ futureVersionBehaviour: workspaceFutureVersionBehaviour(descriptor),
1005
+ migrationTarget: current || descriptor.migration !== 'normalize_to_current'
1006
+ ? null
1007
+ : { schema, version: descriptor.currentVersion },
1008
+ canonicalisation: null,
1009
+ byteBudget: workspaceByteBudget(descriptor),
1010
+ fixtureIds: workspaceFixtureIds(schema, version),
1011
+ };
1012
+ });
1013
+ }
1014
+ const WORKSPACE_LIFECYCLE_CONTRACTS = Object.freeze(WORKSPACE_LIFECYCLE_DEFINITIONS.flatMap((definition) => [
1015
+ ...workspaceContracts(definition.browserDescriptor, definition.browserSchema),
1016
+ ...(definition.portable
1017
+ ? workspaceContracts(definition.portable.descriptor, definition.portable.schema)
1018
+ : []),
1019
+ ]));
1020
+ function workspaceShape(definition, plane, lifecycle) {
1021
+ const portable = plane === 'portable' ? definition.portable : null;
1022
+ const descriptor = portable?.descriptor ?? definition.browserDescriptor;
1023
+ const schema = portable?.schema ?? definition.browserSchema;
1024
+ const rootKeys = plane === 'portable'
1025
+ ? ['schema', 'version', definition.collectionKey, 'exportedAt', 'generatedAt', 'limitations']
1026
+ : ['schema', 'version', definition.collectionKey, ...(definition.slug === 'review' ? ['rows'] : [])];
1027
+ return {
1028
+ id: workspaceShapeId(plane, definition.slug, lifecycle),
1029
+ schema,
1030
+ versions: descriptor.supportedVersions.filter((version) => (lifecycle === 'current'
1031
+ ? version === descriptor.currentVersion
1032
+ : version !== descriptor.currentVersion)),
1033
+ objects: [{
1034
+ path: '$',
1035
+ requiredKeys: descriptor.acceptsUnversionedLegacy
1036
+ ? []
1037
+ : ['version', definition.collectionKey],
1038
+ optionalKeys: rootKeys.filter((key) => (descriptor.acceptsUnversionedLegacy || (key !== 'version' && key !== definition.collectionKey))),
1039
+ alternativeRequiredKeys: [],
1040
+ unknownKeys: 'discard_bounded',
1041
+ }],
1042
+ fixedArrays: [],
1043
+ normalisation: 'project_known_fields',
1044
+ target: null,
1045
+ discriminator: null,
1046
+ };
1047
+ }
1048
+ const WORKSPACE_LIFECYCLE_SHAPES = Object.freeze(WORKSPACE_LIFECYCLE_DEFINITIONS.flatMap((definition) => [
1049
+ ...(definition.browserDescriptor.supportedVersions.length > 1
1050
+ ? [workspaceShape(definition, 'browser', 'historical')]
1051
+ : []),
1052
+ workspaceShape(definition, 'browser', 'current'),
1053
+ ...(definition.portable
1054
+ ? [
1055
+ ...(definition.portable.descriptor.supportedVersions.length > 1
1056
+ ? [workspaceShape(definition, 'portable', 'historical')]
1057
+ : []),
1058
+ workspaceShape(definition, 'portable', 'current'),
1059
+ ]
1060
+ : []),
1061
+ ]));
1062
+ const WORKSPACE_LIFECYCLE_HOOKS = Object.freeze([
1063
+ ...WORKSPACE_LIFECYCLE_DEFINITIONS.flatMap((definition) => [
1064
+ {
1065
+ id: 'workspace.' + definition.slug + '.normalise',
1066
+ role: 'normaliser',
1067
+ runtime: 'shared',
1068
+ module: definition.module,
1069
+ exportName: definition.normalizer,
1070
+ },
1071
+ {
1072
+ id: 'workspace.' + definition.slug + '.serialise',
1073
+ role: 'serialiser',
1074
+ runtime: 'shared',
1075
+ module: definition.module,
1076
+ exportName: definition.serializer,
1077
+ },
1078
+ ...(definition.portable ? [
1079
+ {
1080
+ id: 'workspace.' + definition.slug + '.build',
1081
+ role: 'builder',
1082
+ runtime: 'shared',
1083
+ module: definition.module,
1084
+ exportName: definition.portable.builder,
1085
+ },
1086
+ {
1087
+ id: 'workspace.' + definition.slug + '.merge',
1088
+ role: 'merger',
1089
+ runtime: 'shared',
1090
+ module: definition.module,
1091
+ exportName: definition.portable.merger,
1092
+ },
1093
+ ] : []),
1094
+ ]),
1095
+ {
1096
+ id: 'workspace.portable.json',
1097
+ role: 'serialiser',
1098
+ runtime: 'shared',
1099
+ module: WORKSPACE_CONTRACT_OWNER,
1100
+ exportName: 'serialiseWorkspacePortableJson',
1101
+ },
1102
+ {
1103
+ id: 'workspace.portable.json-line',
1104
+ role: 'serialiser',
1105
+ runtime: 'shared',
1106
+ module: WORKSPACE_CONTRACT_OWNER,
1107
+ exportName: 'serialiseWorkspacePortableJsonLine',
1108
+ },
1109
+ {
1110
+ id: 'workspace.portable.archive-section',
1111
+ role: 'serialiser',
1112
+ runtime: 'shared',
1113
+ module: 'packages/contracts/case-portability.mts',
1114
+ exportName: 'serialiseWorkspaceArchiveSection',
1115
+ },
1116
+ ]);
1117
+ const WORKSPACE_LIFECYCLE_BYTE_BUDGETS = Object.freeze([
1118
+ ...new Set(WORKSPACE_PORTABILITY_COMPATIBILITY.map(workspaceByteBudget)),
1119
+ ].sort((left, right) => left - right));
1120
+ const WORKSPACE_LIFECYCLE_BOUNDS = Object.freeze([
1121
+ ...WORKSPACE_LIFECYCLE_BYTE_BUDGETS.map((maximum) => ({
1122
+ id: 'workspace.bytes.' + maximum,
1123
+ bounds: [
1124
+ { id: 'raw-bytes', path: '$', phase: 'raw_intake', unit: 'bytes', minimum: 1, maximum, handling: 'reject' },
1125
+ { id: 'serialised-bytes', path: '$', phase: 'serialised', unit: 'bytes', minimum: 1, maximum, handling: 'reject' },
1126
+ ],
1127
+ })),
1128
+ ...WORKSPACE_LIFECYCLE_DEFINITIONS.map((definition) => ({
1129
+ id: 'workspace.' + definition.slug + '.domain-bounds',
1130
+ bounds: definition.itemBounds.map((bound) => ({
1131
+ ...bound,
1132
+ phase: bound.handling === 'truncate' ? 'normalised' : 'pre_accumulation',
1133
+ unit: bound.id === 'watchlists' ? 'entries' : 'items',
1134
+ minimum: 0,
1135
+ })),
1136
+ })),
1137
+ ]);
1138
+ const WORKSPACE_LIFECYCLE_PRIVACY = Object.freeze([
1139
+ {
1140
+ id: 'workspace.privacy.browser-state',
1141
+ classification: 'analyst_authored_sensitive',
1142
+ projection: 'browser_import',
1143
+ includedCategories: ['analyst-selected-identifiers', 'bounded-derived-observations', 'local-workspace-state'],
1144
+ excludedCategories: ['raw-upstream-payloads', 'expanded-contacts', 'credentials', 'cookies', 'session-data'],
1145
+ notePolicy: 'allowed_bounded',
1146
+ retention: 'browser_indexeddb',
1147
+ network: 'none',
1148
+ sharingReview: 'not_applicable',
1149
+ },
1150
+ {
1151
+ id: 'workspace.privacy.portable-input',
1152
+ classification: 'analyst_authored_sensitive',
1153
+ projection: 'browser_import',
1154
+ includedCategories: ['declared-collection-records', 'bounded-derived-observations', 'analyst-authored-state'],
1155
+ excludedCategories: ['raw-upstream-payloads', 'expanded-contacts', 'credentials', 'cookies', 'session-data'],
1156
+ notePolicy: 'allowed_bounded',
1157
+ retention: 'deliberate_local_file',
1158
+ network: 'none',
1159
+ sharingReview: 'required',
1160
+ },
1161
+ {
1162
+ id: 'workspace.privacy.portable-output',
1163
+ classification: 'analyst_authored_sensitive',
1164
+ projection: 'browser_export',
1165
+ includedCategories: ['privacy-minimised-collection-records', 'bounded-derived-observations', 'analyst-authored-state'],
1166
+ excludedCategories: ['raw-upstream-payloads', 'expanded-contacts', 'credentials', 'cookies', 'session-data'],
1167
+ notePolicy: 'allowed_bounded',
1168
+ retention: 'operator_controlled_output',
1169
+ network: 'none',
1170
+ sharingReview: 'required',
1171
+ },
1172
+ ]);
1173
+ const WORKSPACE_EXPIRY_ID = 'workspace.expiry.not-applicable';
1174
+ const WORKSPACE_LIFECYCLE_EXPIRY = Object.freeze([{
1175
+ id: WORKSPACE_EXPIRY_ID,
1176
+ field: null,
1177
+ anchor: null,
1178
+ handling: 'not_applicable',
1179
+ phase: 'not_applicable',
1180
+ maximumLifetimeDays: null,
1181
+ }]);
1182
+ function workspaceBrowserSerialisation(definition) {
1183
+ return {
1184
+ id: 'workspace.browser.' + definition.slug + '.json',
1185
+ schema: definition.browserSchema,
1186
+ versions: [definition.browserDescriptor.currentVersion],
1187
+ mediaType: 'application/json',
1188
+ encoding: 'utf-8',
1189
+ bom: false,
1190
+ indentSpaces: 0,
1191
+ terminalLf: false,
1192
+ propertyOrder: 'normalised_fixed',
1193
+ canonicalisation: null,
1194
+ integrity: 'none',
1195
+ serializerHookId: 'workspace.' + definition.slug + '.serialise',
1196
+ verifierHookIds: [],
1197
+ };
1198
+ }
1199
+ function workspacePortableSerialisation(definition) {
1200
+ if (!definition.portable)
1201
+ return [];
1202
+ return [{
1203
+ id: 'workspace.portable.' + definition.slug + '.json',
1204
+ schema: definition.portable.schema,
1205
+ versions: [definition.portable.descriptor.currentVersion],
1206
+ mediaType: 'application/json',
1207
+ encoding: 'utf-8',
1208
+ bom: false,
1209
+ indentSpaces: definition.portable.indentSpaces,
1210
+ terminalLf: definition.portable.terminalLf,
1211
+ propertyOrder: 'normalised_fixed',
1212
+ canonicalisation: null,
1213
+ integrity: 'none',
1214
+ serializerHookId: definition.portable.serializerHookId,
1215
+ verifierHookIds: [],
1216
+ }];
1217
+ }
1218
+ const WORKSPACE_LIFECYCLE_SERIALISATION = Object.freeze(WORKSPACE_LIFECYCLE_DEFINITIONS.flatMap((definition) => [
1219
+ workspaceBrowserSerialisation(definition),
1220
+ ...workspacePortableSerialisation(definition),
1221
+ ]));
1222
+ const WORKSPACE_EDGE_POLICY = {
1223
+ expiryPolicyId: WORKSPACE_EXPIRY_ID,
1224
+ requestMode: 'none',
1225
+ bindingState: 'declared_unenforced',
1226
+ policyState: 'current',
1227
+ };
1228
+ function workspaceBrowserConsumer(definition) {
1229
+ return {
1230
+ id: 'workspace.browser.' + definition.slug + '.read-write',
1231
+ plane: 'browser',
1232
+ operation: 'normalise-and-store',
1233
+ acceptedContracts: [{
1234
+ schema: definition.browserSchema,
1235
+ versions: definition.browserDescriptor.supportedVersions,
1236
+ mode: 'direct',
1237
+ discriminator: null,
1238
+ }],
1239
+ emittedContract: {
1240
+ schema: definition.browserSchema,
1241
+ version: definition.browserDescriptor.currentVersion,
1242
+ discriminator: null,
1243
+ },
1244
+ shapeIds: [
1245
+ ...(definition.browserDescriptor.supportedVersions.length > 1
1246
+ ? [workspaceShapeId('browser', definition.slug, 'historical')]
1247
+ : []),
1248
+ workspaceShapeId('browser', definition.slug, 'current'),
1249
+ ],
1250
+ boundProfileIds: [
1251
+ workspaceByteBoundId(definition.browserDescriptor),
1252
+ 'workspace.' + definition.slug + '.domain-bounds',
1253
+ ],
1254
+ hookIds: [
1255
+ 'workspace.' + definition.slug + '.normalise',
1256
+ 'workspace.' + definition.slug + '.serialise',
1257
+ ],
1258
+ serialisationProfileId: 'workspace.browser.' + definition.slug + '.json',
1259
+ privacyProfileId: 'workspace.privacy.browser-state',
1260
+ retentionEffect: 'browser_indexeddb',
1261
+ ...WORKSPACE_EDGE_POLICY,
1262
+ };
1263
+ }
1264
+ function workspacePortableConsumers(definition) {
1265
+ if (!definition.portable)
1266
+ return [];
1267
+ const portable = definition.portable;
1268
+ const importShapeIds = [
1269
+ ...(portable.descriptor.supportedVersions.length > 1
1270
+ ? [workspaceShapeId('portable', definition.slug, 'historical')]
1271
+ : []),
1272
+ workspaceShapeId('portable', definition.slug, 'current'),
1273
+ ];
1274
+ const boundProfileIds = [
1275
+ workspaceByteBoundId(portable.descriptor),
1276
+ 'workspace.' + definition.slug + '.domain-bounds',
1277
+ ];
1278
+ return [
1279
+ {
1280
+ id: 'workspace.portable.' + definition.slug + '.import',
1281
+ plane: 'shared',
1282
+ operation: 'normalise-and-non-destructive-merge',
1283
+ acceptedContracts: [{
1284
+ schema: portable.schema,
1285
+ versions: portable.descriptor.supportedVersions,
1286
+ mode: 'direct',
1287
+ discriminator: null,
1288
+ }],
1289
+ emittedContract: null,
1290
+ shapeIds: importShapeIds,
1291
+ boundProfileIds,
1292
+ hookIds: [
1293
+ 'workspace.' + definition.slug + '.normalise',
1294
+ 'workspace.' + definition.slug + '.merge',
1295
+ ],
1296
+ serialisationProfileId: null,
1297
+ privacyProfileId: 'workspace.privacy.portable-input',
1298
+ retentionEffect: 'deliberate_local_file',
1299
+ ...WORKSPACE_EDGE_POLICY,
1300
+ },
1301
+ {
1302
+ id: 'workspace.portable.' + definition.slug + '.output',
1303
+ plane: 'shared',
1304
+ operation: 'build-privacy-projection',
1305
+ acceptedContracts: [],
1306
+ emittedContract: {
1307
+ schema: portable.schema,
1308
+ version: portable.descriptor.currentVersion,
1309
+ discriminator: null,
1310
+ },
1311
+ shapeIds: [workspaceShapeId('portable', definition.slug, 'current')],
1312
+ boundProfileIds,
1313
+ hookIds: [
1314
+ 'workspace.' + definition.slug + '.build',
1315
+ portable.serializerHookId,
1316
+ ],
1317
+ serialisationProfileId: 'workspace.portable.' + definition.slug + '.json',
1318
+ privacyProfileId: 'workspace.privacy.portable-output',
1319
+ retentionEffect: 'operator_controlled_output',
1320
+ ...WORKSPACE_EDGE_POLICY,
1321
+ },
1322
+ ];
1323
+ }
1324
+ const WORKSPACE_LIFECYCLE_CONSUMERS = Object.freeze(WORKSPACE_LIFECYCLE_DEFINITIONS.flatMap((definition) => [
1325
+ workspaceBrowserConsumer(definition),
1326
+ ...workspacePortableConsumers(definition),
1327
+ ]));
1328
+ export const WORKSPACE_PORTABILITY_LIFECYCLE_FAMILY = defineSchemaLifecycleFamily({
1329
+ id: 'workspace-portability',
1330
+ owner: WORKSPACE_CONTRACT_OWNER,
1331
+ privacy: 'analyst_authored_sensitive',
1332
+ compatibility: WORKSPACE_PORTABILITY_COMPATIBILITY,
1333
+ contracts: WORKSPACE_LIFECYCLE_CONTRACTS,
1334
+ fixtures: WORKSPACE_LIFECYCLE_FIXTURES,
1335
+ metadata: {
1336
+ metadataVersion: 4,
1337
+ enforcement: 'declarative_only',
1338
+ shapes: WORKSPACE_LIFECYCLE_SHAPES,
1339
+ boundProfiles: WORKSPACE_LIFECYCLE_BOUNDS,
1340
+ hooks: WORKSPACE_LIFECYCLE_HOOKS,
1341
+ serialisationProfiles: WORKSPACE_LIFECYCLE_SERIALISATION,
1342
+ privacyProfiles: WORKSPACE_LIFECYCLE_PRIVACY,
1343
+ expiryProfiles: WORKSPACE_LIFECYCLE_EXPIRY,
1344
+ consumerEdges: WORKSPACE_LIFECYCLE_CONSUMERS,
1345
+ consumerRelationships: [],
1346
+ },
1347
+ });