@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
@@ -1,8 +1,8 @@
1
1
  import { defineSchemaCompatibility, isCanonicalLocalSchemaIdentifier, } from "./schema-compatibility.mjs";
2
+ export const SCHEMA_LIFECYCLE_METADATA_VERSION = 4;
2
3
  const FAMILY_KEYS = new Set(['id', 'owner', 'privacy', 'compatibility', 'contracts', 'fixtures']);
3
4
  const EXTENDED_FAMILY_KEYS = new Set([...FAMILY_KEYS, 'metadata']);
4
- const METADATA_V1_KEYS = new Set([
5
- 'metadataVersion',
5
+ const METADATA_KEYS = new Set([
6
6
  'enforcement',
7
7
  'shapes',
8
8
  'boundProfiles',
@@ -11,15 +11,8 @@ const METADATA_V1_KEYS = new Set([
11
11
  'privacyProfiles',
12
12
  'expiryProfiles',
13
13
  'consumerEdges',
14
- ]);
15
- const METADATA_V2_KEYS = new Set([
16
- ...METADATA_V1_KEYS,
17
14
  'consumerRelationships',
18
15
  ]);
19
- const METADATA_V3_KEYS = METADATA_V2_KEYS;
20
- const METADATA_V4_KEYS = new Set([
21
- ...METADATA_V2_KEYS,
22
- ]);
23
16
  const COMPATIBILITY_KEYS = new Set([
24
17
  'id',
25
18
  'tier',
@@ -52,7 +45,6 @@ const CONTRACT_KEYS = new Set([
52
45
  'fixtureIds',
53
46
  ]);
54
47
  const TARGET_KEYS = new Set(['schema', 'version']);
55
- const EMITTED_TARGET_V4_KEYS = new Set([...TARGET_KEYS, 'discriminator']);
56
48
  const FIXTURE_KEYS = new Set([
57
49
  'id',
58
50
  'path',
@@ -66,11 +58,8 @@ const FIXTURE_KEYS = new Set([
66
58
  'expectedOutputFixtureId',
67
59
  'scope',
68
60
  ]);
69
- const FIXTURE_V4_KEYS = new Set([...FIXTURE_KEYS, 'shapeId']);
70
61
  const SHAPE_KEYS = new Set(['id', 'schema', 'versions', 'objects', 'fixedArrays', 'normalisation', 'target']);
71
- const SHAPE_V4_KEYS = new Set([...SHAPE_KEYS, 'discriminator']);
72
62
  const OBJECT_SHAPE_KEYS = new Set(['path', 'requiredKeys', 'optionalKeys', 'unknownKeys']);
73
- const OBJECT_SHAPE_V4_KEYS = new Set([...OBJECT_SHAPE_KEYS, 'alternativeRequiredKeys']);
74
63
  const ALTERNATIVE_REQUIRED_KEYS = new Set(['keys', 'resolution']);
75
64
  const FIXED_ARRAY_SHAPE_KEYS = new Set(['path', 'values']);
76
65
  const BOUND_PROFILE_KEYS = new Set(['id', 'bounds']);
@@ -86,7 +75,6 @@ const PRIVACY_PROFILE_KEYS = new Set([
86
75
  ]);
87
76
  const EXPIRY_POLICY_KEYS = new Set(['id', 'field', 'anchor', 'handling', 'phase', 'maximumLifetimeDays']);
88
77
  const CONTRACT_REFERENCE_KEYS = new Set(['schema', 'versions', 'mode']);
89
- const CONTRACT_REFERENCE_V3_KEYS = new Set([...CONTRACT_REFERENCE_KEYS, 'discriminator']);
90
78
  const CONSUMER_DISCRIMINATOR_KEYS = new Set(['path', 'values']);
91
79
  const CONTRACT_VARIANT_DISCRIMINATOR_KEYS = new Set(['path', 'value']);
92
80
  const CONSUMER_KEYS = new Set([
@@ -150,7 +138,7 @@ function consumeMetadataValue(budget, value) {
150
138
  }
151
139
  }
152
140
  }
153
- function ordinaryRecord(value, keys, label, budget) {
141
+ function ordinaryRecord(value, keys, label, budget, optionalKeys = []) {
154
142
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
155
143
  throw new TypeError(`${label} must be an ordinary object.`);
156
144
  }
@@ -162,12 +150,14 @@ function ordinaryRecord(value, keys, label, budget) {
162
150
  ? keys
163
151
  : [keys];
164
152
  const ownKeys = Reflect.ownKeys(value);
165
- const selectedKeys = keySets.find((candidate) => ownKeys.length === candidate.size
166
- && ownKeys.every((key) => typeof key === 'string' && candidate.has(key)));
153
+ const selectedKeys = keySets.find((candidate) => ownKeys.length >= candidate.size
154
+ && ownKeys.length <= candidate.size + optionalKeys.length
155
+ && [...candidate].every((key) => ownKeys.includes(key))
156
+ && ownKeys.every((key) => typeof key === 'string' && (candidate.has(key) || optionalKeys.includes(key))));
167
157
  if (!selectedKeys) {
168
158
  throw new TypeError(`${label} must use its exact registered fields.`);
169
159
  }
170
- consumeMetadataSlots(budget, selectedKeys.size + 1);
160
+ consumeMetadataSlots(budget, ownKeys.length + 1);
171
161
  consumeMetadataSerialisedBytes(budget, 2 + Math.max(0, ownKeys.length - 1) + ownKeys
172
162
  .reduce((bytes, key) => bytes + utf8Length(JSON.stringify(key)) + 1, 0));
173
163
  const result = Object.create(null);
@@ -324,18 +314,18 @@ function copyVariantDiscriminator(value, label, budget) {
324
314
  value: boundedText(source.value, `${label} value`),
325
315
  });
326
316
  }
327
- function copyEmittedTarget(value, label, budget, metadataVersion) {
317
+ function copyEmittedTarget(value, label, budget) {
328
318
  if (value === null)
329
319
  return null;
330
- const source = ordinaryRecord(value, metadataVersion === 4 ? EMITTED_TARGET_V4_KEYS : TARGET_KEYS, label, budget);
331
- const discriminator = metadataVersion === 4 && Object.hasOwn(source, 'discriminator')
320
+ const source = ordinaryRecord(value, TARGET_KEYS, label, budget, ['discriminator']);
321
+ const discriminator = Object.hasOwn(source, 'discriminator')
332
322
  && source.discriminator !== null
333
323
  ? copyVariantDiscriminator(source.discriminator, `${label} discriminator`, budget)
334
324
  : null;
335
325
  return Object.freeze({
336
326
  schema: boundedSchema(source.schema, `${label} schema`),
337
327
  version: positiveInteger(source.version, `${label} version`),
338
- ...(metadataVersion === 4 ? { discriminator } : {}),
328
+ discriminator,
339
329
  });
340
330
  }
341
331
  function copyStringArray(value, label) {
@@ -472,7 +462,7 @@ function copyContract(value, index) {
472
462
  }
473
463
  function copyFixture(value, index) {
474
464
  const label = `Schema lifecycle fixture ${index + 1}`;
475
- const source = ordinaryRecord(value, [FIXTURE_KEYS, FIXTURE_V4_KEYS], label);
465
+ const source = ordinaryRecord(value, FIXTURE_KEYS, label, undefined, ['shapeId']);
476
466
  const role = source.role;
477
467
  const expectation = source.expectation;
478
468
  if (role !== 'input' && role !== 'historical' && role !== 'current') {
@@ -520,29 +510,27 @@ function copyFixture(value, index) {
520
510
  : {}),
521
511
  });
522
512
  }
523
- function copyObjectShape(value, index, shapeLabel, budget, metadataVersion) {
513
+ function copyObjectShape(value, index, shapeLabel, budget) {
524
514
  const label = `${shapeLabel} object ${index + 1}`;
525
- const source = ordinaryRecord(value, metadataVersion === 4 ? OBJECT_SHAPE_V4_KEYS : OBJECT_SHAPE_KEYS, label, budget);
515
+ const source = ordinaryRecord(value, OBJECT_SHAPE_KEYS, label, budget, ['alternativeRequiredKeys']);
526
516
  const requiredKeys = copyFieldArray(source.requiredKeys, `${label} required keys`, true, budget);
527
517
  const optionalKeys = copyFieldArray(source.optionalKeys, `${label} optional keys`, true, budget);
528
518
  if (requiredKeys.some((key) => optionalKeys.includes(key))) {
529
519
  throw new TypeError(`${label} required and optional keys must not overlap.`);
530
520
  }
531
- const alternativeRequiredKeys = metadataVersion === 4
521
+ const alternativeRequiredKeys = Object.hasOwn(source, 'alternativeRequiredKeys')
532
522
  ? copyAlternativeRequiredKeys(source.alternativeRequiredKeys, `${label} alternative required keys`, budget)
533
523
  : Object.freeze([]);
534
524
  if (alternativeRequiredKeys.flatMap((group) => group.keys)
535
525
  .some((key) => requiredKeys.includes(key) || optionalKeys.includes(key))) {
536
526
  throw new TypeError(`${label} alternative fields must not overlap required or optional keys.`);
537
527
  }
538
- const unknownKeys = oneOf(source.unknownKeys, metadataVersion === 4
539
- ? ['discard_bounded', 'preserve_bounded', 'reject']
540
- : ['preserve_bounded', 'reject'], `${label} unknown-key handling`);
528
+ const unknownKeys = oneOf(source.unknownKeys, ['discard_bounded', 'preserve_bounded', 'reject'], `${label} unknown-key handling`);
541
529
  return Object.freeze({
542
530
  path: boundedMetadataPath(source.path, `${label} path`),
543
531
  requiredKeys,
544
532
  optionalKeys,
545
- ...(metadataVersion === 4 ? { alternativeRequiredKeys } : {}),
533
+ alternativeRequiredKeys,
546
534
  unknownKeys,
547
535
  });
548
536
  }
@@ -554,18 +542,16 @@ function copyFixedArrayShape(value, index, shapeLabel, budget) {
554
542
  values: copyTextArray(source.values, `${label} values`, false, budget),
555
543
  });
556
544
  }
557
- function copyShape(value, index, budget, metadataVersion) {
545
+ function copyShape(value, index, budget) {
558
546
  const label = `Schema lifecycle shape ${index + 1}`;
559
- const source = ordinaryRecord(value, metadataVersion === 4 ? SHAPE_V4_KEYS : SHAPE_KEYS, label, budget);
560
- const objects = Object.freeze(denseArray(source.objects, `${label} objects`, MAX_NESTED_METADATA_COLLECTION, 1, budget).map((item, objectIndex) => copyObjectShape(item, objectIndex, label, budget, metadataVersion)));
547
+ const source = ordinaryRecord(value, SHAPE_KEYS, label, budget, ['discriminator']);
548
+ const objects = Object.freeze(denseArray(source.objects, `${label} objects`, MAX_NESTED_METADATA_COLLECTION, 1, budget).map((item, objectIndex) => copyObjectShape(item, objectIndex, label, budget)));
561
549
  const fixedArrays = Object.freeze(denseArray(source.fixedArrays, `${label} fixed arrays`, MAX_NESTED_METADATA_COLLECTION, 0, budget).map((item, arrayIndex) => copyFixedArrayShape(item, arrayIndex, label, budget)));
562
550
  if (new Set(objects.map((item) => item.path)).size !== objects.length
563
551
  || new Set(fixedArrays.map((item) => item.path)).size !== fixedArrays.length) {
564
552
  throw new TypeError(`${label} paths must be unique within their shape kind.`);
565
553
  }
566
- const normalisation = oneOf(source.normalisation, metadataVersion === 4
567
- ? ['input_to_current', 'preserve_bounded_document', 'preserve_document', 'preserve_signed_document', 'project_known_fields']
568
- : ['input_to_current', 'preserve_bounded_document', 'preserve_document', 'preserve_signed_document'], `${label} normalisation`);
554
+ const normalisation = oneOf(source.normalisation, ['input_to_current', 'preserve_bounded_document', 'preserve_document', 'preserve_signed_document', 'project_known_fields'], `${label} normalisation`);
569
555
  const target = copyTarget(source.target, `${label} target`, budget);
570
556
  if ((normalisation === 'input_to_current') !== (target !== null)) {
571
557
  throw new TypeError(`${label} input normalisation must name one target and document preservation must not.`);
@@ -578,13 +564,9 @@ function copyShape(value, index, budget, metadataVersion) {
578
564
  fixedArrays,
579
565
  normalisation,
580
566
  target,
581
- ...(metadataVersion === 4
582
- ? {
583
- discriminator: source.discriminator === null
584
- ? null
585
- : copyVariantDiscriminator(source.discriminator, `${label} discriminator`, budget),
586
- }
587
- : {}),
567
+ discriminator: !Object.hasOwn(source, 'discriminator') || source.discriminator === null
568
+ ? null
569
+ : copyVariantDiscriminator(source.discriminator, `${label} discriminator`, budget),
588
570
  });
589
571
  }
590
572
  function copyBound(value, index, profileLabel, budget) {
@@ -760,30 +742,20 @@ function copyExpiryPolicy(value, index, budget) {
760
742
  maximumLifetimeDays,
761
743
  });
762
744
  }
763
- function copyContractReference(value, index, consumerLabel, budget, metadataVersion) {
745
+ function copyContractReference(value, index, consumerLabel, budget) {
764
746
  const label = `${consumerLabel} accepted contract ${index + 1}`;
765
- const source = ordinaryRecord(value, metadataVersion === 4
766
- ? CONTRACT_REFERENCE_V3_KEYS
767
- : metadataVersion === 3
768
- ? [CONTRACT_REFERENCE_KEYS, CONTRACT_REFERENCE_V3_KEYS]
769
- : CONTRACT_REFERENCE_KEYS, label, budget);
770
- let discriminator;
771
- if (metadataVersion >= 3) {
772
- if (!Object.hasOwn(source, 'discriminator') || source.discriminator === null) {
773
- discriminator = null;
774
- }
775
- else {
776
- discriminator = copyConsumerDiscriminator(source.discriminator, `${label} discriminator`, budget);
777
- }
778
- }
747
+ const source = ordinaryRecord(value, CONTRACT_REFERENCE_KEYS, label, budget, ['discriminator']);
748
+ const discriminator = !Object.hasOwn(source, 'discriminator') || source.discriminator === null
749
+ ? null
750
+ : copyConsumerDiscriminator(source.discriminator, `${label} discriminator`, budget);
779
751
  return Object.freeze({
780
752
  schema: boundedSchema(source.schema, `${label} schema`),
781
753
  versions: copyVersionArray(source.versions, `${label} versions`, budget),
782
754
  mode: oneOf(source.mode, ['direct', 'embedded'], `${label} mode`),
783
- ...(metadataVersion >= 3 ? { discriminator: discriminator ?? null } : {}),
755
+ discriminator,
784
756
  });
785
757
  }
786
- function copyConsumerEdge(value, index, budget, metadataVersion) {
758
+ function copyConsumerEdge(value, index, budget) {
787
759
  const label = `Schema lifecycle consumer ${index + 1}`;
788
760
  const source = ordinaryRecord(value, CONSUMER_KEYS, label, budget);
789
761
  const serialisationProfileId = source.serialisationProfileId;
@@ -794,8 +766,8 @@ function copyConsumerEdge(value, index, budget, metadataVersion) {
794
766
  id: boundedId(source.id, `${label} id`),
795
767
  plane: oneOf(source.plane, ['browser', 'cli', 'node', 'shared'], `${label} plane`),
796
768
  operation: boundedId(source.operation, `${label} operation`),
797
- acceptedContracts: Object.freeze(denseArray(source.acceptedContracts, `${label} accepted contracts`, MAX_NESTED_METADATA_COLLECTION, 0, budget).map((item, referenceIndex) => copyContractReference(item, referenceIndex, label, budget, metadataVersion))),
798
- emittedContract: copyEmittedTarget(source.emittedContract, `${label} emitted contract`, budget, metadataVersion),
769
+ acceptedContracts: Object.freeze(denseArray(source.acceptedContracts, `${label} accepted contracts`, MAX_NESTED_METADATA_COLLECTION, 0, budget).map((item, referenceIndex) => copyContractReference(item, referenceIndex, label, budget))),
770
+ emittedContract: copyEmittedTarget(source.emittedContract, `${label} emitted contract`, budget),
799
771
  shapeIds: copyIdArray(source.shapeIds, `${label} shapes`, false, budget),
800
772
  boundProfileIds: copyIdArray(source.boundProfileIds, `${label} bound profiles`, false, budget),
801
773
  hookIds: copyIdArray(source.hookIds, `${label} hooks`, false, budget),
@@ -1036,9 +1008,6 @@ function utf8Length(value) {
1036
1008
  }
1037
1009
  return bytes;
1038
1010
  }
1039
- function metadataConsumerRelationships(metadata) {
1040
- return metadata.metadataVersion === 1 ? [] : metadata.consumerRelationships;
1041
- }
1042
1011
  function validateMetadataSerialisedBudget(metadata) {
1043
1012
  const serialised = JSON.stringify(metadata);
1044
1013
  if (utf8Length(serialised) > MAX_METADATA_SERIALISED_BYTES) {
@@ -1046,11 +1015,6 @@ function validateMetadataSerialisedBudget(metadata) {
1046
1015
  }
1047
1016
  }
1048
1017
  function validateMetadataRelations(family, metadata) {
1049
- if (metadata.metadataVersion < 3 && (metadata.serialisationProfiles.some((profile) => profile.propertyOrder === 'source_insertion')
1050
- || metadata.privacyProfiles.some((profile) => profile.network === 'explicit_bounded_passive_fast_or_deep')
1051
- || metadata.consumerEdges.some((edge) => edge.requestMode === 'explicit_bounded_passive_fast_or_deep'))) {
1052
- throw new TypeError('Schema lifecycle metadata version 3 is required for extensible serialisation and Fast-or-Deep request policy.');
1053
- }
1054
1018
  const contractPairs = new Map(family.contracts.map((contract) => [`${contract.schema}\u0000${contract.version}`, contract]));
1055
1019
  const shapesById = new Map(metadata.shapes.map((shape) => [shape.id, shape]));
1056
1020
  const boundProfilesById = new Map(metadata.boundProfiles.map((profile) => [profile.id, profile]));
@@ -1059,7 +1023,7 @@ function validateMetadataRelations(family, metadata) {
1059
1023
  const privacyProfilesById = new Map(metadata.privacyProfiles.map((profile) => [profile.id, profile]));
1060
1024
  const expiryProfilesById = new Map(metadata.expiryProfiles.map((profile) => [profile.id, profile]));
1061
1025
  const consumerEdgesById = new Map(metadata.consumerEdges.map((edge) => [edge.id, edge]));
1062
- const consumerRelationships = metadataConsumerRelationships(metadata);
1026
+ const consumerRelationships = metadata.consumerRelationships;
1063
1027
  const consumerRelationshipsById = new Map(consumerRelationships.map((relationship) => [relationship.id, relationship]));
1064
1028
  const registries = [
1065
1029
  [shapesById, metadata.shapes.length, 'shape'],
@@ -1109,12 +1073,6 @@ function validateMetadataRelations(family, metadata) {
1109
1073
  }
1110
1074
  const preservesExtensions = shape.objects.some((object) => object.unknownKeys === 'preserve_bounded');
1111
1075
  const discardsExtensions = shape.objects.some((object) => object.unknownKeys === 'discard_bounded');
1112
- if (metadata.metadataVersion < 3 && preservesExtensions) {
1113
- throw new TypeError(`Schema lifecycle shape ${shape.id} requires metadata version 3 for bounded extensions.`);
1114
- }
1115
- if (metadata.metadataVersion !== 4 && discardsExtensions) {
1116
- throw new TypeError(`Schema lifecycle shape ${shape.id} requires metadata version 4 for bounded projection.`);
1117
- }
1118
1076
  if (preservesExtensions && discardsExtensions) {
1119
1077
  throw new TypeError(`Schema lifecycle shape ${shape.id} must use one bounded extension policy.`);
1120
1078
  }
@@ -1137,7 +1095,7 @@ function validateMetadataRelations(family, metadata) {
1137
1095
  && contracts.some((contract) => contract?.canonicalisation !== null))) {
1138
1096
  throw new TypeError(`Schema lifecycle shape ${shape.id} has inconsistent document integrity metadata.`);
1139
1097
  }
1140
- const discriminator = metadata.metadataVersion === 4 ? shape.discriminator ?? null : null;
1098
+ const discriminator = shape.discriminator ?? null;
1141
1099
  if (discriminator) {
1142
1100
  if (shape.versions.length !== 1) {
1143
1101
  throw new TypeError(`Schema lifecycle shape ${shape.id} must qualify exactly one contract version.`);
@@ -1176,22 +1134,27 @@ function validateMetadataRelations(family, metadata) {
1176
1134
  throw new TypeError('Every schema lifecycle contract must have at least one shape.');
1177
1135
  }
1178
1136
  const fixtureShapeIds = new Set();
1179
- if (metadata.metadataVersion === 4) {
1180
- for (const fixture of family.fixtures) {
1181
- const shape = fixture.shapeId ? shapesById.get(fixture.shapeId) : null;
1182
- if (!shape
1183
- || shape.schema !== fixture.schema
1184
- || !shape.versions.includes(fixture.version)) {
1185
- throw new TypeError(`Schema lifecycle fixture ${fixture.id} must name a matching contract shape.`);
1186
- }
1187
- fixtureShapeIds.add(shape.id);
1137
+ for (const fixture of family.fixtures) {
1138
+ const candidates = metadata.shapes.filter((shape) => shape.schema === fixture.schema && shape.versions.includes(fixture.version));
1139
+ // A routine fixture has one possible shape. Variants still require an
1140
+ // explicit fixture binding; never choose arbitrarily between them.
1141
+ const shape = fixture.shapeId
1142
+ ? shapesById.get(fixture.shapeId)
1143
+ : fixture.shapeId === undefined && candidates.length === 1 && !candidates[0]?.discriminator
1144
+ ? candidates[0]
1145
+ : null;
1146
+ if (!shape
1147
+ || shape.schema !== fixture.schema
1148
+ || !shape.versions.includes(fixture.version)) {
1149
+ throw new TypeError(`Schema lifecycle fixture ${fixture.id} must name a matching contract shape.`);
1188
1150
  }
1189
- for (const shape of metadata.shapes) {
1190
- const contracts = shape.versions.map((version) => contractPairs.get(`${shape.schema}\u0000${version}`));
1191
- if ((shape.discriminator !== null || contracts.some((contract) => contract?.lifecycle === 'retired'))
1192
- && !fixtureShapeIds.has(shape.id)) {
1193
- throw new TypeError(`Schema lifecycle shape ${shape.id} must have an exact repository fixture.`);
1194
- }
1151
+ fixtureShapeIds.add(shape.id);
1152
+ }
1153
+ for (const shape of metadata.shapes) {
1154
+ const contracts = shape.versions.map((version) => contractPairs.get(`${shape.schema}\u0000${version}`));
1155
+ if ((shape.discriminator !== null || contracts.some((contract) => contract?.lifecycle === 'retired'))
1156
+ && !fixtureShapeIds.has(shape.id)) {
1157
+ throw new TypeError(`Schema lifecycle shape ${shape.id} must have an exact repository fixture.`);
1195
1158
  }
1196
1159
  }
1197
1160
  for (const profile of metadata.boundProfiles) {
@@ -1270,7 +1233,6 @@ function validateMetadataRelations(family, metadata) {
1270
1233
  }
1271
1234
  const acceptedShapeIds = new Set();
1272
1235
  const emittedShapeIds = new Set();
1273
- const coveredContractPairs = new Set();
1274
1236
  for (const edge of metadata.consumerEdges) {
1275
1237
  if (edge.acceptedContracts.length === 0 && edge.emittedContract === null) {
1276
1238
  throw new TypeError(`Schema lifecycle consumer ${edge.id} must accept or emit a contract.`);
@@ -1298,13 +1260,11 @@ function validateMetadataRelations(family, metadata) {
1298
1260
  || !contract.readable
1299
1261
  || acceptedSelections.has(selectionKey)
1300
1262
  || selectedShapes.some((shape) => acceptedContractShapes.has(`${key}\u0000${shape.id}`))
1301
- || (metadata.metadataVersion === 4
1302
- && ((qualified && (!reference.discriminator
1303
- || selectedShapes.length !== reference.discriminator.values.length))
1304
- || (!qualified && reference.discriminator !== null && reference.discriminator !== undefined)))) {
1263
+ || (qualified && (!reference.discriminator
1264
+ || selectedShapes.length !== reference.discriminator.values.length))) {
1305
1265
  throw new TypeError(`Schema lifecycle consumer ${edge.id} references an unreadable or duplicate contract.`);
1306
1266
  }
1307
- if (metadata.metadataVersion < 4 && reference.discriminator) {
1267
+ if (!qualified && reference.discriminator) {
1308
1268
  const parentPath = parentMetadataPath(reference.discriminator.path);
1309
1269
  const field = metadataPathField(reference.discriminator.path);
1310
1270
  if (contractShapes.some((shape) => {
@@ -1319,7 +1279,6 @@ function validateMetadataRelations(family, metadata) {
1319
1279
  }
1320
1280
  acceptedSelections.add(selectionKey);
1321
1281
  relevantContractPairs.add(key);
1322
- coveredContractPairs.add(key);
1323
1282
  if (selectedShapes.some((shape) => shape.normalisation === 'project_known_fields')) {
1324
1283
  if (contract.byteBudget === null)
1325
1284
  projectedContractWithoutByteBudget = true;
@@ -1329,7 +1288,7 @@ function validateMetadataRelations(family, metadata) {
1329
1288
  for (const shape of selectedShapes) {
1330
1289
  shapeIds.add(shape.id);
1331
1290
  acceptedContractShapes.add(`${key}\u0000${shape.id}`);
1332
- acceptedShapeIds.add(shape.id);
1291
+ acceptedShapeIds.add(`${key}\u0000${shape.id}`);
1333
1292
  }
1334
1293
  }
1335
1294
  }
@@ -1346,17 +1305,15 @@ function validateMetadataRelations(family, metadata) {
1346
1305
  : contractShapes;
1347
1306
  if (!emitted
1348
1307
  || !emitted.emitted
1349
- || (metadata.metadataVersion === 4
1350
- && ((qualified && selectedShapes.length !== 1)
1351
- || (!qualified && edge.emittedContract.discriminator !== null
1352
- && edge.emittedContract.discriminator !== undefined)))) {
1308
+ || (qualified && selectedShapes.length !== 1)
1309
+ || (!qualified && edge.emittedContract.discriminator !== null
1310
+ && edge.emittedContract.discriminator !== undefined)) {
1353
1311
  throw new TypeError(`Schema lifecycle consumer ${edge.id} must emit a registered writable contract.`);
1354
1312
  }
1355
1313
  relevantContractPairs.add(key);
1356
- coveredContractPairs.add(key);
1357
1314
  for (const shape of selectedShapes) {
1358
1315
  shapeIds.add(shape.id);
1359
- emittedShapeIds.add(shape.id);
1316
+ emittedShapeIds.add(`${key}\u0000${shape.id}`);
1360
1317
  }
1361
1318
  }
1362
1319
  if (shapeIds.size !== edge.shapeIds.length || edge.shapeIds.some((id) => !shapeIds.has(id))) {
@@ -1446,25 +1403,19 @@ function validateMetadataRelations(family, metadata) {
1446
1403
  }
1447
1404
  for (const contract of family.contracts) {
1448
1405
  const key = `${contract.schema}\u0000${contract.version}`;
1449
- if (metadata.metadataVersion < 4) {
1450
- if ((contract.readable || contract.emitted) && !coveredContractPairs.has(key)) {
1451
- throw new TypeError('Every readable or emitted schema lifecycle contract must have a consumer edge.');
1452
- }
1453
- continue;
1454
- }
1455
1406
  const contractShapeIds = shapeIdsByContract.get(key) ?? new Set();
1456
1407
  if ([...contractShapeIds].some((id) => {
1457
1408
  const shape = shapesById.get(id);
1458
1409
  return shape?.discriminator !== null
1459
1410
  && shape?.discriminator !== undefined
1460
1411
  && !(contractPairs.get(key)?.lifecycle === 'retired' && fixtureShapeIds.has(id))
1461
- && !acceptedShapeIds.has(id)
1462
- && !emittedShapeIds.has(id);
1412
+ && !acceptedShapeIds.has(`${key}\u0000${id}`)
1413
+ && !emittedShapeIds.has(`${key}\u0000${id}`);
1463
1414
  })) {
1464
1415
  throw new TypeError(`Schema lifecycle contract ${contract.schema} v${contract.version} has an uncovered shape variant.`);
1465
1416
  }
1466
- const hasReader = [...contractShapeIds].some((id) => acceptedShapeIds.has(id));
1467
- const hasWriter = [...contractShapeIds].some((id) => emittedShapeIds.has(id));
1417
+ const hasReader = [...contractShapeIds].some((id) => acceptedShapeIds.has(`${key}\u0000${id}`));
1418
+ const hasWriter = [...contractShapeIds].some((id) => emittedShapeIds.has(`${key}\u0000${id}`));
1468
1419
  if (contract.readable !== hasReader || contract.emitted !== hasWriter) {
1469
1420
  throw new TypeError(`Schema lifecycle contract ${contract.schema} v${contract.version} must exactly aggregate its readable and emitted shape variants.`);
1470
1421
  }
@@ -1500,13 +1451,13 @@ export function defineSchemaLifecycleFamily(value) {
1500
1451
  validateFamilyRelations(family);
1501
1452
  if (!metadataDescriptor) {
1502
1453
  if (contracts.some((contract) => contract.extensionPolicy === 'preserve_bounded')) {
1503
- throw new TypeError('Schema lifecycle metadata version 3 is required for bounded extensions.');
1454
+ throw new TypeError('Schema lifecycle metadata is required for bounded extensions.');
1504
1455
  }
1505
1456
  if (contracts.some((contract) => contract.extensionPolicy === 'discard_bounded'
1506
1457
  || contract.lifecycle === 'retired')
1507
1458
  || fixtures.some((fixture) => Object.hasOwn(fixture, 'shapeId')
1508
1459
  || fixture.expectation === 'historical_output_exact')) {
1509
- throw new TypeError('Schema lifecycle metadata version 4 is required for retired or projected contracts.');
1460
+ throw new TypeError('Schema lifecycle metadata is required for retired or projected contracts.');
1510
1461
  }
1511
1462
  return family;
1512
1463
  }
@@ -1515,49 +1466,28 @@ export function defineSchemaLifecycleFamily(value) {
1515
1466
  textCodeUnits: 0,
1516
1467
  serialisedBytes: 0,
1517
1468
  };
1518
- const metadataSource = ordinaryRecord(source.metadata, [METADATA_V1_KEYS, METADATA_V2_KEYS, METADATA_V3_KEYS, METADATA_V4_KEYS], 'Schema lifecycle metadata', metadataBudget);
1469
+ const metadataSource = ordinaryRecord(source.metadata, METADATA_KEYS, 'Schema lifecycle metadata', metadataBudget, ['metadataVersion']);
1519
1470
  const metadataVersion = metadataSource.metadataVersion;
1520
- const hasConsumerRelationships = Object.hasOwn(metadataSource, 'consumerRelationships');
1521
- if ((metadataVersion !== 1 && metadataVersion !== 2 && metadataVersion !== 3 && metadataVersion !== 4)
1522
- || metadataSource.enforcement !== 'declarative_only'
1523
- || (metadataVersion === 1 && hasConsumerRelationships)
1524
- || ((metadataVersion === 2 || metadataVersion === 3 || metadataVersion === 4) && !hasConsumerRelationships)) {
1471
+ if ((Object.hasOwn(metadataSource, 'metadataVersion') && metadataVersion !== SCHEMA_LIFECYCLE_METADATA_VERSION)
1472
+ || metadataSource.enforcement !== 'declarative_only') {
1525
1473
  throw new TypeError('Schema lifecycle metadata must use an exact registered declarative-only version.');
1526
1474
  }
1527
- const usesVersion4Vocabulary = contracts.some((contract) => contract.extensionPolicy === 'discard_bounded'
1528
- || contract.lifecycle === 'retired')
1529
- || fixtures.some((fixture) => Object.hasOwn(fixture, 'shapeId')
1530
- || fixture.expectation === 'historical_output_exact');
1531
- if ((metadataVersion === 4) !== usesVersion4Vocabulary
1532
- || (metadataVersion === 4 && fixtures.some((fixture) => !Object.hasOwn(fixture, 'shapeId')))) {
1533
- throw new TypeError('Schema lifecycle metadata version 4 must exclusively and completely own its retired or projected vocabulary.');
1534
- }
1535
1475
  const metadataCommon = {
1536
1476
  enforcement: 'declarative_only',
1537
- shapes: Object.freeze(denseArray(metadataSource.shapes, 'Schema lifecycle shapes', MAX_METADATA_COLLECTION, 1, metadataBudget).map((item, index) => copyShape(item, index, metadataBudget, metadataVersion))),
1477
+ shapes: Object.freeze(denseArray(metadataSource.shapes, 'Schema lifecycle shapes', MAX_METADATA_COLLECTION, 1, metadataBudget).map((item, index) => copyShape(item, index, metadataBudget))),
1538
1478
  boundProfiles: Object.freeze(denseArray(metadataSource.boundProfiles, 'Schema lifecycle bound profiles', MAX_METADATA_COLLECTION, 1, metadataBudget).map((item, index) => copyBoundProfile(item, index, metadataBudget))),
1539
1479
  hooks: Object.freeze(denseArray(metadataSource.hooks, 'Schema lifecycle hooks', MAX_METADATA_COLLECTION, 1, metadataBudget).map((item, index) => copyHook(item, index, metadataBudget))),
1540
1480
  serialisationProfiles: Object.freeze(denseArray(metadataSource.serialisationProfiles, 'Schema lifecycle serialisation profiles', MAX_METADATA_COLLECTION, 1, metadataBudget).map((item, index) => copySerialisationProfile(item, index, metadataBudget))),
1541
1481
  privacyProfiles: Object.freeze(denseArray(metadataSource.privacyProfiles, 'Schema lifecycle privacy profiles', MAX_METADATA_COLLECTION, 1, metadataBudget).map((item, index) => copyPrivacyProfile(item, index, metadataBudget))),
1542
1482
  expiryProfiles: Object.freeze(denseArray(metadataSource.expiryProfiles, 'Schema lifecycle expiry profiles', MAX_METADATA_COLLECTION, 1, metadataBudget).map((item, index) => copyExpiryPolicy(item, index, metadataBudget))),
1543
- consumerEdges: Object.freeze(denseArray(metadataSource.consumerEdges, 'Schema lifecycle consumer edges', MAX_METADATA_COLLECTION, 1, metadataBudget).map((item, index) => copyConsumerEdge(item, index, metadataBudget, metadataVersion))),
1483
+ consumerEdges: Object.freeze(denseArray(metadataSource.consumerEdges, 'Schema lifecycle consumer edges', MAX_METADATA_COLLECTION, 1, metadataBudget).map((item, index) => copyConsumerEdge(item, index, metadataBudget))),
1544
1484
  };
1545
- const consumerRelationships = metadataVersion === 1
1546
- ? Object.freeze([])
1547
- : Object.freeze(denseArray(metadataSource.consumerRelationships, 'Schema lifecycle consumer relationships', MAX_METADATA_COLLECTION, 0, metadataBudget).map((item, index) => copyConsumerRelationship(item, index, metadataBudget)));
1548
- const metadata = metadataVersion === 1
1549
- ? Object.freeze({ metadataVersion: 1, ...metadataCommon })
1550
- : metadataVersion === 2
1551
- ? Object.freeze({ metadataVersion: 2, ...metadataCommon, consumerRelationships })
1552
- : metadataVersion === 3
1553
- ? Object.freeze({ metadataVersion: 3, ...metadataCommon, consumerRelationships })
1554
- : Object.freeze({
1555
- metadataVersion: 4,
1556
- ...metadataCommon,
1557
- shapes: metadataCommon.shapes,
1558
- consumerEdges: metadataCommon.consumerEdges,
1559
- consumerRelationships,
1560
- });
1485
+ const consumerRelationships = Object.freeze(denseArray(metadataSource.consumerRelationships, 'Schema lifecycle consumer relationships', MAX_METADATA_COLLECTION, 0, metadataBudget).map((item, index) => copyConsumerRelationship(item, index, metadataBudget)));
1486
+ const metadata = Object.freeze({
1487
+ metadataVersion: SCHEMA_LIFECYCLE_METADATA_VERSION,
1488
+ ...metadataCommon,
1489
+ consumerRelationships,
1490
+ });
1561
1491
  validateMetadataRelations(family, metadata);
1562
1492
  return Object.freeze({ ...family, metadata });
1563
1493
  }
@@ -1586,7 +1516,7 @@ function validateRegistryConsumerRelationships(families) {
1586
1516
  if (!Object.hasOwn(family, 'metadata'))
1587
1517
  continue;
1588
1518
  const metadata = family.metadata;
1589
- for (const relationship of metadataConsumerRelationships(metadata)) {
1519
+ for (const relationship of metadata.consumerRelationships) {
1590
1520
  const source = consumers.get(relationship.sourceConsumerId);
1591
1521
  const target = consumers.get(relationship.targetConsumerId);
1592
1522
  if (!source || source.familyId !== family.id) {
@@ -1686,7 +1616,7 @@ export function defineSchemaLifecycleRegistry(value) {
1686
1616
  ...metadata.privacyProfiles.map((item) => item.id),
1687
1617
  ...metadata.expiryProfiles.map((item) => item.id),
1688
1618
  ...metadata.consumerEdges.map((item) => item.id),
1689
- ...metadataConsumerRelationships(metadata).map((item) => item.id),
1619
+ ...metadata.consumerRelationships.map((item) => item.id),
1690
1620
  ];
1691
1621
  for (const id of familyMetadataIds) {
1692
1622
  if (metadataIds.has(id)) {
@@ -0,0 +1,6 @@
1
+ // A complete 32-MiB workspace's encrypted JSON exceeds 42 MiB. This allowance
2
+ // admits that file and supporting evidence without splitting a selected file.
3
+ export const MAX_SELECTED_FILES = 128;
4
+ export const MAX_SELECTED_FILE_TOTAL_BYTES = 64 * 1024 * 1024;
5
+ export const MAX_SELECTED_FILE_BYTES = MAX_SELECTED_FILE_TOTAL_BYTES;
6
+ export const SELECTED_FILE_MEDIA_TYPES = ['application/json', 'application/octet-stream', 'image/png', 'image/jpeg', 'image/webp', 'image/gif', 'application/pdf'];
@@ -0,0 +1,5 @@
1
+ // Syntax only: an identifier is not evidence that a record exists or applies.
2
+ function isCveIdentifier(value) {
3
+ return typeof value === 'string' && value.length <= 80 && /^CVE-[0-9]{4}-[0-9]{4,}$/u.test(value);
4
+ }
5
+ export { isCveIdentifier };
@@ -0,0 +1,10 @@
1
+ export { MAX_WEB_CAPTURE_MANIFEST_BYTES, WEB_CAPTURE_MANIFEST_SCHEMA, WEB_CAPTURE_MANIFEST_VERSION, WEB_CAPTURE_SUMMARY_SCHEMA, WEB_CAPTURE_SUMMARY_VERSION, } from "./analyst-interchange.mjs";
2
+ export const WEB_CAPTURE_DOM_DIGEST_SCHEMA = 'whoisleuth.dom-digest';
3
+ export const WEB_CAPTURE_DOM_DIGEST_VERSION = 1;
4
+ export const WEB_CAPTURE_COMPARISON_SCHEMA = 'whoisleuth.web-capture-comparison';
5
+ export const WEB_CAPTURE_COMPARISON_VERSION = 3;
6
+ export const MAX_WEB_CAPTURE_DOM_DIGEST_BYTES = 1024 * 1024;
7
+ export const MAX_WEB_CAPTURE_SCREENSHOT_BYTES = 10 * 1024 * 1024;
8
+ export const MAX_WEB_CAPTURE_DOM_ELEMENTS = 20_000;
9
+ export const MAX_WEB_CAPTURE_DOM_PROJECTION_CHARACTERS = 256 * 1024;
10
+ export const MAX_WEB_CAPTURE_VISIBLE_TEXT_BYTES = 256 * 1024;