@slicedearth/whoisleuth-cli 2.2.0 → 2.4.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 (282) hide show
  1. package/NOTICE +6 -0
  2. package/README.md +20 -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/arguments.mjs +454 -1748
  7. package/cli/artifact-validation/case-response.mjs +66 -35
  8. package/cli/artifact-validation/investigation-capsule.mjs +52 -31
  9. package/cli/artifact-validation/lookup-evidence.mjs +12 -6
  10. package/cli/artifact-validation/signed-review.mjs +112 -72
  11. package/cli/artifact-validation/structure-primitives.mjs +1 -105
  12. package/cli/artifact-verify.mjs +90 -24
  13. package/cli/assurance-command-runner.mjs +39 -4
  14. package/cli/bagit-folder.mjs +57 -0
  15. package/cli/bagit-review.mjs +23 -0
  16. package/cli/bounded-json.mjs +1 -1
  17. package/cli/bulk-checkpoint.mjs +34 -3
  18. package/cli/bulk-command-runner.mjs +2 -0
  19. package/cli/bulk-output.mjs +29 -3
  20. package/cli/case-command.mjs +195 -0
  21. package/cli/case-pack.mjs +66 -111
  22. package/cli/command-argument-grammar.mjs +215 -0
  23. package/cli/command-reference.mjs +342 -285
  24. package/cli/completion.mjs +303 -80
  25. package/cli/config-profile.mjs +7 -5
  26. package/cli/ct-event-intake.mjs +12 -12
  27. package/cli/discover.mjs +4 -3
  28. package/cli/discovery-observation-snapshot.mjs +59 -18
  29. package/cli/discovery-scan-command-runner.mjs +21 -0
  30. package/cli/discovery-scan.mjs +13 -4
  31. package/cli/discovery-workflow.mjs +1 -1
  32. package/cli/domain-control-monitor.mjs +50 -20
  33. package/cli/domain-control-observations.mjs +149 -102
  34. package/cli/errors.mjs +33 -1
  35. package/cli/evidence-command-runner.mjs +16 -5
  36. package/cli/evidence-signing.mjs +2 -2
  37. package/cli/external-observation-mapping.mjs +7 -8
  38. package/cli/formatters/terminal-lookup.mjs +18 -46
  39. package/cli/formatters/terminal-shared.mjs +5 -3
  40. package/cli/formatters/terminal-technology.mjs +114 -0
  41. package/cli/inline-command-families.mjs +2 -0
  42. package/cli/input.mjs +116 -0
  43. package/cli/interactive-launcher.mjs +2 -160
  44. package/cli/interchange-report.mjs +18 -8
  45. package/cli/investigation-artifacts.mjs +176 -0
  46. package/cli/investigation-checkpoint.mjs +10 -0
  47. package/cli/investigation-folder.mjs +114 -0
  48. package/cli/investigation-manifest.mjs +1 -112
  49. package/cli/investigation-package-review.mjs +103 -0
  50. package/cli/investigation-plan.mjs +32 -19
  51. package/cli/investigation-run.mjs +188 -33
  52. package/cli/local-document-checkpoint.mjs +170 -0
  53. package/cli/lookalike-calibration.mjs +7 -2
  54. package/cli/lookup-brief.mjs +4 -2
  55. package/cli/lookup-browser-view.mjs +1 -1
  56. package/cli/lookup-browser.mjs +5 -12
  57. package/cli/lookup-command-runner.mjs +17 -6
  58. package/cli/lookup-diff.mjs +37 -3
  59. package/cli/lookup-plan.mjs +11 -5
  60. package/cli/lookup-reconcile.mjs +10 -8
  61. package/cli/mail-header-review.mjs +467 -0
  62. package/cli/mail-review.mjs +66 -14
  63. package/cli/manual.mjs +1 -0
  64. package/cli/network-command-runner.mjs +2 -0
  65. package/cli/output-file.mjs +31 -5
  66. package/cli/registry-doctor.mjs +3 -1
  67. package/cli/registry-support.mjs +1 -1
  68. package/cli/retained-artifact-diff.mjs +2 -2
  69. package/cli/review-command-runner.mjs +70 -0
  70. package/cli/risk-calibration.mjs +19 -17
  71. package/cli/runner.mjs +62 -7
  72. package/cli/sharing-review.mjs +21 -34
  73. package/cli/signer-trust.mjs +104 -0
  74. package/cli/source-reliability.mjs +74 -83
  75. package/cli/terminal-input.mjs +168 -0
  76. package/cli/workflow-command-runner.mjs +68 -13
  77. package/docs/cli-reference.md +77 -24
  78. package/docs/cli.md +300 -25
  79. package/lib/abort.mjs +4 -1
  80. package/lib/availability.mjs +161 -112
  81. package/lib/bounded-file.mjs +3 -1
  82. package/lib/bounded-html-document.mjs +125 -0
  83. package/lib/bounded-json.mjs +51 -8
  84. package/lib/browser-library-profile.mjs +42 -51
  85. package/lib/browser-library-worker.mjs +112 -0
  86. package/lib/client-behavior-profile.mjs +3 -3
  87. package/lib/ct-search.mjs +11 -4
  88. package/lib/dns-convergence-review.mjs +1 -0
  89. package/lib/dns-delegation-health.mjs +24 -14
  90. package/lib/dns-inheritance-review.mjs +217 -0
  91. package/lib/dns-intelligence.mjs +73 -26
  92. package/lib/dns-mx.mjs +1 -1
  93. package/lib/dnssec-chain-validation.mjs +6 -0
  94. package/lib/dnssec-evidence-validation.mjs +11 -3
  95. package/lib/domain-change-packet.mjs +49 -16
  96. package/lib/domain-change-review.mjs +14 -8
  97. package/lib/domain-control-flight-recorder.mjs +221 -117
  98. package/lib/domain-control-manifest.mjs +129 -69
  99. package/lib/domain-posture-analysis.mjs +20 -7
  100. package/lib/domain-posture-parsers.mjs +201 -8
  101. package/lib/domain-posture.mjs +147 -29
  102. package/lib/evidence-export-privacy.mjs +561 -0
  103. package/lib/evidence-export.mjs +11 -543
  104. package/lib/evidence-report.mjs +5 -3
  105. package/lib/favicon.mjs +117 -41
  106. package/lib/generated/cisa-kev-catalog.mjs +12 -4
  107. package/lib/generated/registrar-standing-catalogue.mjs +7 -7
  108. package/lib/generated/retire-browser-catalog.mjs +2335 -940
  109. package/lib/hostname.mjs +2 -38
  110. package/lib/html-signals.mjs +111 -108
  111. package/lib/http-evidence-bounds.mjs +2 -1
  112. package/lib/http-intelligence.mjs +39 -26
  113. package/lib/local-geoip-evidence.mjs +19 -3
  114. package/lib/lookup-cache.mjs +34 -11
  115. package/lib/lookup-child-profile-contract.mjs +24 -15
  116. package/lib/lookup-diagnostics.mjs +24 -1
  117. package/lib/lookup-progress.mjs +30 -13
  118. package/lib/lookup-response-contract.mjs +1048 -0
  119. package/lib/lookup-response.mjs +8 -10
  120. package/lib/lookup-source-progress.mjs +40 -14
  121. package/lib/lookup.mjs +48 -27
  122. package/lib/nameserver-preflight-review.mjs +1 -0
  123. package/lib/observed-network-context.mjs +43 -27
  124. package/lib/outbound-request-bounds.mjs +4 -1
  125. package/lib/page-fingerprints.mjs +69 -133
  126. package/lib/page-role-profile.mjs +12 -8
  127. package/lib/perceptual-hash.mjs +43 -14
  128. package/lib/rdap-bootstrap.mjs +72 -22
  129. package/lib/rdap-capabilities.mjs +1 -1
  130. package/lib/rdap-client.mjs +15 -7
  131. package/lib/rdap-normalization.mjs +33 -18
  132. package/lib/rdap-search-workbench.mjs +1 -1
  133. package/lib/rdap-transport.mjs +47 -20
  134. package/lib/rdap-types.mjs +2 -1
  135. package/lib/rdap.mjs +18 -3
  136. package/lib/registrable-domain.mjs +21 -1
  137. package/lib/registrar-standing-contract.mjs +1 -0
  138. package/lib/registry-capabilities.mjs +5 -1
  139. package/lib/registry-capability-catalogue.mjs +2 -1
  140. package/lib/registry-dates.mjs +2 -190
  141. package/lib/registry-insights.mjs +85 -34
  142. package/lib/response-policy.mjs +86 -48
  143. package/lib/risk-calibration-summary.mjs +18 -6
  144. package/lib/rpki-evidence.mjs +8 -5
  145. package/lib/safe-fetch.mjs +54 -47
  146. package/lib/security-txt.mjs +42 -7
  147. package/lib/service-binding-dns.mjs +8 -0
  148. package/lib/smtp-transport-review.mjs +27 -15
  149. package/lib/sslbl-certificates.generated.mjs +136 -135
  150. package/lib/static-html-analysis.mjs +178 -151
  151. package/lib/structured-data-identity.mjs +1 -0
  152. package/lib/technology-markup.mjs +100 -0
  153. package/lib/threat-intelligence-manifests.mjs +11 -139
  154. package/lib/threat-intelligence-runtime.mjs +24 -52
  155. package/lib/threat-intelligence-targets.mjs +6 -34
  156. package/lib/threat-intelligence-types.mjs +39 -0
  157. package/lib/typosquat-generator.mjs +19 -15
  158. package/lib/urlhaus-intelligence.mjs +8 -3
  159. package/lib/urlscan-intelligence.mjs +1 -1
  160. package/lib/website-security-posture.mjs +30 -6
  161. package/lib/website-technology.mjs +153 -45
  162. package/lib/whois-authority.mjs +14 -2
  163. package/lib/whois-chain.mjs +9 -2
  164. package/lib/whois-common-formats.mjs +5 -5
  165. package/lib/whois-contracts.mjs +2 -1
  166. package/lib/whois-dialects.mjs +2 -2
  167. package/lib/whois-parser-support.mjs +8 -1
  168. package/lib/whois-transport.mjs +23 -7
  169. package/lib/zone-intent-review.mjs +16 -6
  170. package/package.json +2 -1
  171. package/packages/cases/case-attachment-model.mjs +83 -0
  172. package/packages/cases/case-evidence-model.mjs +156 -62
  173. package/packages/cases/case-export-input.mjs +53 -0
  174. package/packages/cases/case-migration-model.mjs +127 -44
  175. package/packages/cases/case-model.mjs +1 -0
  176. package/packages/cases/case-recheck-model.mjs +91 -0
  177. package/packages/cases/case-record-contracts.mjs +5 -20
  178. package/packages/cases/case-record-core.mjs +12 -22
  179. package/packages/cases/case-record-decisions.mjs +72 -0
  180. package/packages/cases/case-record-model.mjs +1 -0
  181. package/packages/cases/case-record-operations.mjs +95 -31
  182. package/packages/cases/case-record-projection.mjs +221 -0
  183. package/packages/cases/case-report.mjs +70 -54
  184. package/packages/cases/case-response-actions.mjs +654 -0
  185. package/packages/cases/case-response-model.mjs +64 -1115
  186. package/packages/cases/case-response-outcomes.mjs +317 -0
  187. package/packages/cases/case-response-packet-render.mjs +153 -0
  188. package/packages/cases/case-response-packet-types.mjs +1 -0
  189. package/packages/cases/case-response-packet-vocabulary.mjs +48 -0
  190. package/packages/cases/case-response-packet.mjs +74 -572
  191. package/packages/cases/case-response-records.mjs +94 -0
  192. package/packages/cases/case-response-review-inputs.mjs +371 -0
  193. package/packages/cases/case-response-values.mjs +85 -0
  194. package/packages/cases/case-review-package.mjs +45 -0
  195. package/packages/cases/case-selection.mjs +46 -0
  196. package/packages/cases/case-storage-model.mjs +31 -2
  197. package/packages/cases/response-route-freshness.mjs +16 -0
  198. package/packages/comparison/capture-context.mjs +61 -0
  199. package/packages/comparison/comparison-ledger-bulk.mjs +13 -11
  200. package/packages/comparison/page-similarity.mjs +5 -2
  201. package/packages/comparison/relationship-evidence.mjs +85 -8
  202. package/packages/comparison/relationship-provenance.mjs +93 -0
  203. package/packages/contracts/analyst-interchange.mjs +216 -0
  204. package/packages/contracts/analyst-review-state-contract.mjs +11 -0
  205. package/packages/contracts/analyst-review-state.mjs +5 -4
  206. package/packages/contracts/case-portability.mjs +366 -83
  207. package/packages/contracts/case-views-contract.mjs +12 -0
  208. package/packages/contracts/case-views.mjs +26 -0
  209. package/packages/contracts/cli-command-semantics.mjs +94 -0
  210. package/packages/contracts/cli-lookup.mjs +0 -1
  211. package/packages/contracts/domain-control-flight-recorder.mjs +80 -42
  212. package/packages/contracts/domain-control-manifest.mjs +127 -73
  213. package/packages/contracts/domain-control-monitor.mjs +48 -45
  214. package/packages/contracts/domain-control-review.mjs +179 -121
  215. package/packages/contracts/domain-name.mjs +21 -0
  216. package/packages/contracts/external-observation-interchange.mjs +2 -1
  217. package/packages/contracts/extracted-domain-lifecycle.mjs +11 -32
  218. package/packages/contracts/http-url.mjs +2 -0
  219. package/packages/contracts/investigation-package-limits.mjs +24 -0
  220. package/packages/contracts/investigation-portability.mjs +56 -27
  221. package/packages/contracts/investigation-run.mjs +16 -0
  222. package/packages/contracts/ip-address.mjs +34 -0
  223. package/packages/contracts/lookup-evidence.mjs +35 -0
  224. package/packages/contracts/offline-comparison.mjs +1 -2
  225. package/packages/contracts/page-fingerprints.mjs +15 -0
  226. package/packages/contracts/risk-calibration.mjs +15 -11
  227. package/packages/contracts/schema-compatibility.mjs +1 -1
  228. package/packages/contracts/schema-lifecycle.mjs +83 -153
  229. package/packages/contracts/selected-file-limits.mjs +6 -0
  230. package/packages/contracts/vulnerability-identifiers.mjs +5 -0
  231. package/packages/contracts/web-capture.mjs +10 -0
  232. package/packages/contracts/workspace-portability.mjs +183 -211
  233. package/packages/evidence/artifact-integrity.mjs +8 -2
  234. package/packages/evidence/artifact-structure.mjs +105 -0
  235. package/packages/evidence/decision-fact.mjs +4 -1
  236. package/packages/evidence/domain-control-runtime.mjs +201 -33
  237. package/packages/evidence/domain-posture-context.mjs +79 -0
  238. package/packages/evidence/image-regions.mjs +56 -0
  239. package/packages/evidence/latest-observations.mjs +28 -0
  240. package/packages/evidence/lookup-target.mjs +104 -0
  241. package/packages/evidence/observation.mjs +16 -1
  242. package/packages/evidence/passphrase-encryption.mjs +44 -0
  243. package/packages/evidence/record-identity.mjs +72 -0
  244. package/packages/evidence/registry-dates.mjs +190 -0
  245. package/packages/evidence/retained-file.mjs +69 -0
  246. package/packages/evidence/review-age.mjs +14 -0
  247. package/packages/interchange/bagit.mjs +342 -0
  248. package/packages/interchange/bounded-zip-extraction.mjs +278 -0
  249. package/packages/interchange/crc32.mjs +17 -0
  250. package/packages/interchange/external-findings-import.mjs +92 -51
  251. package/packages/interchange/web-capture-import.mjs +382 -0
  252. package/packages/investigation/bulk-mail-exposure.mjs +2 -1
  253. package/packages/investigation/investigation-bagit.mjs +14 -0
  254. package/packages/investigation/investigation-capsule.mjs +33 -5
  255. package/packages/investigation/investigation-manifest.mjs +200 -0
  256. package/packages/investigation/investigation-package-crypto.mjs +98 -0
  257. package/packages/investigation/investigation-package.mjs +200 -0
  258. package/packages/investigation/lookup-asset-graph-inputs.mjs +38 -0
  259. package/packages/investigation/lookup-asset-graph.mjs +193 -165
  260. package/packages/investigation/lookup-claim-readiness.mjs +18 -0
  261. package/packages/investigation/lookup-investigation-brief.mjs +3 -3
  262. package/packages/investigation/lookup-task-guidance.mjs +58 -0
  263. package/packages/monitoring/analyst-review-state.mjs +38 -90
  264. package/packages/workspace/brand-profile-model.mjs +276 -22
  265. package/packages/workspace/bulk-columns.mjs +17 -0
  266. package/packages/workspace/bulk-review-model.mjs +23 -11
  267. package/packages/workspace/bulk-session-model.mjs +100 -24
  268. package/packages/workspace/bulk-sort.mjs +1 -0
  269. package/packages/workspace/case-views.mjs +102 -0
  270. package/packages/workspace/detection-rule-model.mjs +70 -22
  271. package/packages/workspace/hostile-input.mjs +29 -8
  272. package/packages/workspace/investigation-template-model.mjs +19 -4
  273. package/packages/workspace/page-baseline.mjs +18 -15
  274. package/packages/workspace/relationship-observation-model.mjs +41 -23
  275. package/packages/workspace/watchlist-history.mjs +2 -2
  276. package/packages/workspace/watchlist-store.mjs +8 -2
  277. package/packages/workspace/website-snapshot-model.mjs +164 -26
  278. package/packages/workspace/workspace-archive-crypto.mjs +41 -61
  279. package/packages/workspace/workspace-archive.mjs +87 -21
  280. package/third-party-notices.txt +29 -2
  281. package/cli/evidence-command-arguments.mjs +0 -156
  282. package/packages/investigation/bulk-review-export.mjs +0 -63
@@ -2,7 +2,15 @@ import { MAX_DOMAIN_NAME_LENGTH } from "./domain-name.mjs";
2
2
  import { defineSchemaCompatibility } from "./schema-compatibility.mjs";
3
3
  import { defineSchemaLifecycleFamily } from "./schema-lifecycle.mjs";
4
4
  export const CASE_CONTRACT_OWNER = 'packages/contracts/case-portability.mts';
5
- export const LATEST_PUBLIC_APPLICATION_VERSION = '2.1.0';
5
+ export const MAX_CASE_OBJECTIVE_LENGTH = 320;
6
+ export const LATEST_PUBLIC_APPLICATION_VERSION = '2.3.0';
7
+ // Published writer identities are independent of the next writer. Historical
8
+ // readers below use fixed release epochs, never a moving "latest" identity.
9
+ export const LATEST_PUBLIC_CASE_SCHEMA_VERSION = 15;
10
+ export const LATEST_PUBLIC_CASE_REPORT_SCHEMA_VERSION = 11;
11
+ export const LATEST_PUBLIC_CASE_RESPONSE_PACKET_VERSION = 9;
12
+ export const LATEST_PUBLIC_CASE_RESPONSE_REVIEW_INPUTS_VERSION = 3;
13
+ export const LATEST_PUBLIC_WORKSPACE_ARCHIVE_VERSION = 8;
6
14
  export const CASE_PORTABILITY_IDENTITY_CONSTANTS = Object.freeze([
7
15
  'LATEST_PUBLIC_APPLICATION_VERSION',
8
16
  'LATEST_PUBLIC_CASE_SCHEMA_VERSION',
@@ -10,9 +18,19 @@ export const CASE_PORTABILITY_IDENTITY_CONSTANTS = Object.freeze([
10
18
  'LATEST_PUBLIC_CASE_RESPONSE_PACKET_VERSION',
11
19
  'LATEST_PUBLIC_CASE_RESPONSE_REVIEW_INPUTS_VERSION',
12
20
  'LATEST_PUBLIC_WORKSPACE_ARCHIVE_VERSION',
21
+ 'PUBLISHED_V2_2_CASE_SCHEMA_VERSION',
22
+ 'PUBLISHED_V2_2_CASE_REPORT_SCHEMA_VERSION',
23
+ 'PUBLISHED_V2_2_CASE_RESPONSE_PACKET_VERSION',
24
+ 'PUBLISHED_V2_2_CASE_RESPONSE_REVIEW_INPUTS_VERSION',
25
+ 'PUBLISHED_V2_2_WORKSPACE_ARCHIVE_VERSION',
26
+ 'PUBLISHED_V2_3_CASE_SCHEMA_VERSION',
27
+ 'PUBLISHED_V2_3_CASE_REPORT_SCHEMA_VERSION',
28
+ 'PUBLISHED_V2_3_CASE_RESPONSE_PACKET_VERSION',
29
+ 'PUBLISHED_V2_3_CASE_RESPONSE_REVIEW_INPUTS_VERSION',
13
30
  'CASE_BROWSER_STORE_LIFECYCLE_SCHEMA',
14
31
  'CASE_EXPORT_LIFECYCLE_SCHEMA',
15
32
  'CASE_SCHEMA_VERSION',
33
+ 'INCIDENT_CASE_SCHEMA_VERSION',
16
34
  'CASE_BROWSER_SUPPORTED_VERSIONS',
17
35
  'CASE_IMPORT_VERSIONS',
18
36
  'CASE_REPORT_SCHEMA',
@@ -27,6 +45,8 @@ export const CASE_PORTABILITY_IDENTITY_CONSTANTS = Object.freeze([
27
45
  'SUPPORTED_CASE_RESPONSE_REVIEW_INPUTS_VERSIONS',
28
46
  'CLI_CASE_PACK_SCHEMA',
29
47
  'CLI_CASE_PACK_VERSION',
48
+ 'CLI_CASE_PACK_WRITER_FIXTURE_ID',
49
+ 'CLI_CASE_PACK_INPUT_CASE_VERSIONS',
30
50
  'SUPPORTED_CLI_CASE_PACK_VERSIONS',
31
51
  'CLI_CASE_PACK_CASE_REPORT_EPOCHS',
32
52
  'WORKSPACE_ARCHIVE_SCHEMA',
@@ -42,12 +62,15 @@ export const CASE_PORTABILITY_IDENTITY_CONSTANTS = Object.freeze([
42
62
  'ENCRYPTED_WORKSPACE_ARCHIVE_VERSION',
43
63
  ]);
44
64
  export const CASE_PORTABILITY_BOUND_CONSTANTS = Object.freeze([
65
+ 'MAX_CASE_OBJECTIVE_LENGTH',
45
66
  'MAX_CASES',
46
67
  'MAX_NOTES_PER_CASE',
47
68
  'MAX_NOTE_LENGTH',
48
69
  'MAX_TAGS_PER_CASE',
49
70
  'MAX_CASE_IMPORT_BYTES',
50
71
  'MAX_CASE_STORE_BYTES',
72
+ 'MAX_EDITABLE_CASE_INPUT_BYTES',
73
+ 'MAX_EDITABLE_CASE_OUTPUT_BYTES',
51
74
  'MAX_EVIDENCE_SNAPSHOTS_PER_CASE',
52
75
  'MAX_EVIDENCE_FACTORS',
53
76
  'MAX_EVIDENCE_NAMESERVERS',
@@ -118,6 +141,7 @@ export const CASE_DOMAIN_COMPATIBILITY_FACADES = Object.freeze([
118
141
  ['frontend/src/lib/analysis/case-response-model.ts', 'packages/cases/case-response-model.mts'],
119
142
  ['frontend/src/lib/analysis/case-response-packet.ts', 'packages/cases/case-response-packet.mts'],
120
143
  ['frontend/src/lib/analysis/case-storage-model.ts', 'packages/cases/case-storage-model.mts'],
144
+ ['frontend/src/lib/analysis/case-type-evidence-readiness.ts', 'packages/cases/case-type-evidence-readiness.mts'],
121
145
  ['frontend/src/lib/analysis/http-summary.ts', 'packages/cases/http-summary.mts'],
122
146
  ['frontend/src/lib/analysis/opaque-reference-id.ts', 'packages/cases/opaque-reference-id.mts'],
123
147
  ['frontend/src/lib/analysis/workspace-archive.ts', 'packages/workspace/workspace-archive.mts'],
@@ -131,44 +155,59 @@ export const CASE_BROWSER_STORE_LIFECYCLE_SCHEMA = 'whoisleuth.browser.case-stor
131
155
  export const CASE_EXPORT_LIFECYCLE_SCHEMA = 'whoisleuth.case-export';
132
156
  export const PUBLIC_CASE_SCHEMA_VERSION = 12;
133
157
  export const PUBLISHED_V2_CASE_SCHEMA_VERSION = 13;
134
- export const CASE_SCHEMA_VERSION = 14;
135
- export const LATEST_PUBLIC_CASE_SCHEMA_VERSION = 14;
158
+ export const PUBLISHED_V2_2_CASE_SCHEMA_VERSION = 14;
159
+ export const PUBLISHED_V2_3_CASE_SCHEMA_VERSION = 15;
160
+ export const INCIDENT_CASE_SCHEMA_VERSION = 16;
161
+ export const CASE_SCHEMA_VERSION = INCIDENT_CASE_SCHEMA_VERSION;
136
162
  export const CASE_BROWSER_SUPPORTED_VERSIONS = Object.freeze([
137
163
  PUBLIC_CASE_SCHEMA_VERSION,
138
164
  PUBLISHED_V2_CASE_SCHEMA_VERSION,
165
+ PUBLISHED_V2_2_CASE_SCHEMA_VERSION,
166
+ PUBLISHED_V2_3_CASE_SCHEMA_VERSION,
139
167
  CASE_SCHEMA_VERSION,
140
168
  ]);
141
169
  export const CASE_IMPORT_VERSIONS = CASE_BROWSER_SUPPORTED_VERSIONS;
142
170
  export const CASE_REPORT_SCHEMA = 'whoisleuth.case-report';
143
171
  export const PUBLIC_CASE_REPORT_SCHEMA_VERSION = 8;
144
172
  export const PUBLISHED_V2_CASE_REPORT_SCHEMA_VERSION = 9;
145
- export const CASE_REPORT_SCHEMA_VERSION = 10;
146
- export const LATEST_PUBLIC_CASE_REPORT_SCHEMA_VERSION = 10;
173
+ export const PUBLISHED_V2_2_CASE_REPORT_SCHEMA_VERSION = 10;
174
+ export const PUBLISHED_V2_3_CASE_REPORT_SCHEMA_VERSION = 11;
175
+ export const CASE_REPORT_SCHEMA_VERSION = 12;
147
176
  export const CASE_REPORT_OUTPUT_VERSIONS = Object.freeze([
148
177
  PUBLISHED_V2_CASE_REPORT_SCHEMA_VERSION,
178
+ PUBLISHED_V2_2_CASE_REPORT_SCHEMA_VERSION,
179
+ PUBLISHED_V2_3_CASE_REPORT_SCHEMA_VERSION,
149
180
  CASE_REPORT_SCHEMA_VERSION,
150
181
  ]);
151
182
  export const CASE_RESPONSE_PACKET_SCHEMA = 'whoisleuth.case-response-packet';
152
183
  export const PUBLIC_CASE_RESPONSE_PACKET_VERSION = 6;
153
184
  export const PUBLISHED_V2_CASE_RESPONSE_PACKET_VERSION = 7;
154
- export const CASE_RESPONSE_PACKET_VERSION = 8;
155
- export const LATEST_PUBLIC_CASE_RESPONSE_PACKET_VERSION = 8;
185
+ export const PUBLISHED_V2_2_CASE_RESPONSE_PACKET_VERSION = 8;
186
+ export const PUBLISHED_V2_3_CASE_RESPONSE_PACKET_VERSION = 9;
187
+ export const CASE_RESPONSE_PACKET_VERSION = 10;
156
188
  export const CASE_RESPONSE_PACKET_OUTPUT_VERSIONS = Object.freeze([
157
189
  PUBLIC_CASE_RESPONSE_PACKET_VERSION,
158
190
  PUBLISHED_V2_CASE_RESPONSE_PACKET_VERSION,
191
+ PUBLISHED_V2_2_CASE_RESPONSE_PACKET_VERSION,
192
+ PUBLISHED_V2_3_CASE_RESPONSE_PACKET_VERSION,
159
193
  CASE_RESPONSE_PACKET_VERSION,
160
194
  ]);
161
195
  export const SUPPORTED_CASE_RESPONSE_PACKET_VERSIONS = CASE_RESPONSE_PACKET_OUTPUT_VERSIONS;
162
196
  export const CASE_RESPONSE_REVIEW_INPUTS_SCHEMA = 'whoisleuth.case-response-review-inputs';
163
197
  export const PUBLISHED_V2_CASE_RESPONSE_REVIEW_INPUTS_VERSION = 1;
164
- export const CASE_RESPONSE_REVIEW_INPUTS_VERSION = 2;
165
- export const LATEST_PUBLIC_CASE_RESPONSE_REVIEW_INPUTS_VERSION = 2;
198
+ export const PUBLISHED_V2_2_CASE_RESPONSE_REVIEW_INPUTS_VERSION = 2;
199
+ export const PUBLISHED_V2_3_CASE_RESPONSE_REVIEW_INPUTS_VERSION = 3;
200
+ export const CASE_RESPONSE_REVIEW_INPUTS_VERSION = 4;
166
201
  export const SUPPORTED_CASE_RESPONSE_REVIEW_INPUTS_VERSIONS = Object.freeze([
167
202
  PUBLISHED_V2_CASE_RESPONSE_REVIEW_INPUTS_VERSION,
203
+ PUBLISHED_V2_2_CASE_RESPONSE_REVIEW_INPUTS_VERSION,
204
+ PUBLISHED_V2_3_CASE_RESPONSE_REVIEW_INPUTS_VERSION,
168
205
  CASE_RESPONSE_REVIEW_INPUTS_VERSION,
169
206
  ]);
170
207
  export const CLI_CASE_PACK_SCHEMA = 'whoisleuth.cli.case-pack';
171
208
  export const CLI_CASE_PACK_VERSION = 2;
209
+ export const CLI_CASE_PACK_WRITER_FIXTURE_ID = 'cli-case-pack-v2-case-v16-current';
210
+ export const CLI_CASE_PACK_INPUT_CASE_VERSIONS = Object.freeze(CASE_IMPORT_VERSIONS.filter((version) => version >= PUBLISHED_V2_3_CASE_SCHEMA_VERSION));
172
211
  export const SUPPORTED_CLI_CASE_PACK_VERSIONS = Object.freeze([CLI_CASE_PACK_VERSION]);
173
212
  export const CLI_CASE_PACK_ROOT_KEYS = Object.freeze(['version', 'exportedAt', 'cases', 'packet', 'integrity']);
174
213
  export const CLI_CASE_PACK_PACKET_KEYS = Object.freeze(['schema', 'version', 'audience', 'reviewed', 'reports', 'redactionManifest', 'limitations']);
@@ -184,6 +223,8 @@ export const CLI_CASE_PACK_LIMITATIONS = Object.freeze([
184
223
  export const CLI_CASE_PACK_CASE_REPORT_EPOCHS = Object.freeze([
185
224
  Object.freeze({ caseVersions: Object.freeze([PUBLIC_CASE_SCHEMA_VERSION]), reportVersions: Object.freeze([PUBLIC_CASE_REPORT_SCHEMA_VERSION]) }),
186
225
  Object.freeze({ caseVersions: Object.freeze([PUBLISHED_V2_CASE_SCHEMA_VERSION]), reportVersions: Object.freeze([PUBLISHED_V2_CASE_REPORT_SCHEMA_VERSION]) }),
226
+ Object.freeze({ caseVersions: Object.freeze([PUBLISHED_V2_2_CASE_SCHEMA_VERSION]), reportVersions: Object.freeze([PUBLISHED_V2_2_CASE_REPORT_SCHEMA_VERSION]) }),
227
+ Object.freeze({ caseVersions: Object.freeze([PUBLISHED_V2_3_CASE_SCHEMA_VERSION]), reportVersions: Object.freeze([PUBLISHED_V2_3_CASE_REPORT_SCHEMA_VERSION]) }),
187
228
  Object.freeze({ caseVersions: Object.freeze([CASE_SCHEMA_VERSION]), reportVersions: Object.freeze([CASE_REPORT_SCHEMA_VERSION]) }),
188
229
  ]);
189
230
  export function caseReportVersionMatchesCase(caseVersion, reportVersion) {
@@ -195,11 +236,13 @@ export function caseReportVersionMatchesCase(caseVersion, reportVersion) {
195
236
  export const WORKSPACE_ARCHIVE_SCHEMA = 'whoisleuth.workspace-archive';
196
237
  export const PUBLIC_WORKSPACE_ARCHIVE_VERSION = 5;
197
238
  export const PUBLISHED_V2_WORKSPACE_ARCHIVE_VERSION = 6;
198
- export const WORKSPACE_ARCHIVE_VERSION = 7;
199
- export const LATEST_PUBLIC_WORKSPACE_ARCHIVE_VERSION = 7;
239
+ export const PUBLISHED_V2_2_WORKSPACE_ARCHIVE_VERSION = 7;
240
+ export const WORKSPACE_ARCHIVE_VERSION = 9;
200
241
  export const SUPPORTED_WORKSPACE_ARCHIVE_VERSIONS = Object.freeze([
201
242
  PUBLIC_WORKSPACE_ARCHIVE_VERSION,
202
243
  PUBLISHED_V2_WORKSPACE_ARCHIVE_VERSION,
244
+ PUBLISHED_V2_2_WORKSPACE_ARCHIVE_VERSION,
245
+ LATEST_PUBLIC_WORKSPACE_ARCHIVE_VERSION,
203
246
  WORKSPACE_ARCHIVE_VERSION,
204
247
  ]);
205
248
  export function isSupportedWorkspaceArchiveVersion(value) {
@@ -210,9 +253,9 @@ export function isSupportedWorkspaceArchiveVersion(value) {
210
253
  export const WORKSPACE_ARCHIVE_SECTION_IDS = Object.freeze([
211
254
  'cases', 'campaigns', 'brandProfiles', 'watchlists', 'shortlist', 'detectionRules',
212
255
  'relationshipObservations', 'bulkSessions', 'websiteSnapshots', 'investigationTemplates',
213
- 'bulkReview', 'analystReviewState', 'settings',
256
+ 'bulkReview', 'analystReviewState', 'caseViews', 'settings',
214
257
  ]);
215
- export const PUBLIC_WORKSPACE_ARCHIVE_SECTION_IDS = Object.freeze(WORKSPACE_ARCHIVE_SECTION_IDS.filter((id) => id !== 'analystReviewState'));
258
+ export const PUBLIC_WORKSPACE_ARCHIVE_SECTION_IDS = Object.freeze(WORKSPACE_ARCHIVE_SECTION_IDS.filter((id) => id !== 'analystReviewState' && id !== 'caseViews'));
216
259
  export const WORKSPACE_ARCHIVE_CASE_SECTION = Object.freeze({
217
260
  id: 'cases',
218
261
  schema: null,
@@ -251,8 +294,12 @@ export const MAX_NOTE_LENGTH = 2000;
251
294
  export const MAX_TAGS_PER_CASE = 20;
252
295
  export const MAX_TAG_LENGTH = 40;
253
296
  export const MAX_DOMAIN_LENGTH = MAX_DOMAIN_NAME_LENGTH;
254
- export const MAX_CASE_IMPORT_BYTES = 2 * 1024 * 1024;
255
297
  export const MAX_CASE_STORE_BYTES = 4 * 1024 * 1024;
298
+ // Formatting and JSON escaping may enlarge an admitted canonical Case store.
299
+ // Editable files retain the store budget; the envelope adds bounded metadata.
300
+ export const MAX_CASE_IMPORT_BYTES = MAX_CASE_STORE_BYTES * 4;
301
+ export const MAX_EDITABLE_CASE_INPUT_BYTES = MAX_CASE_IMPORT_BYTES;
302
+ export const MAX_EDITABLE_CASE_OUTPUT_BYTES = MAX_CASE_STORE_BYTES + 256;
256
303
  export const MAX_EVIDENCE_SNAPSHOTS_PER_CASE = 25;
257
304
  export const MAX_EVIDENCE_FACTORS = 20;
258
305
  export const MAX_EVIDENCE_NAMESERVERS = 12;
@@ -301,9 +348,9 @@ export const RESPONSE_ROUTE_STALE_AFTER_DAYS = 30;
301
348
  export const MAX_RESPONSE_AUTHORISATION_CLOCK_SKEW_MS = 300_000;
302
349
  export const MAX_CASE_PACK_INPUT_BYTES = 4 * 1024 * 1024;
303
350
  export const MAX_CASE_PACK_CASES = 25;
304
- export const MAX_WORKSPACE_ARCHIVE_BYTES = 10 * 1024 * 1024;
351
+ export const MAX_WORKSPACE_ARCHIVE_BYTES = 32 * 1024 * 1024;
305
352
  export const MAX_WORKSPACE_ARCHIVE_SECTION_BYTES = 5 * 1024 * 1024;
306
- export const MAX_WORKSPACE_ARCHIVE_SECTIONS = 13;
353
+ export const MAX_WORKSPACE_ARCHIVE_SECTIONS = WORKSPACE_ARCHIVE_SECTION_IDS.length;
307
354
  export const WORKSPACE_ARCHIVE_PBKDF2_ITERATIONS = 600_000;
308
355
  export const MIN_WORKSPACE_ARCHIVE_PASSPHRASE_CHARACTERS = 12;
309
356
  export const MAX_WORKSPACE_ARCHIVE_PASSPHRASE_BYTES = 1024;
@@ -313,49 +360,49 @@ export const CASE_BROWSER_COMPATIBILITY = defineSchemaCompatibility({
313
360
  supportedVersions: CASE_BROWSER_SUPPORTED_VERSIONS, acceptsUnversionedLegacy: false,
314
361
  futureVersionBehavior: 'preserve_without_write', migration: 'normalize_to_current',
315
362
  writeSemantics: 'normalized_rewrite', byteBudget: MAX_CASE_STORE_BYTES, owner: CASE_CONTRACT_OWNER,
316
- note: 'Exact Case versions 12 and 13 migrate directly to current version 14; other retired stores are rejected and future stores remain untouched.',
363
+ note: 'Declared supported Case stores migrate to the current writer; retired stores are rejected and future stores remain untouched.',
317
364
  });
318
365
  export const CASE_EXPORT_COMPATIBILITY = defineSchemaCompatibility({
319
366
  id: 'export.cases', kind: 'export', schema: null, currentVersion: CASE_SCHEMA_VERSION,
320
367
  supportedVersions: CASE_IMPORT_VERSIONS, acceptsUnversionedLegacy: false,
321
368
  futureVersionBehavior: 'reject', migration: 'normalize_to_current',
322
369
  writeSemantics: 'non_destructive_merge', byteBudget: MAX_CASE_IMPORT_BYTES, owner: CASE_CONTRACT_OWNER,
323
- note: 'Exact Case versions 12, 13 and 14 merge non-destructively through one bounded reader; other versions fail without mutation.',
370
+ note: 'Declared supported Case exports merge non-destructively through one bounded reader; other versions fail without mutation.',
324
371
  });
325
372
  export const CASE_REPORT_COMPATIBILITY = defineSchemaCompatibility({
326
373
  id: 'export.case-report', kind: 'export', schema: CASE_REPORT_SCHEMA, currentVersion: CASE_REPORT_SCHEMA_VERSION,
327
374
  supportedVersions: CASE_REPORT_OUTPUT_VERSIONS, acceptsUnversionedLegacy: false,
328
375
  futureVersionBehavior: 'reject', migration: 'read_only', writeSemantics: 'read_only',
329
376
  byteBudget: null, owner: CASE_CONTRACT_OWNER,
330
- note: 'Published Case report version 9 and current version 10 are exact readable output contracts; retired and future report versions are unsupported.',
377
+ note: 'Supported Case reports are exact readable output contracts; retired and future report versions are unsupported.',
331
378
  });
332
379
  export const CASE_RESPONSE_PACKET_COMPATIBILITY = defineSchemaCompatibility({
333
380
  id: 'export.case-response-packet', kind: 'export', schema: CASE_RESPONSE_PACKET_SCHEMA,
334
381
  currentVersion: CASE_RESPONSE_PACKET_VERSION, supportedVersions: CASE_RESPONSE_PACKET_OUTPUT_VERSIONS,
335
382
  acceptsUnversionedLegacy: false, futureVersionBehavior: 'reject', migration: 'read_only',
336
383
  writeSemantics: 'read_only', byteBudget: null, owner: CASE_CONTRACT_OWNER,
337
- note: 'Exact packet versions 6, 7 and 8 are verified independently; other versions fail closed.',
384
+ note: 'Declared supported packet versions are verified independently; other versions fail closed.',
338
385
  });
339
386
  export const CASE_RESPONSE_REVIEW_INPUTS_COMPATIBILITY = defineSchemaCompatibility({
340
387
  id: 'derived.case-response-review-inputs', kind: 'derived', schema: CASE_RESPONSE_REVIEW_INPUTS_SCHEMA,
341
388
  currentVersion: CASE_RESPONSE_REVIEW_INPUTS_VERSION, supportedVersions: SUPPORTED_CASE_RESPONSE_REVIEW_INPUTS_VERSIONS,
342
389
  acceptsUnversionedLegacy: false, futureVersionBehavior: 'reject', migration: 'read_only',
343
390
  writeSemantics: 'read_only', byteBudget: null, owner: CASE_CONTRACT_OWNER,
344
- note: 'Exact published review-input version 1 and current version 2 remain independently readable for response authorisation; extensions and future versions fail closed.',
391
+ note: 'Declared supported review inputs remain independently readable for response authorisation; extensions and future versions fail closed.',
345
392
  });
346
393
  export const CLI_CASE_PACK_COMPATIBILITY = defineSchemaCompatibility({
347
394
  id: 'export.cli-case-pack', kind: 'export', schema: CLI_CASE_PACK_SCHEMA, currentVersion: CLI_CASE_PACK_VERSION,
348
395
  supportedVersions: SUPPORTED_CLI_CASE_PACK_VERSIONS, acceptsUnversionedLegacy: false,
349
396
  futureVersionBehavior: 'reject', migration: 'read_only', writeSemantics: 'read_only',
350
397
  byteBudget: MAX_CASE_IMPORT_BYTES, owner: CASE_CONTRACT_OWNER,
351
- note: 'Case-pack version 2 verifies exact Case/report epochs 12/8, 13/9 and 14/10 with deterministic sorted-json-v2 integrity.',
398
+ note: 'Supported Case packs verify the declared exact Case/report epoch pairs with deterministic sorted-json-v2 integrity.',
352
399
  });
353
400
  export const WORKSPACE_ARCHIVE_COMPATIBILITY = defineSchemaCompatibility({
354
401
  id: 'export.workspace-archive', kind: 'export', schema: WORKSPACE_ARCHIVE_SCHEMA,
355
402
  currentVersion: WORKSPACE_ARCHIVE_VERSION, supportedVersions: SUPPORTED_WORKSPACE_ARCHIVE_VERSIONS,
356
403
  acceptsUnversionedLegacy: false, futureVersionBehavior: 'reject', migration: 'normalize_to_current',
357
404
  writeSemantics: 'non_destructive_merge', byteBudget: MAX_WORKSPACE_ARCHIVE_BYTES, owner: CASE_CONTRACT_OWNER,
358
- note: 'Workspace version 7 carries the current Case contract. Exact versions 5 and 6 remain readable; version 5 gains an empty Review Item section without inventing decisions, and future envelopes fail without mutation.',
405
+ note: 'The current workspace carries the current Case contract. Declared historical versions remain readable; version 5 gains an empty Review Item section without inventing decisions, and future envelopes fail without mutation.',
359
406
  });
360
407
  export const WORKSPACE_SETTINGS_COMPATIBILITY = defineSchemaCompatibility({
361
408
  id: 'export.workspace-settings-section', kind: 'export', schema: WORKSPACE_SETTINGS_SCHEMA,
@@ -401,8 +448,8 @@ const CASE_LIFECYCLE_FIXTURES = Object.freeze([
401
448
  version: PUBLIC_CASE_SCHEMA_VERSION,
402
449
  role: 'historical',
403
450
  expectation: 'normalises_to_current_output',
404
- expectedOutputFixtureId: 'browser-case-v14',
405
- shapeId: 'case.browser-store.public',
451
+ expectedOutputFixtureId: 'browser-case-v16',
452
+ shapeId: `case.browser-store.v${PUBLIC_CASE_SCHEMA_VERSION}`,
406
453
  scope: 'repository',
407
454
  }),
408
455
  Object.freeze({
@@ -415,8 +462,8 @@ const CASE_LIFECYCLE_FIXTURES = Object.freeze([
415
462
  version: PUBLISHED_V2_CASE_SCHEMA_VERSION,
416
463
  role: 'historical',
417
464
  expectation: 'normalises_to_current_output',
418
- expectedOutputFixtureId: 'browser-case-v14',
419
- shapeId: 'case.browser-store.v13',
465
+ expectedOutputFixtureId: 'browser-case-v16',
466
+ shapeId: `case.browser-store.v${PUBLISHED_V2_CASE_SCHEMA_VERSION}`,
420
467
  scope: 'repository',
421
468
  }),
422
469
  Object.freeze({
@@ -426,11 +473,39 @@ const CASE_LIFECYCLE_FIXTURES = Object.freeze([
426
473
  sha256: 'ece46f5d27a52a20196678af8236674836f3979c0d45f0ad5751c00677ec8bae',
427
474
  contentDigestSha256: null,
428
475
  schema: CASE_BROWSER_STORE_LIFECYCLE_SCHEMA,
476
+ version: PUBLISHED_V2_2_CASE_SCHEMA_VERSION,
477
+ role: 'historical',
478
+ expectation: 'normalises_to_current_output',
479
+ expectedOutputFixtureId: 'browser-case-v16',
480
+ shapeId: `case.browser-store.v${PUBLISHED_V2_2_CASE_SCHEMA_VERSION}`,
481
+ scope: 'repository',
482
+ }),
483
+ Object.freeze({
484
+ id: 'browser-case-v15',
485
+ path: 'test/fixtures/case-lifecycle/browser-case-v15.json',
486
+ bytes: 2221,
487
+ sha256: 'a061116c9052a7f73ca6b5a88870bc58cbb083cf5fe82e942539b3847c12ad88',
488
+ contentDigestSha256: null,
489
+ schema: CASE_BROWSER_STORE_LIFECYCLE_SCHEMA,
490
+ version: PUBLISHED_V2_3_CASE_SCHEMA_VERSION,
491
+ role: 'historical',
492
+ expectation: 'normalises_to_current_output',
493
+ expectedOutputFixtureId: 'browser-case-v16',
494
+ shapeId: `case.browser-store.v${PUBLISHED_V2_3_CASE_SCHEMA_VERSION}`,
495
+ scope: 'repository',
496
+ }),
497
+ Object.freeze({
498
+ id: 'browser-case-v16',
499
+ path: 'test/fixtures/case-lifecycle/browser-case-v16.json',
500
+ bytes: 3186,
501
+ sha256: 'e0d9dd7d1da29ca31e1b6ccd0c514c44267d84fe9db2eff0ffce23c51fb04ce4',
502
+ contentDigestSha256: null,
503
+ schema: CASE_BROWSER_STORE_LIFECYCLE_SCHEMA,
429
504
  version: CASE_SCHEMA_VERSION,
430
505
  role: 'current',
431
506
  expectation: 'accepted_exact',
432
507
  expectedOutputFixtureId: null,
433
- shapeId: 'case.browser-store.current',
508
+ shapeId: `case.browser-store.v${CASE_SCHEMA_VERSION}`,
434
509
  scope: 'repository',
435
510
  }),
436
511
  Object.freeze({
@@ -443,8 +518,8 @@ const CASE_LIFECYCLE_FIXTURES = Object.freeze([
443
518
  version: PUBLIC_CASE_SCHEMA_VERSION,
444
519
  role: 'historical',
445
520
  expectation: 'normalises_to_current_output',
446
- expectedOutputFixtureId: 'case-export-v14',
447
- shapeId: 'case.export.public',
521
+ expectedOutputFixtureId: 'case-export-v16',
522
+ shapeId: `case.export.v${PUBLIC_CASE_SCHEMA_VERSION}`,
448
523
  scope: 'repository',
449
524
  }),
450
525
  Object.freeze({
@@ -457,8 +532,8 @@ const CASE_LIFECYCLE_FIXTURES = Object.freeze([
457
532
  version: PUBLISHED_V2_CASE_SCHEMA_VERSION,
458
533
  role: 'historical',
459
534
  expectation: 'normalises_to_current_output',
460
- expectedOutputFixtureId: 'case-export-v14',
461
- shapeId: 'case.export.v13',
535
+ expectedOutputFixtureId: 'case-export-v16',
536
+ shapeId: `case.export.v${PUBLISHED_V2_CASE_SCHEMA_VERSION}`,
462
537
  scope: 'repository',
463
538
  }),
464
539
  Object.freeze({
@@ -468,11 +543,39 @@ const CASE_LIFECYCLE_FIXTURES = Object.freeze([
468
543
  sha256: '50cf1df53c3558852b712844d588d9eb955af858e160969d9a6af613c257feaf',
469
544
  contentDigestSha256: null,
470
545
  schema: CASE_EXPORT_LIFECYCLE_SCHEMA,
546
+ version: PUBLISHED_V2_2_CASE_SCHEMA_VERSION,
547
+ role: 'historical',
548
+ expectation: 'normalises_to_current_output',
549
+ expectedOutputFixtureId: 'case-export-v16',
550
+ shapeId: `case.export.v${PUBLISHED_V2_2_CASE_SCHEMA_VERSION}`,
551
+ scope: 'repository',
552
+ }),
553
+ Object.freeze({
554
+ id: 'case-export-v15',
555
+ path: 'test/fixtures/case-lifecycle/case-export-v15.json',
556
+ bytes: 3211,
557
+ sha256: '26cd852ee202b7a50bd2a64e5d412ab52a38aa7195f81a86b26b222398b406f9',
558
+ contentDigestSha256: null,
559
+ schema: CASE_EXPORT_LIFECYCLE_SCHEMA,
560
+ version: PUBLISHED_V2_3_CASE_SCHEMA_VERSION,
561
+ role: 'historical',
562
+ expectation: 'normalises_to_current_output',
563
+ expectedOutputFixtureId: 'case-export-v16',
564
+ shapeId: `case.export.v${PUBLISHED_V2_3_CASE_SCHEMA_VERSION}`,
565
+ scope: 'repository',
566
+ }),
567
+ Object.freeze({
568
+ id: 'case-export-v16',
569
+ path: 'test/fixtures/case-lifecycle/case-export-v16.json',
570
+ bytes: 3230,
571
+ sha256: 'b0c14fd4d391b1067f101d61c1549c28570999c9cebb58aed21e164d7af56ad8',
572
+ contentDigestSha256: null,
573
+ schema: CASE_EXPORT_LIFECYCLE_SCHEMA,
471
574
  version: CASE_SCHEMA_VERSION,
472
575
  role: 'current',
473
576
  expectation: 'accepted_exact',
474
577
  expectedOutputFixtureId: null,
475
- shapeId: 'case.export.current',
578
+ shapeId: `case.export.v${CASE_SCHEMA_VERSION}`,
476
579
  scope: 'repository',
477
580
  }),
478
581
  Object.freeze({
@@ -500,7 +603,7 @@ const CASE_LIFECYCLE_FIXTURES = Object.freeze([
500
603
  role: 'historical',
501
604
  expectation: 'accepted_exact',
502
605
  expectedOutputFixtureId: null,
503
- shapeId: 'case.report.v9',
606
+ shapeId: `case.report.v${PUBLISHED_V2_CASE_REPORT_SCHEMA_VERSION}`,
504
607
  scope: 'repository',
505
608
  }),
506
609
  Object.freeze({
@@ -510,11 +613,39 @@ const CASE_LIFECYCLE_FIXTURES = Object.freeze([
510
613
  sha256: '36c8b89bb1b335d923bd5035020ff47f89c88775e14d08af2d2eb2ee2f5cc03a',
511
614
  contentDigestSha256: null,
512
615
  schema: CASE_REPORT_SCHEMA,
616
+ version: PUBLISHED_V2_2_CASE_REPORT_SCHEMA_VERSION,
617
+ role: 'historical',
618
+ expectation: 'accepted_exact',
619
+ expectedOutputFixtureId: null,
620
+ shapeId: `case.report.v${PUBLISHED_V2_2_CASE_REPORT_SCHEMA_VERSION}`,
621
+ scope: 'repository',
622
+ }),
623
+ Object.freeze({
624
+ id: 'case-report-v11',
625
+ path: 'test/fixtures/case-lifecycle/case-report-v11.json',
626
+ bytes: 4640,
627
+ sha256: 'bbe57dac61074b7badd094b7ffaabd46d3779c10152563e7a101a2126daf09b6',
628
+ contentDigestSha256: null,
629
+ schema: CASE_REPORT_SCHEMA,
630
+ version: PUBLISHED_V2_3_CASE_REPORT_SCHEMA_VERSION,
631
+ role: 'historical',
632
+ expectation: 'accepted_exact',
633
+ expectedOutputFixtureId: null,
634
+ shapeId: `case.report.v${PUBLISHED_V2_3_CASE_REPORT_SCHEMA_VERSION}`,
635
+ scope: 'repository',
636
+ }),
637
+ Object.freeze({
638
+ id: 'case-report-v12',
639
+ path: 'test/fixtures/case-lifecycle/case-report-v12.json',
640
+ bytes: 4778,
641
+ sha256: '175b9ab720d95e497eb89a73f437b00c5db9595833b02b033a09bb526a926f36',
642
+ contentDigestSha256: null,
643
+ schema: CASE_REPORT_SCHEMA,
513
644
  version: CASE_REPORT_SCHEMA_VERSION,
514
645
  role: 'current',
515
646
  expectation: 'accepted_exact',
516
647
  expectedOutputFixtureId: null,
517
- shapeId: 'case.report.v10',
648
+ shapeId: `case.report.v${CASE_REPORT_SCHEMA_VERSION}`,
518
649
  scope: 'repository',
519
650
  }),
520
651
  Object.freeze({
@@ -538,11 +669,39 @@ const CASE_LIFECYCLE_FIXTURES = Object.freeze([
538
669
  sha256: '7bd2237f208714b92c3669c0581dbf0bc2dc22369e53b3ef9da088e8af0f4a83',
539
670
  contentDigestSha256: 'sha256:86d4d88d1d9fc969e6efa432061c64bf0725767d81c50b8655abd2d231dcc5b6',
540
671
  schema: CASE_RESPONSE_PACKET_SCHEMA,
672
+ version: PUBLISHED_V2_2_CASE_RESPONSE_PACKET_VERSION,
673
+ role: 'historical',
674
+ expectation: 'accepted_exact',
675
+ expectedOutputFixtureId: null,
676
+ shapeId: `case.response-packet.v${PUBLISHED_V2_2_CASE_RESPONSE_PACKET_VERSION}`,
677
+ scope: 'repository',
678
+ }),
679
+ Object.freeze({
680
+ id: 'case-response-packet-v9',
681
+ path: 'test/fixtures/case-lifecycle/case-response-packet-v9.json',
682
+ bytes: 12275,
683
+ sha256: '6dbb9b9d2c199ebb0a1f43c3a08aaac98554787b40fed0456eff60ab2f36906c',
684
+ contentDigestSha256: 'sha256:3500158f08920e43ad16d588b8bfe3b8b92c58370d80468db4330baabeeca497',
685
+ schema: CASE_RESPONSE_PACKET_SCHEMA,
686
+ version: PUBLISHED_V2_3_CASE_RESPONSE_PACKET_VERSION,
687
+ role: 'historical',
688
+ expectation: 'accepted_exact',
689
+ expectedOutputFixtureId: null,
690
+ shapeId: `case.response-packet.v${PUBLISHED_V2_3_CASE_RESPONSE_PACKET_VERSION}`,
691
+ scope: 'repository',
692
+ }),
693
+ Object.freeze({
694
+ id: 'case-response-packet-v10',
695
+ path: 'test/fixtures/case-lifecycle/case-response-packet-v10.json',
696
+ bytes: 12276,
697
+ sha256: '991475b878f65d9673e60c776162daf921ec5915e16715080ea0534516cff9c4',
698
+ contentDigestSha256: 'sha256:97cfe929ace59edb46ee0b84e6135614aba782e170e730c3de3fb24b82413162',
699
+ schema: CASE_RESPONSE_PACKET_SCHEMA,
541
700
  version: CASE_RESPONSE_PACKET_VERSION,
542
701
  role: 'current',
543
702
  expectation: 'accepted_exact',
544
703
  expectedOutputFixtureId: null,
545
- shapeId: 'case.response-packet.v8',
704
+ shapeId: `case.response-packet.v${CASE_RESPONSE_PACKET_VERSION}`,
546
705
  scope: 'repository',
547
706
  }),
548
707
  Object.freeze({
@@ -566,11 +725,39 @@ const CASE_LIFECYCLE_FIXTURES = Object.freeze([
566
725
  sha256: 'dcc9aaa7cdbd79a33559095f4c212e4eea5ad635c8c36290456d8b1946845aa6',
567
726
  contentDigestSha256: null,
568
727
  schema: CASE_RESPONSE_REVIEW_INPUTS_SCHEMA,
728
+ version: PUBLISHED_V2_2_CASE_RESPONSE_REVIEW_INPUTS_VERSION,
729
+ role: 'historical',
730
+ expectation: 'accepted_exact',
731
+ expectedOutputFixtureId: null,
732
+ shapeId: `case.response-review-inputs.v${PUBLISHED_V2_2_CASE_RESPONSE_REVIEW_INPUTS_VERSION}`,
733
+ scope: 'repository',
734
+ }),
735
+ Object.freeze({
736
+ id: 'case-response-review-inputs-v3',
737
+ path: 'test/fixtures/case-lifecycle/case-response-review-inputs-v3.json',
738
+ bytes: 7604,
739
+ sha256: '13e0711d0a75635825af8cff4a1552efe1be153cc1892c886585f28c25ff03ae',
740
+ contentDigestSha256: null,
741
+ schema: CASE_RESPONSE_REVIEW_INPUTS_SCHEMA,
742
+ version: PUBLISHED_V2_3_CASE_RESPONSE_REVIEW_INPUTS_VERSION,
743
+ role: 'historical',
744
+ expectation: 'accepted_exact',
745
+ expectedOutputFixtureId: null,
746
+ shapeId: `case.response-review-inputs.v${PUBLISHED_V2_3_CASE_RESPONSE_REVIEW_INPUTS_VERSION}`,
747
+ scope: 'repository',
748
+ }),
749
+ Object.freeze({
750
+ id: 'case-response-review-inputs-v4',
751
+ path: 'test/fixtures/case-lifecycle/case-response-review-inputs-v4.json',
752
+ bytes: 7604,
753
+ sha256: 'b054fc8405663992055ec6c07998fd19ab2f12dcc581b87b42f773b1ab4acec7',
754
+ contentDigestSha256: null,
755
+ schema: CASE_RESPONSE_REVIEW_INPUTS_SCHEMA,
569
756
  version: CASE_RESPONSE_REVIEW_INPUTS_VERSION,
570
757
  role: 'current',
571
758
  expectation: 'accepted_exact',
572
759
  expectedOutputFixtureId: null,
573
- shapeId: 'case.response-review-inputs.v2',
760
+ shapeId: `case.response-review-inputs.v${CASE_RESPONSE_REVIEW_INPUTS_VERSION}`,
574
761
  scope: 'repository',
575
762
  }),
576
763
  Object.freeze({
@@ -615,6 +802,48 @@ const CASE_LIFECYCLE_FIXTURES = Object.freeze([
615
802
  shapeId: 'case.cli-pack.current',
616
803
  scope: 'repository',
617
804
  }),
805
+ Object.freeze({
806
+ id: 'cli-case-pack-v2-case-v15',
807
+ path: 'test/fixtures/case-lifecycle/cli-case-pack-v2-case-v15.json',
808
+ bytes: 9439,
809
+ sha256: '9c07b7828f5e348a6e9ca05e1bd487998851491d85d0b30b38adba196f84faee',
810
+ contentDigestSha256: 'sha256:475a91fe7c3afddd3f73ba5b9a698c08bc7b37f3fb67bcf86441ee908beb0dc8',
811
+ schema: CLI_CASE_PACK_SCHEMA,
812
+ version: CLI_CASE_PACK_VERSION,
813
+ role: 'current',
814
+ expectation: 'accepted_exact',
815
+ expectedOutputFixtureId: null,
816
+ shapeId: 'case.cli-pack.current',
817
+ scope: 'repository',
818
+ }),
819
+ Object.freeze({
820
+ id: 'cli-case-pack-v2-case-v15-current',
821
+ path: 'test/fixtures/case-lifecycle/cli-case-pack-v2-case-v15-current.json',
822
+ bytes: 9439,
823
+ sha256: '9ce7303956589ebfb832157823c74e3ec4d3852507e991d0e1b0c542a06dc377',
824
+ contentDigestSha256: 'sha256:5a88d202efd12c522a6001fa579d2fff8196a0d2ab68571c27894040edbdc3d0',
825
+ schema: CLI_CASE_PACK_SCHEMA,
826
+ version: CLI_CASE_PACK_VERSION,
827
+ role: 'current',
828
+ expectation: 'accepted_exact',
829
+ expectedOutputFixtureId: null,
830
+ shapeId: 'case.cli-pack.current',
831
+ scope: 'repository',
832
+ }),
833
+ Object.freeze({
834
+ id: CLI_CASE_PACK_WRITER_FIXTURE_ID,
835
+ path: `test/fixtures/case-lifecycle/${CLI_CASE_PACK_WRITER_FIXTURE_ID}.json`,
836
+ bytes: 9602,
837
+ sha256: '2ac29a7b64b3d8930e92f6ab6b826566061224f281799f9b4b15c0acc98550f0',
838
+ contentDigestSha256: 'sha256:c6e3cfbe171a85e9318a0a6780d54dc4ad3e662113a75f483fd527c2e36e0143',
839
+ schema: CLI_CASE_PACK_SCHEMA,
840
+ version: CLI_CASE_PACK_VERSION,
841
+ role: 'current',
842
+ expectation: 'accepted_exact',
843
+ expectedOutputFixtureId: null,
844
+ shapeId: 'case.cli-pack.current',
845
+ scope: 'repository',
846
+ }),
618
847
  Object.freeze({
619
848
  id: 'workspace-archive-v5-public',
620
849
  path: 'test/fixtures/case-lifecycle/workspace-archive-v5-public.json',
@@ -625,7 +854,7 @@ const CASE_LIFECYCLE_FIXTURES = Object.freeze([
625
854
  version: PUBLIC_WORKSPACE_ARCHIVE_VERSION,
626
855
  role: 'historical',
627
856
  expectation: 'normalises_to_current_output',
628
- expectedOutputFixtureId: 'workspace-archive-v7-empty-current',
857
+ expectedOutputFixtureId: `workspace-archive-v${WORKSPACE_ARCHIVE_VERSION}-empty-current`,
629
858
  shapeId: 'case.workspace-archive.v5',
630
859
  scope: 'repository',
631
860
  }),
@@ -639,7 +868,7 @@ const CASE_LIFECYCLE_FIXTURES = Object.freeze([
639
868
  version: PUBLISHED_V2_WORKSPACE_ARCHIVE_VERSION,
640
869
  role: 'historical',
641
870
  expectation: 'normalises_to_current_output',
642
- expectedOutputFixtureId: 'workspace-archive-v7-empty-current',
871
+ expectedOutputFixtureId: `workspace-archive-v${WORKSPACE_ARCHIVE_VERSION}-empty-current`,
643
872
  shapeId: 'case.workspace-archive.v6',
644
873
  scope: 'repository',
645
874
  }),
@@ -650,11 +879,39 @@ const CASE_LIFECYCLE_FIXTURES = Object.freeze([
650
879
  sha256: '892b0d321f42bfb34501019388da682071024af452a8263f138cc08221947f0a',
651
880
  contentDigestSha256: null,
652
881
  schema: WORKSPACE_ARCHIVE_SCHEMA,
882
+ version: PUBLISHED_V2_2_WORKSPACE_ARCHIVE_VERSION,
883
+ role: 'historical',
884
+ expectation: 'normalises_to_current_output',
885
+ expectedOutputFixtureId: `workspace-archive-v${WORKSPACE_ARCHIVE_VERSION}-empty-current`,
886
+ shapeId: 'case.workspace-archive.v7',
887
+ scope: 'repository',
888
+ }),
889
+ Object.freeze({
890
+ id: 'workspace-archive-v8-empty-current',
891
+ path: 'test/fixtures/case-lifecycle/workspace-archive-v8-empty-current.json',
892
+ bytes: 7258,
893
+ sha256: '747062e2dbbcf96724b19718d36bf0dc50b08e96d8adb670115d2315032b6c05',
894
+ contentDigestSha256: null,
895
+ schema: WORKSPACE_ARCHIVE_SCHEMA,
896
+ version: LATEST_PUBLIC_WORKSPACE_ARCHIVE_VERSION,
897
+ role: 'historical',
898
+ expectation: 'normalises_to_current_output',
899
+ expectedOutputFixtureId: `workspace-archive-v${WORKSPACE_ARCHIVE_VERSION}-empty-current`,
900
+ shapeId: 'case.workspace-archive.v8',
901
+ scope: 'repository',
902
+ }),
903
+ Object.freeze({
904
+ id: 'workspace-archive-v9-empty-current',
905
+ path: 'test/fixtures/case-lifecycle/workspace-archive-v9-empty-current.json',
906
+ bytes: 7613,
907
+ sha256: '6786a9a892bdc21814b0f1165cd9286fb82258862a4c15140daf9caae71daff0',
908
+ contentDigestSha256: null,
909
+ schema: WORKSPACE_ARCHIVE_SCHEMA,
653
910
  version: WORKSPACE_ARCHIVE_VERSION,
654
911
  role: 'current',
655
912
  expectation: 'accepted_exact',
656
913
  expectedOutputFixtureId: null,
657
- shapeId: 'case.workspace-archive.v7',
914
+ shapeId: `case.workspace-archive.v${WORKSPACE_ARCHIVE_VERSION}`,
658
915
  scope: 'repository',
659
916
  }),
660
917
  Object.freeze({
@@ -754,19 +1011,14 @@ function shape(id, schema, versions, requiredKeys, normalisation, target = null)
754
1011
  };
755
1012
  }
756
1013
  const CASE_LIFECYCLE_SHAPES = Object.freeze([
757
- shape('case.browser-store.public', CASE_BROWSER_STORE_LIFECYCLE_SCHEMA, [PUBLIC_CASE_SCHEMA_VERSION], ['version', 'cases'], 'project_known_fields'),
758
- shape('case.browser-store.v13', CASE_BROWSER_STORE_LIFECYCLE_SCHEMA, [PUBLISHED_V2_CASE_SCHEMA_VERSION], ['version', 'cases'], 'project_known_fields'),
759
- shape('case.browser-store.current', CASE_BROWSER_STORE_LIFECYCLE_SCHEMA, [CASE_SCHEMA_VERSION], ['version', 'cases'], 'project_known_fields'),
760
- shape('case.export.v13', CASE_EXPORT_LIFECYCLE_SCHEMA, [PUBLISHED_V2_CASE_SCHEMA_VERSION], ['version', 'cases'], 'preserve_document'),
761
- shape('case.export.public', CASE_EXPORT_LIFECYCLE_SCHEMA, [PUBLIC_CASE_SCHEMA_VERSION], ['version', 'cases'], 'preserve_document'),
1014
+ ...CASE_BROWSER_SUPPORTED_VERSIONS.map((version) => shape(`case.browser-store.v${version}`, CASE_BROWSER_STORE_LIFECYCLE_SCHEMA, [version], ['version', 'cases'], 'project_known_fields')),
1015
+ ...CASE_IMPORT_VERSIONS.map((version) => shape(`case.export.v${version}`, CASE_EXPORT_LIFECYCLE_SCHEMA, [version], ['version', 'cases'], 'preserve_document')),
762
1016
  shape('case.response-packet.v6', CASE_RESPONSE_PACKET_SCHEMA, [PUBLIC_CASE_RESPONSE_PACKET_VERSION], [
763
1017
  'schema', 'schemaVersion', 'generatedAt', 'reviewRequired', 'submissionPerformed',
764
1018
  'profile', 'case', 'incident', 'contacts', 'preflight', 'escalationHistory',
765
1019
  'provenance', 'integrity',
766
1020
  ], 'preserve_signed_document'),
767
- shape('case.export.current', CASE_EXPORT_LIFECYCLE_SCHEMA, [CASE_SCHEMA_VERSION], ['version', 'cases'], 'preserve_document'),
768
- shape('case.report.v9', CASE_REPORT_SCHEMA, [PUBLISHED_V2_CASE_REPORT_SCHEMA_VERSION], ['schema', 'schemaVersion', 'generatedAt', 'application', 'case', 'currentAssessment', 'evidenceTimeline', 'analystResponse', 'responseLifecycle', 'limitations'], 'preserve_document'),
769
- shape('case.report.v10', CASE_REPORT_SCHEMA, [CASE_REPORT_SCHEMA_VERSION], ['schema', 'schemaVersion', 'generatedAt', 'application', 'case', 'currentAssessment', 'evidenceTimeline', 'analystResponse', 'responseLifecycle', 'limitations'], 'preserve_document'),
1021
+ ...CASE_REPORT_OUTPUT_VERSIONS.map((version) => shape(`case.report.v${version}`, CASE_REPORT_SCHEMA, [version], ['schema', 'schemaVersion', 'generatedAt', 'application', 'case', 'currentAssessment', 'evidenceTimeline', 'analystResponse', 'responseLifecycle', 'limitations'], 'preserve_document')),
770
1022
  shape('case.response-packet.v7', CASE_RESPONSE_PACKET_SCHEMA, [PUBLISHED_V2_CASE_RESPONSE_PACKET_VERSION], [
771
1023
  'schema', 'schemaVersion', 'generatedAt', 'reviewRequired', 'submissionPerformed',
772
1024
  'profile', 'case', 'incident', 'contacts', 'provenance', 'selectedEvidence',
@@ -774,28 +1026,26 @@ const CASE_LIFECYCLE_SHAPES = Object.freeze([
774
1026
  'escalationHistoryOmitted', 'escalationHistoryLimitations', 'responseLifecycle',
775
1027
  'authorisation', 'integrity',
776
1028
  ], 'preserve_signed_document'),
777
- shape('case.response-packet.v8', CASE_RESPONSE_PACKET_SCHEMA, [CASE_RESPONSE_PACKET_VERSION], [
1029
+ ...CASE_RESPONSE_PACKET_OUTPUT_VERSIONS.filter((version) => version >= PUBLISHED_V2_2_CASE_RESPONSE_PACKET_VERSION).map((version) => shape(`case.response-packet.v${version}`, CASE_RESPONSE_PACKET_SCHEMA, [version], [
778
1030
  'schema', 'schemaVersion', 'generatedAt', 'reviewRequired', 'submissionPerformed',
779
1031
  'profile', 'case', 'incident', 'contacts', 'recipientRoute', 'actionBinding',
780
1032
  'provenance', 'selectedEvidence', 'contradictions', 'readiness', 'artefactReferences',
781
1033
  'preflight', 'escalationHistory', 'escalationHistoryOmitted',
782
1034
  'escalationHistoryLimitations', 'responseLifecycle', 'authorisation', 'integrity',
783
- ], 'preserve_signed_document'),
1035
+ ], 'preserve_signed_document')),
784
1036
  shape('case.response-review-inputs.v1', CASE_RESPONSE_REVIEW_INPUTS_SCHEMA, [PUBLISHED_V2_CASE_RESPONSE_REVIEW_INPUTS_VERSION], [
785
1037
  'contract', 'version', 'profile', 'case', 'incident', 'contacts', 'selectedEvidence',
786
1038
  'contradictions', 'readiness', 'artefactReferences', 'escalationHistory',
787
1039
  'escalationHistoryOmitted', 'escalationHistoryLimitations', 'responseLifecycle',
788
1040
  ], 'preserve_signed_document'),
789
- shape('case.response-review-inputs.v2', CASE_RESPONSE_REVIEW_INPUTS_SCHEMA, [CASE_RESPONSE_REVIEW_INPUTS_VERSION], [
1041
+ ...SUPPORTED_CASE_RESPONSE_REVIEW_INPUTS_VERSIONS.filter((version) => version >= PUBLISHED_V2_2_CASE_RESPONSE_REVIEW_INPUTS_VERSION).map((version) => shape(`case.response-review-inputs.v${version}`, CASE_RESPONSE_REVIEW_INPUTS_SCHEMA, [version], [
790
1042
  'contract', 'version', 'profile', 'case', 'incident', 'contacts',
791
1043
  'recipientRoute', 'actionBinding', 'selectedEvidence', 'contradictions',
792
1044
  'readiness', 'artefactReferences', 'escalationHistory',
793
1045
  'escalationHistoryOmitted', 'escalationHistoryLimitations', 'responseLifecycle',
794
- ], 'preserve_signed_document'),
1046
+ ], 'preserve_signed_document')),
795
1047
  shape('case.cli-pack.current', CLI_CASE_PACK_SCHEMA, [CLI_CASE_PACK_VERSION], CLI_CASE_PACK_ROOT_KEYS, 'preserve_signed_document'),
796
- shape('case.workspace-archive.v5', WORKSPACE_ARCHIVE_SCHEMA, [5], ['schema', 'version', 'generatedAt', 'manifest', 'sections', 'limitations'], 'preserve_signed_document'),
797
- shape('case.workspace-archive.v6', WORKSPACE_ARCHIVE_SCHEMA, [PUBLISHED_V2_WORKSPACE_ARCHIVE_VERSION], ['schema', 'version', 'generatedAt', 'manifest', 'sections', 'limitations'], 'preserve_signed_document'),
798
- shape('case.workspace-archive.v7', WORKSPACE_ARCHIVE_SCHEMA, [WORKSPACE_ARCHIVE_VERSION], ['schema', 'version', 'generatedAt', 'manifest', 'sections', 'limitations'], 'preserve_signed_document'),
1048
+ ...SUPPORTED_WORKSPACE_ARCHIVE_VERSIONS.map(version => shape(`case.workspace-archive.v${version}`, WORKSPACE_ARCHIVE_SCHEMA, [version], ['schema', 'version', 'generatedAt', 'manifest', 'sections', 'limitations'], 'preserve_signed_document')),
799
1049
  shape('case.workspace-settings.v1', WORKSPACE_SETTINGS_SCHEMA, [WORKSPACE_SETTINGS_VERSION], ['schema', 'version', 'activeProfileId', 'theme'], 'preserve_document'),
800
1050
  shape('case.encrypted-workspace-archive.v1', ENCRYPTED_WORKSPACE_ARCHIVE_SCHEMA, [ENCRYPTED_WORKSPACE_ARCHIVE_VERSION], ['schema', 'version', 'createdAt', 'content', 'kdf', 'cipher', 'ciphertext'], 'preserve_signed_document'),
801
1051
  ]);
@@ -877,6 +1127,13 @@ const CASE_LIFECYCLE_HOOKS = Object.freeze([
877
1127
  module: 'packages/cases/case-response-packet.mts',
878
1128
  exportName: 'validateCaseResponseReviewInputs',
879
1129
  },
1130
+ {
1131
+ id: 'case.editable.read',
1132
+ role: 'structure_validator',
1133
+ runtime: 'shared',
1134
+ module: 'packages/cases/case-export-input.mts',
1135
+ exportName: 'readEditableCaseExport',
1136
+ },
880
1137
  {
881
1138
  id: 'case.cli-pack.build',
882
1139
  role: 'builder',
@@ -942,6 +1199,14 @@ const CASE_LIFECYCLE_BOUNDS = Object.freeze([
942
1199
  { id: 'serialised-bytes', path: '$', phase: 'serialised', unit: 'bytes', minimum: 1, maximum: MAX_CASE_IMPORT_BYTES, handling: 'reject' },
943
1200
  ],
944
1201
  },
1202
+ {
1203
+ id: 'case.editable.bounds',
1204
+ bounds: [
1205
+ { id: 'raw-bytes', path: '$', phase: 'raw_intake', unit: 'bytes', minimum: 1, maximum: MAX_EDITABLE_CASE_INPUT_BYTES, handling: 'reject' },
1206
+ { id: 'canonical-store-bytes', path: '$', phase: 'normalised', unit: 'bytes', minimum: 1, maximum: MAX_CASE_STORE_BYTES, handling: 'reject' },
1207
+ { id: 'output-bytes', path: '$', phase: 'serialised', unit: 'bytes', minimum: 1, maximum: MAX_EDITABLE_CASE_OUTPUT_BYTES, handling: 'reject' },
1208
+ ],
1209
+ },
945
1210
  {
946
1211
  id: 'case.domain.bounds',
947
1212
  bounds: [
@@ -1035,14 +1300,14 @@ const CASE_LIFECYCLE_EXPIRY = Object.freeze([{
1035
1300
  }]);
1036
1301
  const CASE_LIFECYCLE_SERIALISATION = Object.freeze([
1037
1302
  {
1038
- id: 'case.browser-store.json.v14',
1303
+ id: 'case.browser-store.json',
1039
1304
  schema: CASE_BROWSER_STORE_LIFECYCLE_SCHEMA,
1040
1305
  versions: [CASE_SCHEMA_VERSION],
1041
1306
  mediaType: 'application/json',
1042
1307
  encoding: 'utf-8',
1043
1308
  bom: false,
1044
- indentSpaces: 2,
1045
- terminalLf: true,
1309
+ indentSpaces: 0,
1310
+ terminalLf: false,
1046
1311
  propertyOrder: 'normalised_fixed',
1047
1312
  canonicalisation: null,
1048
1313
  integrity: 'none',
@@ -1050,10 +1315,10 @@ const CASE_LIFECYCLE_SERIALISATION = Object.freeze([
1050
1315
  verifierHookIds: [],
1051
1316
  },
1052
1317
  ...[
1053
- ['case.export.json.v14', CASE_EXPORT_LIFECYCLE_SCHEMA, [CASE_SCHEMA_VERSION], 'none', []],
1054
- ['case.report.json.v10', CASE_REPORT_SCHEMA, [...CASE_REPORT_OUTPUT_VERSIONS], 'none', []],
1055
- ['case.packet.json.v8', CASE_RESPONSE_PACKET_SCHEMA, [...SUPPORTED_CASE_RESPONSE_PACKET_VERSIONS], 'structural_only_requires_separate_verification', ['case.packet.verify']],
1056
- ['case.workspace.json.v7', WORKSPACE_ARCHIVE_SCHEMA, [WORKSPACE_ARCHIVE_VERSION], 'structural_only_requires_separate_verification', ['case.workspace.verify']],
1318
+ ['case.export.json', CASE_EXPORT_LIFECYCLE_SCHEMA, [CASE_SCHEMA_VERSION], 'none', []],
1319
+ ['case.report.json', CASE_REPORT_SCHEMA, [...CASE_REPORT_OUTPUT_VERSIONS], 'none', []],
1320
+ ['case.packet.json', CASE_RESPONSE_PACKET_SCHEMA, [...SUPPORTED_CASE_RESPONSE_PACKET_VERSIONS], 'structural_only_requires_separate_verification', ['case.packet.verify']],
1321
+ [`case.workspace.json.v${WORKSPACE_ARCHIVE_VERSION}`, WORKSPACE_ARCHIVE_SCHEMA, [WORKSPACE_ARCHIVE_VERSION], 'structural_only_requires_separate_verification', ['case.workspace.verify']],
1057
1322
  ['case.encrypted-workspace.json.v1', ENCRYPTED_WORKSPACE_ARCHIVE_SCHEMA, [ENCRYPTED_WORKSPACE_ARCHIVE_VERSION], 'structural_only_requires_separate_verification', ['case.encrypted-workspace.verify']],
1058
1323
  ].map(([id, schema, versions, integrity, verifierHookIds]) => ({
1059
1324
  id,
@@ -1104,10 +1369,10 @@ const CASE_LIFECYCLE_CONSUMERS = Object.freeze([
1104
1369
  discriminator: null,
1105
1370
  }],
1106
1371
  emittedContract: { schema: CASE_BROWSER_STORE_LIFECYCLE_SCHEMA, version: CASE_SCHEMA_VERSION, discriminator: null },
1107
- shapeIds: ['case.browser-store.public', 'case.browser-store.v13', 'case.browser-store.current'],
1372
+ shapeIds: CASE_BROWSER_SUPPORTED_VERSIONS.map((version) => `case.browser-store.v${version}`),
1108
1373
  boundProfileIds: ['case.browser-store.bounds', 'case.domain.bounds'],
1109
1374
  hookIds: ['case.browser.normalise', 'case.browser.serialise'],
1110
- serialisationProfileId: 'case.browser-store.json.v14',
1375
+ serialisationProfileId: 'case.browser-store.json',
1111
1376
  privacyProfileId: 'case.privacy.browser-state',
1112
1377
  retentionEffect: 'browser_indexeddb',
1113
1378
  ...sharedEdge,
@@ -1123,7 +1388,7 @@ const CASE_LIFECYCLE_CONSUMERS = Object.freeze([
1123
1388
  discriminator: null,
1124
1389
  }],
1125
1390
  emittedContract: null,
1126
- shapeIds: ['case.export.public', 'case.export.v13', 'case.export.current'],
1391
+ shapeIds: CASE_IMPORT_VERSIONS.map((version) => `case.export.v${version}`),
1127
1392
  boundProfileIds: ['case.portable.bounds', 'case.domain.bounds'],
1128
1393
  hookIds: ['case.export.merge'],
1129
1394
  serialisationProfileId: null,
@@ -1137,10 +1402,10 @@ const CASE_LIFECYCLE_CONSUMERS = Object.freeze([
1137
1402
  operation: 'build-portable-export',
1138
1403
  acceptedContracts: [],
1139
1404
  emittedContract: { schema: CASE_EXPORT_LIFECYCLE_SCHEMA, version: CASE_SCHEMA_VERSION, discriminator: null },
1140
- shapeIds: ['case.export.current'],
1405
+ shapeIds: [`case.export.v${CASE_SCHEMA_VERSION}`],
1141
1406
  boundProfileIds: ['case.portable.bounds', 'case.domain.bounds'],
1142
1407
  hookIds: ['case.export.build', 'case.portable.serialise'],
1143
- serialisationProfileId: 'case.export.json.v14',
1408
+ serialisationProfileId: 'case.export.json',
1144
1409
  privacyProfileId: 'case.privacy.portable-output',
1145
1410
  retentionEffect: 'operator_controlled_output',
1146
1411
  ...sharedEdge,
@@ -1156,7 +1421,7 @@ const CASE_LIFECYCLE_CONSUMERS = Object.freeze([
1156
1421
  discriminator: null,
1157
1422
  }],
1158
1423
  emittedContract: null,
1159
- shapeIds: ['case.report.v9', 'case.report.v10'],
1424
+ shapeIds: CASE_REPORT_OUTPUT_VERSIONS.map((version) => `case.report.v${version}`),
1160
1425
  boundProfileIds: ['case.domain.bounds'],
1161
1426
  hookIds: ['case.cli-pack.verify'],
1162
1427
  serialisationProfileId: null,
@@ -1170,10 +1435,10 @@ const CASE_LIFECYCLE_CONSUMERS = Object.freeze([
1170
1435
  operation: 'build-report',
1171
1436
  acceptedContracts: [],
1172
1437
  emittedContract: { schema: CASE_REPORT_SCHEMA, version: CASE_REPORT_SCHEMA_VERSION, discriminator: null },
1173
- shapeIds: ['case.report.v10'],
1438
+ shapeIds: [`case.report.v${CASE_REPORT_SCHEMA_VERSION}`],
1174
1439
  boundProfileIds: ['case.domain.bounds'],
1175
1440
  hookIds: ['case.report.build', 'case.portable.serialise'],
1176
- serialisationProfileId: 'case.report.json.v10',
1441
+ serialisationProfileId: 'case.report.json',
1177
1442
  privacyProfileId: 'case.privacy.portable-output',
1178
1443
  retentionEffect: 'operator_controlled_output',
1179
1444
  ...sharedEdge,
@@ -1189,7 +1454,7 @@ const CASE_LIFECYCLE_CONSUMERS = Object.freeze([
1189
1454
  discriminator: null,
1190
1455
  }],
1191
1456
  emittedContract: null,
1192
- shapeIds: ['case.response-packet.v6', 'case.response-packet.v7', 'case.response-packet.v8'],
1457
+ shapeIds: CASE_RESPONSE_PACKET_OUTPUT_VERSIONS.map((version) => `case.response-packet.v${version}`),
1193
1458
  boundProfileIds: ['case.packet.bounds'],
1194
1459
  hookIds: ['case.packet.verify'],
1195
1460
  serialisationProfileId: null,
@@ -1208,7 +1473,7 @@ const CASE_LIFECYCLE_CONSUMERS = Object.freeze([
1208
1473
  discriminator: null,
1209
1474
  }],
1210
1475
  emittedContract: null,
1211
- shapeIds: ['case.response-packet.v6', 'case.response-packet.v7', 'case.response-packet.v8'],
1476
+ shapeIds: CASE_RESPONSE_PACKET_OUTPUT_VERSIONS.map((version) => `case.response-packet.v${version}`),
1212
1477
  boundProfileIds: ['case.packet.bounds'],
1213
1478
  hookIds: ['case.packet.structure', 'case.packet.verify'],
1214
1479
  serialisationProfileId: null,
@@ -1222,10 +1487,10 @@ const CASE_LIFECYCLE_CONSUMERS = Object.freeze([
1222
1487
  operation: 'build-packet',
1223
1488
  acceptedContracts: [],
1224
1489
  emittedContract: { schema: CASE_RESPONSE_PACKET_SCHEMA, version: CASE_RESPONSE_PACKET_VERSION, discriminator: null },
1225
- shapeIds: ['case.response-packet.v8'],
1490
+ shapeIds: [`case.response-packet.v${CASE_RESPONSE_PACKET_VERSION}`],
1226
1491
  boundProfileIds: ['case.packet.bounds'],
1227
1492
  hookIds: ['case.packet.build', 'case.portable.serialise'],
1228
- serialisationProfileId: 'case.packet.json.v8',
1493
+ serialisationProfileId: 'case.packet.json',
1229
1494
  privacyProfileId: 'case.privacy.portable-output',
1230
1495
  retentionEffect: 'operator_controlled_output',
1231
1496
  ...sharedEdge,
@@ -1241,7 +1506,7 @@ const CASE_LIFECYCLE_CONSUMERS = Object.freeze([
1241
1506
  discriminator: null,
1242
1507
  }],
1243
1508
  emittedContract: null,
1244
- shapeIds: ['case.response-review-inputs.v1', 'case.response-review-inputs.v2'],
1509
+ shapeIds: SUPPORTED_CASE_RESPONSE_REVIEW_INPUTS_VERSIONS.map((version) => `case.response-review-inputs.v${version}`),
1245
1510
  boundProfileIds: ['case.packet.bounds'],
1246
1511
  hookIds: ['case.review-inputs.validate'],
1247
1512
  serialisationProfileId: null,
@@ -1259,7 +1524,7 @@ const CASE_LIFECYCLE_CONSUMERS = Object.freeze([
1259
1524
  version: CASE_RESPONSE_REVIEW_INPUTS_VERSION,
1260
1525
  discriminator: null,
1261
1526
  },
1262
- shapeIds: ['case.response-review-inputs.v2'],
1527
+ shapeIds: [`case.response-review-inputs.v${CASE_RESPONSE_REVIEW_INPUTS_VERSION}`],
1263
1528
  boundProfileIds: ['case.packet.bounds'],
1264
1529
  hookIds: ['case.review-inputs.build'],
1265
1530
  serialisationProfileId: null,
@@ -1286,18 +1551,37 @@ const CASE_LIFECYCLE_CONSUMERS = Object.freeze([
1286
1551
  retentionEffect: 'deliberate_local_file',
1287
1552
  ...sharedEdge,
1288
1553
  },
1554
+ {
1555
+ id: 'case.consumer.cli-edit',
1556
+ plane: 'cli',
1557
+ operation: 'review-and-edit-local-file',
1558
+ acceptedContracts: [{
1559
+ schema: CASE_EXPORT_LIFECYCLE_SCHEMA,
1560
+ versions: [...CLI_CASE_PACK_INPUT_CASE_VERSIONS],
1561
+ mode: 'direct',
1562
+ discriminator: null,
1563
+ }],
1564
+ emittedContract: { schema: CASE_EXPORT_LIFECYCLE_SCHEMA, version: CASE_SCHEMA_VERSION, discriminator: null },
1565
+ shapeIds: CLI_CASE_PACK_INPUT_CASE_VERSIONS.map(version => `case.export.v${version}`),
1566
+ boundProfileIds: ['case.editable.bounds', 'case.portable.bounds'],
1567
+ hookIds: ['case.editable.read', 'case.export.build', 'case.browser.serialise'],
1568
+ serialisationProfileId: null,
1569
+ privacyProfileId: 'case.privacy.portable-output',
1570
+ retentionEffect: 'operator_controlled_output',
1571
+ ...sharedEdge,
1572
+ },
1289
1573
  {
1290
1574
  id: 'case.consumer.cli-pack-build',
1291
1575
  plane: 'cli',
1292
1576
  operation: 'build-case-pack',
1293
1577
  acceptedContracts: [{
1294
1578
  schema: CASE_EXPORT_LIFECYCLE_SCHEMA,
1295
- versions: [CASE_SCHEMA_VERSION],
1579
+ versions: [...CLI_CASE_PACK_INPUT_CASE_VERSIONS],
1296
1580
  mode: 'direct',
1297
1581
  discriminator: null,
1298
1582
  }],
1299
1583
  emittedContract: { schema: CLI_CASE_PACK_SCHEMA, version: CLI_CASE_PACK_VERSION, discriminator: null },
1300
- shapeIds: ['case.export.current', 'case.cli-pack.current'],
1584
+ shapeIds: [...CLI_CASE_PACK_INPUT_CASE_VERSIONS.map((version) => `case.export.v${version}`), 'case.cli-pack.current'],
1301
1585
  boundProfileIds: ['case.portable.bounds', 'case.domain.bounds', 'case.cli-pack.bounds'],
1302
1586
  hookIds: ['case.cli-pack.build', 'case.cli-pack.serialise'],
1303
1587
  serialisationProfileId: 'case.cli-pack.json.v2',
@@ -1321,7 +1605,7 @@ const CASE_LIFECYCLE_CONSUMERS = Object.freeze([
1321
1605
  discriminator: null,
1322
1606
  }],
1323
1607
  emittedContract: null,
1324
- shapeIds: ['case.workspace-archive.v5', 'case.workspace-archive.v6', 'case.workspace-archive.v7', 'case.workspace-settings.v1'],
1608
+ shapeIds: [...SUPPORTED_WORKSPACE_ARCHIVE_VERSIONS.map(version => `case.workspace-archive.v${version}`), 'case.workspace-settings.v1'],
1325
1609
  boundProfileIds: ['case.workspace.bounds'],
1326
1610
  hookIds: ['case.workspace.verify'],
1327
1611
  serialisationProfileId: null,
@@ -1335,10 +1619,10 @@ const CASE_LIFECYCLE_CONSUMERS = Object.freeze([
1335
1619
  operation: 'build-workspace',
1336
1620
  acceptedContracts: [],
1337
1621
  emittedContract: { schema: WORKSPACE_ARCHIVE_SCHEMA, version: WORKSPACE_ARCHIVE_VERSION, discriminator: null },
1338
- shapeIds: ['case.workspace-archive.v7'],
1622
+ shapeIds: [`case.workspace-archive.v${WORKSPACE_ARCHIVE_VERSION}`],
1339
1623
  boundProfileIds: ['case.workspace.bounds'],
1340
1624
  hookIds: ['case.workspace.build', 'case.portable.serialise'],
1341
- serialisationProfileId: 'case.workspace.json.v7',
1625
+ serialisationProfileId: `case.workspace.json.v${WORKSPACE_ARCHIVE_VERSION}`,
1342
1626
  privacyProfileId: 'case.privacy.portable-output',
1343
1627
  retentionEffect: 'operator_controlled_output',
1344
1628
  ...sharedEdge,
@@ -1391,7 +1675,7 @@ const CASE_LIFECYCLE_CONSUMERS = Object.freeze([
1391
1675
  version: ENCRYPTED_WORKSPACE_ARCHIVE_VERSION,
1392
1676
  discriminator: null,
1393
1677
  },
1394
- shapeIds: ['case.workspace-archive.v5', 'case.workspace-archive.v6', 'case.workspace-archive.v7', 'case.encrypted-workspace-archive.v1'],
1678
+ shapeIds: [...SUPPORTED_WORKSPACE_ARCHIVE_VERSIONS.map(version => `case.workspace-archive.v${version}`), 'case.encrypted-workspace-archive.v1'],
1395
1679
  boundProfileIds: ['case.workspace.bounds', 'case.encrypted-workspace.bounds'],
1396
1680
  hookIds: ['case.encrypted-workspace.build', 'case.portable.serialise'],
1397
1681
  serialisationProfileId: 'case.encrypted-workspace.json.v1',
@@ -1470,7 +1754,6 @@ export const CASE_PORTABILITY_LIFECYCLE_FAMILY = defineSchemaLifecycleFamily({
1470
1754
  ],
1471
1755
  fixtures: CASE_LIFECYCLE_FIXTURES,
1472
1756
  metadata: {
1473
- metadataVersion: 4,
1474
1757
  enforcement: 'declarative_only',
1475
1758
  shapes: CASE_LIFECYCLE_SHAPES,
1476
1759
  boundProfiles: CASE_LIFECYCLE_BOUNDS,