@slicedearth/whoisleuth-cli 2.3.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 (264) hide show
  1. package/NOTICE +6 -0
  2. package/README.md +19 -15
  3. package/bin/whoisleuth.mjs +8 -1
  4. package/cli/archive-content-digest.mjs +14 -0
  5. package/cli/archive-inspect.mjs +20 -53
  6. package/cli/arguments.mjs +68 -6
  7. package/cli/artifact-validation/case-response.mjs +47 -41
  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 +83 -61
  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-command-runner.mjs +2 -0
  18. package/cli/bulk-output.mjs +29 -3
  19. package/cli/case-command.mjs +195 -0
  20. package/cli/case-pack.mjs +39 -31
  21. package/cli/command-argument-grammar.mjs +4 -0
  22. package/cli/command-reference.mjs +145 -75
  23. package/cli/completion.mjs +298 -79
  24. package/cli/config-profile.mjs +7 -5
  25. package/cli/ct-event-intake.mjs +12 -12
  26. package/cli/discover.mjs +4 -3
  27. package/cli/discovery-observation-snapshot.mjs +44 -16
  28. package/cli/discovery-scan-command-runner.mjs +2 -0
  29. package/cli/discovery-scan.mjs +7 -4
  30. package/cli/discovery-workflow.mjs +1 -1
  31. package/cli/domain-control-monitor.mjs +50 -20
  32. package/cli/domain-control-observations.mjs +149 -102
  33. package/cli/errors.mjs +33 -1
  34. package/cli/evidence-command-runner.mjs +16 -5
  35. package/cli/evidence-signing.mjs +2 -2
  36. package/cli/external-observation-mapping.mjs +7 -8
  37. package/cli/formatters/terminal-lookup.mjs +18 -46
  38. package/cli/formatters/terminal-shared.mjs +5 -3
  39. package/cli/formatters/terminal-technology.mjs +114 -0
  40. package/cli/inline-command-families.mjs +1 -0
  41. package/cli/interactive-launcher.mjs +2 -160
  42. package/cli/interchange-report.mjs +4 -8
  43. package/cli/investigation-artifacts.mjs +176 -0
  44. package/cli/investigation-checkpoint.mjs +10 -0
  45. package/cli/investigation-folder.mjs +114 -0
  46. package/cli/investigation-manifest.mjs +1 -112
  47. package/cli/investigation-package-review.mjs +103 -0
  48. package/cli/investigation-plan.mjs +32 -19
  49. package/cli/investigation-run.mjs +188 -33
  50. package/cli/local-document-checkpoint.mjs +170 -0
  51. package/cli/lookup-browser.mjs +5 -12
  52. package/cli/lookup-command-runner.mjs +17 -6
  53. package/cli/lookup-diff.mjs +37 -3
  54. package/cli/lookup-plan.mjs +11 -5
  55. package/cli/lookup-reconcile.mjs +10 -8
  56. package/cli/mail-review.mjs +66 -14
  57. package/cli/manual.mjs +1 -0
  58. package/cli/network-command-runner.mjs +2 -0
  59. package/cli/output-file.mjs +31 -5
  60. package/cli/registry-doctor.mjs +3 -1
  61. package/cli/registry-support.mjs +1 -1
  62. package/cli/retained-artifact-diff.mjs +2 -2
  63. package/cli/review-command-runner.mjs +45 -0
  64. package/cli/runner.mjs +55 -6
  65. package/cli/sharing-review.mjs +21 -34
  66. package/cli/signer-trust.mjs +104 -0
  67. package/cli/source-reliability.mjs +74 -83
  68. package/cli/terminal-input.mjs +168 -0
  69. package/cli/workflow-command-runner.mjs +68 -13
  70. package/docs/cli-reference.md +76 -30
  71. package/docs/cli.md +289 -25
  72. package/lib/abort.mjs +4 -1
  73. package/lib/availability.mjs +161 -112
  74. package/lib/bounded-file.mjs +3 -1
  75. package/lib/bounded-html-document.mjs +125 -0
  76. package/lib/bounded-json.mjs +43 -3
  77. package/lib/browser-library-profile.mjs +42 -51
  78. package/lib/browser-library-worker.mjs +112 -0
  79. package/lib/client-behavior-profile.mjs +3 -3
  80. package/lib/ct-search.mjs +11 -4
  81. package/lib/dns-delegation-health.mjs +24 -14
  82. package/lib/dns-inheritance-review.mjs +217 -0
  83. package/lib/dns-intelligence.mjs +36 -23
  84. package/lib/dns-mx.mjs +1 -1
  85. package/lib/dnssec-evidence-validation.mjs +11 -3
  86. package/lib/domain-change-packet.mjs +7 -2
  87. package/lib/domain-change-review.mjs +14 -8
  88. package/lib/domain-control-flight-recorder.mjs +219 -118
  89. package/lib/domain-control-manifest.mjs +117 -57
  90. package/lib/domain-posture-analysis.mjs +20 -7
  91. package/lib/domain-posture-parsers.mjs +88 -6
  92. package/lib/domain-posture.mjs +113 -26
  93. package/lib/evidence-export-privacy.mjs +561 -0
  94. package/lib/evidence-export.mjs +11 -592
  95. package/lib/evidence-report.mjs +3 -1
  96. package/lib/favicon.mjs +117 -41
  97. package/lib/generated/cisa-kev-catalog.mjs +12 -4
  98. package/lib/generated/registrar-standing-catalogue.mjs +7 -7
  99. package/lib/generated/retire-browser-catalog.mjs +2335 -940
  100. package/lib/hostname.mjs +2 -38
  101. package/lib/html-signals.mjs +82 -85
  102. package/lib/http-evidence-bounds.mjs +2 -1
  103. package/lib/http-intelligence.mjs +39 -26
  104. package/lib/lookup-cache.mjs +34 -11
  105. package/lib/lookup-child-profile-contract.mjs +24 -15
  106. package/lib/lookup-diagnostics.mjs +24 -1
  107. package/lib/lookup-progress.mjs +30 -13
  108. package/lib/lookup-response-contract.mjs +1048 -0
  109. package/lib/lookup-response.mjs +7 -10
  110. package/lib/lookup-source-progress.mjs +17 -14
  111. package/lib/lookup.mjs +48 -27
  112. package/lib/observed-network-context.mjs +43 -27
  113. package/lib/outbound-request-bounds.mjs +4 -1
  114. package/lib/page-fingerprints.mjs +69 -133
  115. package/lib/page-role-profile.mjs +12 -8
  116. package/lib/perceptual-hash.mjs +43 -14
  117. package/lib/rdap-bootstrap.mjs +72 -22
  118. package/lib/rdap-client.mjs +15 -7
  119. package/lib/rdap-normalization.mjs +33 -18
  120. package/lib/rdap-search-workbench.mjs +1 -1
  121. package/lib/rdap-transport.mjs +47 -20
  122. package/lib/rdap-types.mjs +2 -1
  123. package/lib/rdap.mjs +18 -3
  124. package/lib/registrable-domain.mjs +21 -1
  125. package/lib/registrar-standing-contract.mjs +1 -0
  126. package/lib/registry-capabilities.mjs +5 -1
  127. package/lib/registry-capability-catalogue.mjs +2 -1
  128. package/lib/registry-dates.mjs +2 -190
  129. package/lib/registry-insights.mjs +56 -18
  130. package/lib/response-policy.mjs +86 -48
  131. package/lib/risk-calibration-summary.mjs +17 -3
  132. package/lib/rpki-evidence.mjs +8 -5
  133. package/lib/safe-fetch.mjs +54 -47
  134. package/lib/security-txt.mjs +10 -7
  135. package/lib/service-binding-dns.mjs +8 -0
  136. package/lib/smtp-transport-review.mjs +27 -15
  137. package/lib/sslbl-certificates.generated.mjs +136 -135
  138. package/lib/static-html-analysis.mjs +117 -146
  139. package/lib/structured-data-identity.mjs +1 -0
  140. package/lib/technology-markup.mjs +100 -0
  141. package/lib/threat-intelligence-manifests.mjs +11 -139
  142. package/lib/threat-intelligence-runtime.mjs +24 -52
  143. package/lib/threat-intelligence-targets.mjs +6 -34
  144. package/lib/threat-intelligence-types.mjs +39 -0
  145. package/lib/typosquat-generator.mjs +19 -15
  146. package/lib/urlhaus-intelligence.mjs +8 -3
  147. package/lib/urlscan-intelligence.mjs +1 -1
  148. package/lib/website-security-posture.mjs +30 -6
  149. package/lib/website-technology.mjs +111 -71
  150. package/lib/whois-authority.mjs +14 -2
  151. package/lib/whois-chain.mjs +9 -2
  152. package/lib/whois-common-formats.mjs +5 -5
  153. package/lib/whois-contracts.mjs +2 -1
  154. package/lib/whois-dialects.mjs +2 -2
  155. package/lib/whois-parser-support.mjs +8 -1
  156. package/lib/whois-transport.mjs +23 -7
  157. package/package.json +2 -1
  158. package/packages/cases/case-attachment-model.mjs +83 -0
  159. package/packages/cases/case-evidence-model.mjs +156 -62
  160. package/packages/cases/case-export-input.mjs +53 -0
  161. package/packages/cases/case-migration-model.mjs +59 -25
  162. package/packages/cases/case-model.mjs +1 -0
  163. package/packages/cases/case-recheck-model.mjs +91 -0
  164. package/packages/cases/case-record-core.mjs +3 -2
  165. package/packages/cases/case-record-operations.mjs +89 -27
  166. package/packages/cases/case-record-projection.mjs +23 -20
  167. package/packages/cases/case-report.mjs +63 -52
  168. package/packages/cases/case-response-actions.mjs +654 -0
  169. package/packages/cases/case-response-model.mjs +41 -1129
  170. package/packages/cases/case-response-outcomes.mjs +317 -0
  171. package/packages/cases/case-response-packet-render.mjs +153 -0
  172. package/packages/cases/case-response-packet-types.mjs +1 -0
  173. package/packages/cases/case-response-packet-vocabulary.mjs +48 -0
  174. package/packages/cases/case-response-packet.mjs +38 -587
  175. package/packages/cases/case-response-records.mjs +94 -0
  176. package/packages/cases/case-response-review-inputs.mjs +371 -0
  177. package/packages/cases/case-response-values.mjs +85 -0
  178. package/packages/cases/case-review-package.mjs +45 -0
  179. package/packages/cases/case-selection.mjs +46 -0
  180. package/packages/cases/case-storage-model.mjs +25 -0
  181. package/packages/cases/response-route-freshness.mjs +16 -0
  182. package/packages/comparison/capture-context.mjs +61 -0
  183. package/packages/comparison/comparison-ledger-bulk.mjs +4 -1
  184. package/packages/comparison/page-similarity.mjs +2 -1
  185. package/packages/comparison/relationship-evidence.mjs +85 -8
  186. package/packages/comparison/relationship-provenance.mjs +93 -0
  187. package/packages/contracts/analyst-interchange.mjs +216 -0
  188. package/packages/contracts/analyst-review-state-contract.mjs +11 -0
  189. package/packages/contracts/analyst-review-state.mjs +5 -4
  190. package/packages/contracts/case-portability.mjs +279 -116
  191. package/packages/contracts/case-views-contract.mjs +12 -0
  192. package/packages/contracts/case-views.mjs +26 -0
  193. package/packages/contracts/cli-command-semantics.mjs +1 -0
  194. package/packages/contracts/cli-lookup.mjs +0 -1
  195. package/packages/contracts/domain-control-flight-recorder.mjs +80 -42
  196. package/packages/contracts/domain-control-manifest.mjs +127 -73
  197. package/packages/contracts/domain-control-monitor.mjs +48 -45
  198. package/packages/contracts/domain-control-review.mjs +179 -121
  199. package/packages/contracts/domain-name.mjs +21 -0
  200. package/packages/contracts/external-observation-interchange.mjs +2 -1
  201. package/packages/contracts/extracted-domain-lifecycle.mjs +11 -32
  202. package/packages/contracts/http-url.mjs +2 -0
  203. package/packages/contracts/investigation-package-limits.mjs +24 -0
  204. package/packages/contracts/investigation-portability.mjs +56 -27
  205. package/packages/contracts/investigation-run.mjs +16 -0
  206. package/packages/contracts/ip-address.mjs +34 -0
  207. package/packages/contracts/lookup-evidence.mjs +35 -0
  208. package/packages/contracts/offline-comparison.mjs +1 -2
  209. package/packages/contracts/page-fingerprints.mjs +15 -0
  210. package/packages/contracts/risk-calibration.mjs +0 -1
  211. package/packages/contracts/schema-compatibility.mjs +1 -1
  212. package/packages/contracts/schema-lifecycle.mjs +83 -153
  213. package/packages/contracts/selected-file-limits.mjs +6 -0
  214. package/packages/contracts/vulnerability-identifiers.mjs +5 -0
  215. package/packages/contracts/web-capture.mjs +10 -0
  216. package/packages/contracts/workspace-portability.mjs +160 -225
  217. package/packages/evidence/artifact-integrity.mjs +8 -2
  218. package/packages/evidence/artifact-structure.mjs +105 -0
  219. package/packages/evidence/decision-fact.mjs +4 -1
  220. package/packages/evidence/domain-control-runtime.mjs +155 -27
  221. package/packages/evidence/domain-posture-context.mjs +79 -0
  222. package/packages/evidence/image-regions.mjs +56 -0
  223. package/packages/evidence/latest-observations.mjs +28 -0
  224. package/packages/evidence/lookup-target.mjs +104 -0
  225. package/packages/evidence/observation.mjs +16 -1
  226. package/packages/evidence/passphrase-encryption.mjs +44 -0
  227. package/packages/evidence/record-identity.mjs +72 -0
  228. package/packages/evidence/registry-dates.mjs +190 -0
  229. package/packages/evidence/retained-file.mjs +69 -0
  230. package/packages/evidence/review-age.mjs +14 -0
  231. package/packages/interchange/bagit.mjs +342 -0
  232. package/packages/interchange/bounded-zip-extraction.mjs +278 -0
  233. package/packages/interchange/crc32.mjs +17 -0
  234. package/packages/interchange/external-findings-import.mjs +89 -49
  235. package/packages/interchange/web-capture-import.mjs +382 -0
  236. package/packages/investigation/bulk-mail-exposure.mjs +2 -1
  237. package/packages/investigation/investigation-bagit.mjs +14 -0
  238. package/packages/investigation/investigation-capsule.mjs +33 -5
  239. package/packages/investigation/investigation-manifest.mjs +200 -0
  240. package/packages/investigation/investigation-package-crypto.mjs +98 -0
  241. package/packages/investigation/investigation-package.mjs +200 -0
  242. package/packages/investigation/lookup-asset-graph-inputs.mjs +38 -0
  243. package/packages/investigation/lookup-asset-graph.mjs +161 -141
  244. package/packages/investigation/lookup-claim-readiness.mjs +18 -0
  245. package/packages/investigation/lookup-investigation-brief.mjs +3 -3
  246. package/packages/investigation/lookup-task-guidance.mjs +58 -0
  247. package/packages/monitoring/analyst-review-state.mjs +38 -90
  248. package/packages/workspace/brand-profile-model.mjs +158 -20
  249. package/packages/workspace/bulk-columns.mjs +17 -0
  250. package/packages/workspace/bulk-review-model.mjs +11 -5
  251. package/packages/workspace/bulk-session-model.mjs +95 -20
  252. package/packages/workspace/bulk-sort.mjs +1 -0
  253. package/packages/workspace/case-views.mjs +102 -0
  254. package/packages/workspace/hostile-input.mjs +29 -8
  255. package/packages/workspace/investigation-template-model.mjs +19 -4
  256. package/packages/workspace/page-baseline.mjs +18 -15
  257. package/packages/workspace/relationship-observation-model.mjs +41 -26
  258. package/packages/workspace/watchlist-history.mjs +2 -2
  259. package/packages/workspace/watchlist-store.mjs +8 -2
  260. package/packages/workspace/website-snapshot-model.mjs +70 -30
  261. package/packages/workspace/workspace-archive-crypto.mjs +41 -61
  262. package/packages/workspace/workspace-archive.mjs +85 -21
  263. package/third-party-notices.txt +29 -2
  264. package/packages/investigation/bulk-review-export.mjs +0 -63
@@ -8,6 +8,8 @@ export const MAX_EXTERNAL_FINDINGS_IMPORT_BYTES = 384 * 1024;
8
8
  export const MAX_EXTERNAL_FINDINGS = 100;
9
9
  export const MAX_EXTERNAL_FINDINGS_PER_DOMAIN = 20;
10
10
  export const MAX_EXTERNAL_FINDING_DOMAINS = 25;
11
+ export const MAX_EXTERNAL_FINDING_LIMITATIONS = 8;
12
+ export const MAX_EXTERNAL_FINDING_LIMITATION_LENGTH = 240;
11
13
  export const EXTERNAL_FINDING_ROWS_SCHEMA = 'whoisleuth.external-finding-rows';
12
14
  export const EXTERNAL_FINDING_ROWS_VERSION = 1;
13
15
  export const DOMAIN_OBSERVATION_ROWS_SCHEMA = 'whoisleuth.domain-observation-rows';
@@ -147,7 +149,6 @@ export const EXTERNAL_OBSERVATION_INTERCHANGE_LIFECYCLE_FAMILY = defineSchemaLif
147
149
  contracts: EXTERNAL_INTERCHANGE_CONTRACTS,
148
150
  fixtures: EXTERNAL_INTERCHANGE_FIXTURES,
149
151
  metadata: {
150
- metadataVersion: 3,
151
152
  enforcement: 'declarative_only',
152
153
  shapes: [
153
154
  externalShape('external-interchange.findings.v4', EXTERNAL_FINDINGS_SCHEMA, [...SUPPORTED_EXTERNAL_FINDINGS_VERSIONS], ['schema', 'schemaVersion', 'source', 'findings'], 'preserve_document'),
@@ -1,10 +1,8 @@
1
1
  function formatSlug(id) {
2
2
  return id.replaceAll('.', '-');
3
3
  }
4
- function lifecycleShapeId(format, version, v4) {
5
- return v4
6
- ? `${formatSlug(format.descriptor.id)}.v${version}`
7
- : `${formatSlug(format.descriptor.id)}.document`;
4
+ function lifecycleShapeId(format, version) {
5
+ return `${formatSlug(format.descriptor.id)}.v${version}`;
8
6
  }
9
7
  function contractState(format, version) {
10
8
  const current = version === format.descriptor.currentVersion;
@@ -21,7 +19,7 @@ function contractState(format, version) {
21
19
  : null,
22
20
  };
23
21
  }
24
- function fixtureRecord(format, fixture, v4) {
22
+ function fixtureRecord(format, fixture) {
25
23
  const state = contractState(format, fixture.version);
26
24
  const output = format.fixtures.find((candidate) => candidate.version === format.descriptor.currentVersion);
27
25
  return {
@@ -38,12 +36,11 @@ function fixtureRecord(format, fixture, v4) {
38
36
  ? output?.id ?? null
39
37
  : null,
40
38
  scope: 'repository',
41
- ...(v4 ? { shapeId: lifecycleShapeId(format, fixture.version, true) } : {}),
39
+ shapeId: lifecycleShapeId(format, fixture.version),
42
40
  };
43
41
  }
44
- function buildExtractedLifecycleFamily(options, metadataVersion) {
45
- const v4 = metadataVersion === 4;
46
- const fixtures = options.formats.flatMap((format) => format.fixtures.map((fixture) => (fixtureRecord(format, fixture, v4))));
42
+ export function buildExtractedLifecycleFamily(options) {
43
+ const fixtures = options.formats.flatMap((format) => format.fixtures.map((fixture) => (fixtureRecord(format, fixture))));
47
44
  const contracts = options.formats.flatMap((format) => format.descriptor.supportedVersions.map((version) => ({
48
45
  compatibilityId: format.descriptor.id,
49
46
  schema: format.lifecycleSchema,
@@ -58,23 +55,19 @@ function buildExtractedLifecycleFamily(options, metadataVersion) {
58
55
  .filter((fixture) => fixture.schema === format.lifecycleSchema && fixture.version === version)
59
56
  .map((fixture) => fixture.id),
60
57
  })));
61
- const shapes = options.formats.flatMap((format) => (v4
62
- ? format.descriptor.supportedVersions
63
- : [format.descriptor.currentVersion]).map((version) => ({
64
- id: lifecycleShapeId(format, version, v4),
58
+ const shapes = options.formats.flatMap((format) => format.descriptor.supportedVersions.map((version) => ({
59
+ id: lifecycleShapeId(format, version),
65
60
  schema: format.lifecycleSchema,
66
- versions: v4 ? [version] : format.descriptor.supportedVersions,
61
+ versions: [version],
67
62
  objects: [{
68
63
  path: '$',
69
64
  requiredKeys: format.requiredKeys,
70
65
  optionalKeys: format.optionalKeys,
71
- ...(v4 ? { alternativeRequiredKeys: [] } : {}),
72
66
  unknownKeys: 'reject',
73
67
  }],
74
68
  fixedArrays: [],
75
69
  normalisation: 'preserve_document',
76
70
  target: null,
77
- ...(v4 ? { discriminator: null } : {}),
78
71
  })));
79
72
  const boundProfiles = options.formats.map((format) => ({
80
73
  id: `${formatSlug(format.descriptor.id)}.bounds`,
@@ -120,9 +113,7 @@ function buildExtractedLifecycleFamily(options, metadataVersion) {
120
113
  const outputOnly = format.descriptor.futureVersionBehavior === 'not_applicable';
121
114
  const acceptedVersions = outputOnly ? [] : format.descriptor.supportedVersions;
122
115
  const edgeVersions = outputOnly ? [format.descriptor.currentVersion] : format.descriptor.supportedVersions;
123
- const shapeIds = v4
124
- ? edgeVersions.map((version) => lifecycleShapeId(format, version, true))
125
- : [lifecycleShapeId(format, format.descriptor.currentVersion, false)];
116
+ const shapeIds = edgeVersions.map((version) => lifecycleShapeId(format, version));
126
117
  const hookId = hookIdFor(format);
127
118
  const serialises = index === 0;
128
119
  return {
@@ -134,13 +125,11 @@ function buildExtractedLifecycleFamily(options, metadataVersion) {
134
125
  schema: format.lifecycleSchema,
135
126
  versions: acceptedVersions,
136
127
  mode: 'direct',
137
- ...(metadataVersion >= 3 ? { discriminator: null } : {}),
138
128
  }]
139
129
  : [],
140
130
  emittedContract: {
141
131
  schema: format.lifecycleSchema,
142
132
  version: format.descriptor.currentVersion,
143
- ...(v4 ? { discriminator: null } : {}),
144
133
  },
145
134
  shapeIds,
146
135
  boundProfileIds: [`${formatSlug(format.descriptor.id)}.bounds`],
@@ -155,7 +144,6 @@ function buildExtractedLifecycleFamily(options, metadataVersion) {
155
144
  };
156
145
  });
157
146
  const metadata = {
158
- metadataVersion,
159
147
  enforcement: 'declarative_only',
160
148
  shapes,
161
149
  boundProfiles,
@@ -181,7 +169,7 @@ function buildExtractedLifecycleFamily(options, metadataVersion) {
181
169
  projection: options.projection,
182
170
  includedCategories: options.includedCategories,
183
171
  excludedCategories: options.excludedCategories,
184
- notePolicy: 'discarded',
172
+ notePolicy: options.notePolicy ?? 'discarded',
185
173
  retention: options.retention,
186
174
  network: 'none',
187
175
  sharingReview: options.retention === 'operator_controlled_output' ? 'required' : 'not_applicable',
@@ -207,12 +195,3 @@ function buildExtractedLifecycleFamily(options, metadataVersion) {
207
195
  metadata,
208
196
  };
209
197
  }
210
- export function buildExtractedLifecycleFamilyV2(options) {
211
- return buildExtractedLifecycleFamily(options, 2);
212
- }
213
- export function buildExtractedLifecycleFamilyV3(options) {
214
- return buildExtractedLifecycleFamily(options, 3);
215
- }
216
- export function buildExtractedLifecycleFamilyV4(options) {
217
- return buildExtractedLifecycleFamily(options, 4);
218
- }
@@ -0,0 +1,2 @@
1
+ // Shared admission bound for a single outbound HTTP URL, before any request.
2
+ export const MAX_OUTBOUND_HTTP_URL_CHARACTERS = 4_096;
@@ -0,0 +1,24 @@
1
+ import { MAX_SELECTED_FILES, MAX_SELECTED_FILE_TOTAL_BYTES, MAX_SELECTED_FILE_BYTES } from "./selected-file-limits.mjs";
2
+ import { MAX_EDITABLE_CASE_OUTPUT_BYTES } from "./case-portability.mjs";
3
+ // A complete Case handoff carries every admitted original plus the Case JSON.
4
+ // File-retention bounds remain independent of this container allowance.
5
+ export const MAX_INVESTIGATION_MANIFEST_ARTIFACTS = MAX_SELECTED_FILES + 1;
6
+ export const MAX_INVESTIGATION_MANIFEST_ARTIFACT_BYTES = MAX_SELECTED_FILE_BYTES;
7
+ export const MAX_INVESTIGATION_MANIFEST_TOTAL_BYTES = MAX_SELECTED_FILE_TOTAL_BYTES + MAX_EDITABLE_CASE_OUTPUT_BYTES;
8
+ export const MAX_INVESTIGATION_MANIFEST_DOCUMENT_BYTES = 512 * 1024;
9
+ export const MAX_INVESTIGATION_PACKAGE_ENTRIES = MAX_INVESTIGATION_MANIFEST_ARTIFACTS + 1;
10
+ // Stored ZIP entries use 76 header bytes and two generated ASCII paths (at
11
+ // most 21 bytes each). 128 bytes per entry plus the 22-byte end record cover
12
+ // this writer's overhead without reducing the independently admitted payload.
13
+ export const MAX_INVESTIGATION_PACKAGE_BYTES = MAX_INVESTIGATION_MANIFEST_TOTAL_BYTES
14
+ + MAX_INVESTIGATION_MANIFEST_DOCUMENT_BYTES + MAX_INVESTIGATION_PACKAGE_ENTRIES * 128 + 22;
15
+ export const ENCRYPTED_INVESTIGATION_PACKAGE_MAGIC = 'WHOISLEUTH-ENCRYPTED\0';
16
+ export const ENCRYPTED_INVESTIGATION_PACKAGE_VERSION = 1;
17
+ // Magic, version byte, iteration count, salt, IV and plaintext byte count.
18
+ export const ENCRYPTED_INVESTIGATION_PACKAGE_HEADER_BYTES = ENCRYPTED_INVESTIGATION_PACKAGE_MAGIC.length + 1 + 4 + 16 + 12 + 4;
19
+ export const MAX_ENCRYPTED_INVESTIGATION_PACKAGE_BYTES = MAX_INVESTIGATION_PACKAGE_BYTES
20
+ + ENCRYPTED_INVESTIGATION_PACKAGE_HEADER_BYTES + 16;
21
+ export function hasEncryptedInvestigationPackagePrefix(bytes) {
22
+ return bytes.byteLength >= ENCRYPTED_INVESTIGATION_PACKAGE_MAGIC.length
23
+ && [...ENCRYPTED_INVESTIGATION_PACKAGE_MAGIC].every((character, index) => bytes[index] === character.charCodeAt(0));
24
+ }
@@ -22,27 +22,44 @@ export const BULK_MAIL_EXPOSURE_EXPORT_VERSION = 2;
22
22
  export const SUPPORTED_BULK_MAIL_EXPOSURE_EXPORT_VERSIONS = Object.freeze([BULK_MAIL_EXPOSURE_EXPORT_VERSION]);
23
23
  export const MAX_BULK_MAIL_EXPOSURE_ROWS = 2_000;
24
24
  export const BULK_REVIEW_MANIFEST_SCHEMA = 'whoisleuth.bulk-review-manifest';
25
- export const BULK_REVIEW_MANIFEST_VERSION = 2;
26
- export const SUPPORTED_BULK_REVIEW_MANIFEST_VERSIONS = Object.freeze([BULK_REVIEW_MANIFEST_VERSION]);
25
+ export const BULK_REVIEW_MANIFEST_VERSION = 3;
26
+ export const SUPPORTED_BULK_REVIEW_MANIFEST_VERSIONS = Object.freeze([2, BULK_REVIEW_MANIFEST_VERSION]);
27
27
  export const INVESTIGATION_CAPSULE_SCHEMA = 'whoisleuth.investigation-capsule';
28
- export const INVESTIGATION_CAPSULE_VERSION = 3;
28
+ export const INVESTIGATION_CAPSULE_VERSION = 4;
29
29
  export const PUBLIC_INVESTIGATION_CAPSULE_VERSION = 2;
30
30
  export const SUPPORTED_INVESTIGATION_CAPSULE_VERSIONS = Object.freeze([
31
31
  PUBLIC_INVESTIGATION_CAPSULE_VERSION,
32
+ 3,
32
33
  INVESTIGATION_CAPSULE_VERSION,
33
34
  ]);
34
35
  export const INVESTIGATION_CAPSULE_ANALYST_RECORDS_SCHEMA = 'whoisleuth.case-analyst-records';
35
36
  export const INVESTIGATION_CAPSULE_ANALYST_RECORDS_VERSION = 1;
36
37
  export const LOOKUP_INVESTIGATION_BRIEF_SCHEMA = 'whoisleuth.investigation-brief';
37
- export const LOOKUP_INVESTIGATION_BRIEF_VERSION = 2;
38
+ export const LOOKUP_INVESTIGATION_BRIEF_VERSION = 3;
38
39
  export const PUBLIC_LOOKUP_INVESTIGATION_BRIEF_VERSION = 1;
39
40
  export const SUPPORTED_LOOKUP_INVESTIGATION_BRIEF_VERSIONS = Object.freeze([
40
41
  PUBLIC_LOOKUP_INVESTIGATION_BRIEF_VERSION,
42
+ 2,
41
43
  LOOKUP_INVESTIGATION_BRIEF_VERSION,
42
44
  ]);
43
45
  export const MAX_LOOKUP_INVESTIGATION_BRIEF_BYTES = 128 * 1024;
44
46
  export const LOOKUP_ASSET_GRAPH_SCHEMA = 'whoisleuth.lookup-asset-graph';
45
- export const LOOKUP_ASSET_GRAPH_VERSION = 2;
47
+ export const LOOKUP_ASSET_GRAPH_VERSION = 3;
48
+ export const SUPPORTED_LOOKUP_ASSET_GRAPH_VERSIONS = Object.freeze([2, LOOKUP_ASSET_GRAPH_VERSION]);
49
+ // Offline-parser emergency bounds exceed every relationship that the bounded
50
+ // graph inputs can produce. The writer does not truncate nodes or edges.
51
+ export const MAX_LOOKUP_ASSET_NODES = 8_192;
52
+ export const MAX_LOOKUP_ASSET_EDGES = 8_192;
53
+ export const MAX_LOOKUP_ASSET_LIMITATIONS = 16;
54
+ export const MAX_LOOKUP_ASSET_INPUT_ROWS = 160;
55
+ export function investigationCapsuleContracts(version) {
56
+ switch (version) {
57
+ case 2: return { brief: 1, graph: 2 };
58
+ case 3: return { brief: 2, graph: 2 };
59
+ case INVESTIGATION_CAPSULE_VERSION: return { brief: LOOKUP_INVESTIGATION_BRIEF_VERSION, graph: LOOKUP_ASSET_GRAPH_VERSION };
60
+ default: return null;
61
+ }
62
+ }
46
63
  export const INVESTIGATION_DOMAIN_COMPATIBILITY_FACADES = Object.freeze([
47
64
  ['frontend/src/lib/analysis/acquisition-decision-packet.ts', 'packages/investigation/acquisition-decision-packet.mts'],
48
65
  ['frontend/src/lib/analysis/acquisition-due-diligence.ts', 'packages/investigation/acquisition-due-diligence.mts'],
@@ -89,20 +106,20 @@ export const BULK_REVIEW_MANIFEST_COMPATIBILITY = defineSchemaCompatibility({
89
106
  currentVersion: BULK_REVIEW_MANIFEST_VERSION, supportedVersions: SUPPORTED_BULK_REVIEW_MANIFEST_VERSIONS,
90
107
  acceptsUnversionedLegacy: false, futureVersionBehavior: 'reject', migration: 'read_only', writeSemantics: 'read_only',
91
108
  byteBudget: MAX_INVESTIGATION_PORTABLE_BYTES, owner: INVESTIGATION_PORTABILITY_CONTRACT_OWNER,
92
- note: 'Version 2 uses deterministic sorted-json-v2 integrity for one bounded Bulk review selection while excluding raw payloads, contacts, notes, and transient request state.',
109
+ note: 'Version 3 retains nullable batch, row and source observation times in one selected Bulk review with deterministic sorted-json-v2 integrity. Published version 2 remains readable without inventing absent source times. Raw payloads, contacts, notes and transient request state are excluded.',
93
110
  });
94
111
  export const INVESTIGATION_CAPSULE_COMPATIBILITY = defineSchemaCompatibility({
95
112
  id: 'export.investigation-capsule', kind: 'export', schema: INVESTIGATION_CAPSULE_SCHEMA,
96
113
  currentVersion: INVESTIGATION_CAPSULE_VERSION, supportedVersions: SUPPORTED_INVESTIGATION_CAPSULE_VERSIONS,
97
114
  acceptsUnversionedLegacy: false, futureVersionBehavior: 'reject', migration: 'read_only', writeSemantics: 'read_only',
98
115
  byteBudget: MAX_INVESTIGATION_PORTABLE_BYTES, owner: INVESTIGATION_PORTABILITY_CONTRACT_OWNER,
99
- note: 'Version 3 embeds investigation brief v2 with canonical Decision Facts; the v1.47.4 version 2 and v2 version 3 retain whole-capsule sorted-json-v2 integrity.',
116
+ note: 'Version 4 embeds brief v3 and graph v3 with projection coverage. Versions 2 and 3 remain readable with their exact embedded contracts and whole-capsule sorted-json-v2 integrity.',
100
117
  });
101
118
  export const LOOKUP_ASSET_GRAPH_COMPATIBILITY = defineSchemaCompatibility({
102
119
  id: 'derived.lookup-asset-graph', kind: 'derived', schema: LOOKUP_ASSET_GRAPH_SCHEMA,
103
120
  tier: 'durable_interchange',
104
- currentVersion: LOOKUP_ASSET_GRAPH_VERSION, supportedVersions: [LOOKUP_ASSET_GRAPH_VERSION],
105
- acceptsUnversionedLegacy: false, futureVersionBehavior: 'reject', migration: 'exact_current_only', writeSemantics: 'read_only',
121
+ currentVersion: LOOKUP_ASSET_GRAPH_VERSION, supportedVersions: SUPPORTED_LOOKUP_ASSET_GRAPH_VERSIONS,
122
+ acceptsUnversionedLegacy: false, futureVersionBehavior: 'reject', migration: 'read_only', writeSemantics: 'read_only',
106
123
  byteBudget: null, owner: INVESTIGATION_PORTABILITY_CONTRACT_OWNER,
107
124
  note: 'Bounded relationship projection embedded in investigation capsules; source attribution and incomplete evidence remain explicit.',
108
125
  });
@@ -120,7 +137,7 @@ export const LOOKUP_INVESTIGATION_BRIEF_COMPATIBILITY = defineSchemaCompatibilit
120
137
  currentVersion: LOOKUP_INVESTIGATION_BRIEF_VERSION, supportedVersions: SUPPORTED_LOOKUP_INVESTIGATION_BRIEF_VERSIONS,
121
138
  acceptsUnversionedLegacy: false, futureVersionBehavior: 'reject', migration: 'read_only', writeSemantics: 'none',
122
139
  byteBudget: MAX_LOOKUP_INVESTIGATION_BRIEF_BYTES, owner: INVESTIGATION_PORTABILITY_CONTRACT_OWNER,
123
- note: 'Version 2 replaces the public version-1 summary and decision-entry arrays with one bounded canonical Decision Fact projection; public version 1 remains structurally verified when embedded in supported capsule versions.',
140
+ note: 'Version 3 summarises the complete retained graph with canonical Decision Facts. Versions 1 and 2 retain their exact historical relationship bounds within supported capsules.',
124
141
  });
125
142
  export const INVESTIGATION_PORTABILITY_COMPATIBILITY = Object.freeze([
126
143
  ACQUISITION_DECISION_PACKET_COMPATIBILITY,
@@ -134,17 +151,24 @@ export const INVESTIGATION_PORTABILITY_COMPATIBILITY = Object.freeze([
134
151
  LOOKUP_INVESTIGATION_BRIEF_COMPATIBILITY,
135
152
  ]);
136
153
  const INVESTIGATION_FIXTURES = Object.freeze([
154
+ { id: 'investigation-capsule-v4', path: 'test/fixtures/investigation-portability/investigation-capsule-v4.json', bytes: 9005, sha256: 'a1c193ab16fe79cbd39182b4a554e09ce785f71c19aa8185f442a3cc06d54830', schema: INVESTIGATION_CAPSULE_SCHEMA, version: 4, role: 'current' },
155
+ { id: 'lookup-asset-graph-v3', path: 'test/fixtures/investigation-portability/lookup-asset-graph-v3.json', bytes: 8821, sha256: 'b37ce0d3cc18af95e09dc0e34b6a610a7e0fcbb9ab095b5da6a0d72c54d63c30', schema: LOOKUP_ASSET_GRAPH_SCHEMA, version: 3, role: 'current' },
156
+ { id: 'lookup-investigation-brief-v3', path: 'test/fixtures/investigation-portability/lookup-investigation-brief-v3.json', bytes: 1779, sha256: '49b78aa7b7c620ac521c329a30af990b33114e4122028e277e1419b352944e8b', schema: LOOKUP_INVESTIGATION_BRIEF_SCHEMA, version: 3, role: 'current' },
137
157
  { id: 'acquisition-decision-v2', path: 'test/fixtures/investigation-portability/acquisition-decision-v2.json', bytes: 6_278, sha256: '1c1b5c2fcea2886dae5661085f605d95317b89bb6d9774cb8967a754ef8165bf', schema: ACQUISITION_DECISION_PACKET_SCHEMA, version: 2, role: 'current' },
138
158
  { id: 'lookup-claim-passport-v1', path: 'test/fixtures/investigation-portability/lookup-claim-passport-v1.json', bytes: 2_011, sha256: 'dbf1c3975282e340b6639162a0555aeaed42c06abe92ed3b354409ce73c284a4', schema: LOOKUP_CLAIM_PASSPORT_SCHEMA, version: 1, role: 'current' },
139
159
  { id: 'bulk-domain-comparison-v4', path: 'test/fixtures/investigation-portability/bulk-domain-comparison-v4.json', bytes: 17_079, sha256: 'b718af34c8f5f1ba4427807cd149561bc1b7e605a6e74206a694beaee078cc2d', schema: BULK_DOMAIN_COMPARISON_SCHEMA, version: 4, role: 'current' },
140
160
  { id: 'bulk-mail-exposure-v2', path: 'test/fixtures/investigation-portability/bulk-mail-exposure-v2.json', bytes: 2_582, sha256: '9ff6c4c9f2dda4c5451daf8fb11e36a3ee15b513a09ad3c93515cc37df5d6660', schema: BULK_MAIL_EXPOSURE_SCHEMA, version: 2, role: 'current' },
141
- { id: 'bulk-review-manifest-v2', path: 'test/fixtures/investigation-portability/bulk-review-manifest-v2.json', bytes: 1_657, sha256: '1c90f4d0661357fc277360cc924f2464e463f9c8bd815a6b45408e95e1caf7d3', schema: BULK_REVIEW_MANIFEST_SCHEMA, version: 2, role: 'current' },
161
+ { id: 'bulk-review-manifest-v2', path: 'test/fixtures/investigation-portability/bulk-review-manifest-v2-current.json', bytes: 2_118, sha256: 'b4e209df9bc0e1c8be8a34d4f4aac486df9d0a4e1a1f750b9a21ab6e0d35b7b6', schema: BULK_REVIEW_MANIFEST_SCHEMA, version: 2, role: 'historical' },
162
+ { id: 'bulk-review-manifest-v3', path: 'test/fixtures/investigation-portability/bulk-review-manifest-v3.json', bytes: 2_422, sha256: '0ee1022140b8d19a92100806f0091c197be0ffec0faae808721c47d7cb96d9ad', schema: BULK_REVIEW_MANIFEST_SCHEMA, version: 3, role: 'current' },
163
+ // Synthetic integrity examples deliberately combine independently versioned
164
+ // contracts. Their application labels are not captured-release provenance;
165
+ // historical/current here describes the supported capsule schema only.
142
166
  { id: 'investigation-capsule-v2', path: 'test/fixtures/investigation-capsule-v2.json', bytes: 3_286, sha256: 'fb71cb243020d401e3653bb714f9de07a71b81622fcf83c0027599b763dda070', schema: INVESTIGATION_CAPSULE_SCHEMA, version: 2, role: 'historical' },
143
- { id: 'investigation-capsule-v3', path: 'test/fixtures/investigation-portability/investigation-capsule-v3.json', bytes: 6_132, sha256: '685123c8bd152ee1a8074441bcd56bbde9fb0304aa4a320ee9b37d02061ed82a', schema: INVESTIGATION_CAPSULE_SCHEMA, version: 3, role: 'current' },
144
- { id: 'lookup-asset-graph-v2', path: 'test/fixtures/investigation-portability/lookup-asset-graph-v2.json', bytes: 1_157, sha256: '455397a2b2987180d54657199e76c4dc175e01aed0fa83d945a6f3ec30bdcd49', schema: LOOKUP_ASSET_GRAPH_SCHEMA, version: 2, role: 'current' },
167
+ { id: 'investigation-capsule-v3', path: 'test/fixtures/investigation-portability/investigation-capsule-v3.json', bytes: 6_132, sha256: '685123c8bd152ee1a8074441bcd56bbde9fb0304aa4a320ee9b37d02061ed82a', schema: INVESTIGATION_CAPSULE_SCHEMA, version: 3, role: 'historical' },
168
+ { id: 'lookup-asset-graph-v2', path: 'test/fixtures/investigation-portability/lookup-asset-graph-v2.json', bytes: 1_157, sha256: '455397a2b2987180d54657199e76c4dc175e01aed0fa83d945a6f3ec30bdcd49', schema: LOOKUP_ASSET_GRAPH_SCHEMA, version: 2, role: 'historical' },
145
169
  { id: 'investigation-analyst-records-v1', path: 'test/fixtures/investigation-portability/investigation-analyst-records-v1.json', bytes: 624, sha256: '0730116afef29e52b448e150f6755e89ec1189c2fbc6ec4ef8cfc4b41f677456', schema: INVESTIGATION_CAPSULE_ANALYST_RECORDS_SCHEMA, version: 1, role: 'current' },
146
170
  { id: 'lookup-investigation-brief-v1', path: 'test/fixtures/investigation-portability/lookup-investigation-brief-v1.json', bytes: 1_651, sha256: 'bc21c886671370659149c3e494c7e6061302569308823c69ef96f0c601dadbab', schema: LOOKUP_INVESTIGATION_BRIEF_SCHEMA, version: 1, role: 'historical' },
147
- { id: 'lookup-investigation-brief-v2', path: 'test/fixtures/investigation-portability/lookup-investigation-brief-v2.json', bytes: 1_724, sha256: '25d0133318df8daa00848e6a2c03913478beb30818a4959298ec1500b6aaa0fd', schema: LOOKUP_INVESTIGATION_BRIEF_SCHEMA, version: 2, role: 'current' },
171
+ { id: 'lookup-investigation-brief-v2', path: 'test/fixtures/investigation-portability/lookup-investigation-brief-v2.json', bytes: 1_724, sha256: '25d0133318df8daa00848e6a2c03913478beb30818a4959298ec1500b6aaa0fd', schema: LOOKUP_INVESTIGATION_BRIEF_SCHEMA, version: 2, role: 'historical' },
148
172
  ].map((fixture) => Object.freeze({
149
173
  ...fixture,
150
174
  contentDigestSha256: null,
@@ -182,7 +206,7 @@ const INVESTIGATION_CONTRACTS = Object.freeze([
182
206
  ...SUPPORTED_BULK_MAIL_EXPOSURE_EXPORT_VERSIONS.map((version) => investigationContract({ compatibilityId: BULK_MAIL_EXPOSURE_COMPATIBILITY.id, schema: BULK_MAIL_EXPOSURE_SCHEMA, version, currentVersion: BULK_MAIL_EXPOSURE_EXPORT_VERSION, canonicalisation: 'sorted-json-v2', byteBudget: MAX_INVESTIGATION_PORTABLE_BYTES })),
183
207
  ...SUPPORTED_BULK_REVIEW_MANIFEST_VERSIONS.map((version) => investigationContract({ compatibilityId: BULK_REVIEW_MANIFEST_COMPATIBILITY.id, schema: BULK_REVIEW_MANIFEST_SCHEMA, version, currentVersion: BULK_REVIEW_MANIFEST_VERSION, canonicalisation: 'sorted-json-v2', byteBudget: MAX_INVESTIGATION_PORTABLE_BYTES })),
184
208
  ...SUPPORTED_INVESTIGATION_CAPSULE_VERSIONS.map((version) => investigationContract({ compatibilityId: INVESTIGATION_CAPSULE_COMPATIBILITY.id, schema: INVESTIGATION_CAPSULE_SCHEMA, version, currentVersion: INVESTIGATION_CAPSULE_VERSION, canonicalisation: 'sorted-json-v2', byteBudget: MAX_INVESTIGATION_PORTABLE_BYTES })),
185
- investigationContract({ compatibilityId: LOOKUP_ASSET_GRAPH_COMPATIBILITY.id, schema: LOOKUP_ASSET_GRAPH_SCHEMA, version: LOOKUP_ASSET_GRAPH_VERSION, currentVersion: LOOKUP_ASSET_GRAPH_VERSION, canonicalisation: null, byteBudget: null }),
209
+ ...SUPPORTED_LOOKUP_ASSET_GRAPH_VERSIONS.map((version) => investigationContract({ compatibilityId: LOOKUP_ASSET_GRAPH_COMPATIBILITY.id, schema: LOOKUP_ASSET_GRAPH_SCHEMA, version, currentVersion: LOOKUP_ASSET_GRAPH_VERSION, canonicalisation: null, byteBudget: null })),
186
210
  investigationContract({ compatibilityId: INVESTIGATION_CAPSULE_ANALYST_RECORDS_COMPATIBILITY.id, schema: INVESTIGATION_CAPSULE_ANALYST_RECORDS_SCHEMA, version: INVESTIGATION_CAPSULE_ANALYST_RECORDS_VERSION, currentVersion: INVESTIGATION_CAPSULE_ANALYST_RECORDS_VERSION, canonicalisation: null, byteBudget: null }),
187
211
  ...SUPPORTED_LOOKUP_INVESTIGATION_BRIEF_VERSIONS.map((version) => investigationContract({ compatibilityId: LOOKUP_INVESTIGATION_BRIEF_COMPATIBILITY.id, schema: LOOKUP_INVESTIGATION_BRIEF_SCHEMA, version, currentVersion: LOOKUP_INVESTIGATION_BRIEF_VERSION, canonicalisation: null, byteBudget: MAX_LOOKUP_INVESTIGATION_BRIEF_BYTES })),
188
212
  ]);
@@ -203,11 +227,12 @@ const INVESTIGATION_SHAPES = Object.freeze([
203
227
  investigationShape('investigation.domain-comparison.v4', BULK_DOMAIN_COMPARISON_SCHEMA, [...SUPPORTED_BULK_DOMAIN_COMPARISON_EXPORT_VERSIONS], ['schema', 'version', 'generatedAt', 'comparison', 'integrity'], 'preserve_signed_document'),
204
228
  investigationShape('investigation.mail-exposure.v2', BULK_MAIL_EXPOSURE_SCHEMA, [...SUPPORTED_BULK_MAIL_EXPOSURE_EXPORT_VERSIONS], ['schema', 'version', 'report', 'integrity'], 'preserve_signed_document'),
205
229
  investigationShape('investigation.bulk-review.v2', BULK_REVIEW_MANIFEST_SCHEMA, [...SUPPORTED_BULK_REVIEW_MANIFEST_VERSIONS], ['schema', 'version', 'generatedAt', 'observedAt', 'lookupProfile', 'selection', 'view', 'rows', 'limitations', 'integrity'], 'preserve_signed_document'),
206
- investigationShape('investigation.capsule.v2-v3', INVESTIGATION_CAPSULE_SCHEMA, [...SUPPORTED_INVESTIGATION_CAPSULE_VERSIONS], ['schema', 'schemaVersion', 'generatedAt', 'application', 'target', 'sourceContracts', 'investigationBrief', 'graphSnapshot', 'analystRecords', 'limitations', 'integrity'], 'preserve_signed_document'),
207
- investigationShape('investigation.asset-graph.v2', LOOKUP_ASSET_GRAPH_SCHEMA, [LOOKUP_ASSET_GRAPH_VERSION], ['version', 'targetId', 'nodes', 'edges', 'sources', 'truncated', 'limitations'], 'preserve_document'),
230
+ investigationShape('investigation.capsule.v2-v4', INVESTIGATION_CAPSULE_SCHEMA, [...SUPPORTED_INVESTIGATION_CAPSULE_VERSIONS], ['schema', 'schemaVersion', 'generatedAt', 'application', 'target', 'sourceContracts', 'investigationBrief', 'graphSnapshot', 'analystRecords', 'limitations', 'integrity'], 'preserve_signed_document'),
231
+ investigationShape('investigation.asset-graph.v2', LOOKUP_ASSET_GRAPH_SCHEMA, [2], ['version', 'targetId', 'nodes', 'edges', 'sources', 'truncated', 'limitations'], 'preserve_document'),
232
+ investigationShape('investigation.asset-graph.v3', LOOKUP_ASSET_GRAPH_SCHEMA, [LOOKUP_ASSET_GRAPH_VERSION], ['version', 'targetId', 'nodes', 'edges', 'sources', 'coverage', 'truncated', 'limitations'], 'preserve_document'),
208
233
  investigationShape('investigation.analyst-records.v1', INVESTIGATION_CAPSULE_ANALYST_RECORDS_SCHEMA, [INVESTIGATION_CAPSULE_ANALYST_RECORDS_VERSION], ['caseId', 'status', 'disposition', 'decisions', 'assertions'], 'preserve_document'),
209
234
  investigationShape('investigation.brief.v1', LOOKUP_INVESTIGATION_BRIEF_SCHEMA, [PUBLIC_LOOKUP_INVESTIGATION_BRIEF_VERSION], ['schema', 'schemaVersion', 'generatedAt', 'target', 'targetType', 'task', 'taskLabel', 'question', 'summary', 'observation', 'verifiedFacts', 'contradictions', 'unknowns', 'nextActions', 'relationships', 'limitations'], 'preserve_document'),
210
- investigationShape('investigation.brief.v2', LOOKUP_INVESTIGATION_BRIEF_SCHEMA, [LOOKUP_INVESTIGATION_BRIEF_VERSION], ['schema', 'schemaVersion', 'generatedAt', 'target', 'targetType', 'task', 'taskLabel', 'question', 'summary', 'observation', 'decisionFacts', 'relationships', 'limitations'], 'preserve_document'),
235
+ investigationShape('investigation.brief.v2-v3', LOOKUP_INVESTIGATION_BRIEF_SCHEMA, [2, LOOKUP_INVESTIGATION_BRIEF_VERSION], ['schema', 'schemaVersion', 'generatedAt', 'target', 'targetType', 'task', 'taskLabel', 'question', 'summary', 'observation', 'decisionFacts', 'relationships', 'limitations'], 'preserve_document'),
211
236
  ]);
212
237
  const investigationConsumerCommon = Object.freeze({
213
238
  expiryPolicyId: 'investigation.expiry.not-applicable',
@@ -223,7 +248,6 @@ export const INVESTIGATION_PORTABILITY_LIFECYCLE_FAMILY = defineSchemaLifecycleF
223
248
  contracts: INVESTIGATION_CONTRACTS,
224
249
  fixtures: INVESTIGATION_FIXTURES,
225
250
  metadata: {
226
- metadataVersion: 3,
227
251
  enforcement: 'declarative_only',
228
252
  shapes: INVESTIGATION_SHAPES,
229
253
  boundProfiles: [
@@ -242,6 +266,11 @@ export const INVESTIGATION_PORTABILITY_LIFECYCLE_FAMILY = defineSchemaLifecycleF
242
266
  { id: 'investigation.derived.bounds', bounds: [
243
267
  { id: 'projection-items', path: '$', phase: 'normalised', unit: 'items', minimum: 0, maximum: 2_000, handling: 'truncate' },
244
268
  ] },
269
+ { id: 'investigation.graph.bounds', bounds: [
270
+ { id: 'nodes', path: 'nodes', phase: 'normalised', unit: 'items', minimum: 0, maximum: MAX_LOOKUP_ASSET_NODES, handling: 'reject' },
271
+ { id: 'edges', path: 'edges', phase: 'normalised', unit: 'items', minimum: 0, maximum: MAX_LOOKUP_ASSET_EDGES, handling: 'reject' },
272
+ { id: 'input-coverage', path: 'coverage.inputs', phase: 'normalised', unit: 'items', minimum: 0, maximum: MAX_LOOKUP_ASSET_INPUT_ROWS, handling: 'reject' },
273
+ ] },
245
274
  ],
246
275
  hooks: [
247
276
  { id: 'investigation.offline.structure', role: 'structure_validator', runtime: 'cli', module: 'cli/offline-artifact-validation.mts', exportName: 'validateOfflineArtifactStructure' },
@@ -258,7 +287,7 @@ export const INVESTIGATION_PORTABILITY_LIFECYCLE_FAMILY = defineSchemaLifecycleF
258
287
  { id: 'investigation.brief.build', role: 'builder', runtime: 'shared', module: 'packages/investigation/lookup-investigation-brief.mts', exportName: 'buildLookupInvestigationBrief' },
259
288
  ],
260
289
  serialisationProfiles: [{
261
- id: 'investigation.capsule.json.v2-v3',
290
+ id: 'investigation.capsule.json.v2-v4',
262
291
  schema: INVESTIGATION_CAPSULE_SCHEMA,
263
292
  versions: [...SUPPORTED_INVESTIGATION_CAPSULE_VERSIONS],
264
293
  mediaType: 'application/json',
@@ -289,7 +318,7 @@ export const INVESTIGATION_PORTABILITY_LIFECYCLE_FAMILY = defineSchemaLifecycleF
289
318
  { schema: INVESTIGATION_CAPSULE_SCHEMA, versions: [...SUPPORTED_INVESTIGATION_CAPSULE_VERSIONS], mode: 'direct' },
290
319
  ],
291
320
  emittedContract: null,
292
- shapeIds: ['investigation.acquisition.v2', 'investigation.claim-passport.v1', 'investigation.domain-comparison.v4', 'investigation.mail-exposure.v2', 'investigation.bulk-review.v2', 'investigation.capsule.v2-v3'],
321
+ shapeIds: ['investigation.acquisition.v2', 'investigation.claim-passport.v1', 'investigation.domain-comparison.v4', 'investigation.mail-exposure.v2', 'investigation.bulk-review.v2', 'investigation.capsule.v2-v4'],
293
322
  boundProfileIds: ['investigation.portable.bounds', 'investigation.passport.bounds'],
294
323
  hookIds: ['investigation.offline.structure', 'investigation.offline.verify'],
295
324
  serialisationProfileId: null,
@@ -298,15 +327,15 @@ export const INVESTIGATION_PORTABILITY_LIFECYCLE_FAMILY = defineSchemaLifecycleF
298
327
  {
299
328
  id: 'investigation.consumer.capsule-compose', plane: 'shared', operation: 'compose-and-verify-capsule',
300
329
  acceptedContracts: [
301
- { schema: LOOKUP_ASSET_GRAPH_SCHEMA, versions: [LOOKUP_ASSET_GRAPH_VERSION], mode: 'embedded' },
330
+ { schema: LOOKUP_ASSET_GRAPH_SCHEMA, versions: [...SUPPORTED_LOOKUP_ASSET_GRAPH_VERSIONS], mode: 'embedded' },
302
331
  { schema: INVESTIGATION_CAPSULE_ANALYST_RECORDS_SCHEMA, versions: [INVESTIGATION_CAPSULE_ANALYST_RECORDS_VERSION], mode: 'embedded' },
303
332
  { schema: LOOKUP_INVESTIGATION_BRIEF_SCHEMA, versions: [...SUPPORTED_LOOKUP_INVESTIGATION_BRIEF_VERSIONS], mode: 'embedded' },
304
333
  ],
305
334
  emittedContract: { schema: INVESTIGATION_CAPSULE_SCHEMA, version: INVESTIGATION_CAPSULE_VERSION },
306
- shapeIds: ['investigation.asset-graph.v2', 'investigation.analyst-records.v1', 'investigation.brief.v1', 'investigation.brief.v2', 'investigation.capsule.v2-v3'],
307
- boundProfileIds: ['investigation.portable.bounds', 'investigation.brief.bounds', 'investigation.derived.bounds'],
335
+ shapeIds: ['investigation.asset-graph.v2', 'investigation.asset-graph.v3', 'investigation.analyst-records.v1', 'investigation.brief.v1', 'investigation.brief.v2-v3', 'investigation.capsule.v2-v4'],
336
+ boundProfileIds: ['investigation.portable.bounds', 'investigation.brief.bounds', 'investigation.derived.bounds', 'investigation.graph.bounds'],
308
337
  hookIds: ['investigation.capsule.build', 'investigation.capsule.verify', 'investigation.capsule.serialise'],
309
- serialisationProfileId: 'investigation.capsule.json.v2-v3',
338
+ serialisationProfileId: 'investigation.capsule.json.v2-v4',
310
339
  privacyProfileId: 'investigation.privacy.output', retentionEffect: 'operator_controlled_output', ...investigationConsumerCommon,
311
340
  },
312
341
  ...[
@@ -315,9 +344,9 @@ export const INVESTIGATION_PORTABILITY_LIFECYCLE_FAMILY = defineSchemaLifecycleF
315
344
  ['investigation.consumer.domain-comparison-build', BULK_DOMAIN_COMPARISON_SCHEMA, BULK_DOMAIN_COMPARISON_EXPORT_VERSION, 'investigation.domain-comparison.v4', 'investigation.domain-comparison.build', 'investigation.portable.bounds'],
316
345
  ['investigation.consumer.mail-exposure-build', BULK_MAIL_EXPOSURE_SCHEMA, BULK_MAIL_EXPOSURE_EXPORT_VERSION, 'investigation.mail-exposure.v2', 'investigation.mail-exposure.build', 'investigation.portable.bounds'],
317
346
  ['investigation.consumer.bulk-review-build', BULK_REVIEW_MANIFEST_SCHEMA, BULK_REVIEW_MANIFEST_VERSION, 'investigation.bulk-review.v2', 'investigation.bulk-review.build', 'investigation.portable.bounds'],
318
- ['investigation.consumer.graph-build', LOOKUP_ASSET_GRAPH_SCHEMA, LOOKUP_ASSET_GRAPH_VERSION, 'investigation.asset-graph.v2', 'investigation.graph.build', 'investigation.derived.bounds'],
347
+ ['investigation.consumer.graph-build', LOOKUP_ASSET_GRAPH_SCHEMA, LOOKUP_ASSET_GRAPH_VERSION, 'investigation.asset-graph.v3', 'investigation.graph.build', 'investigation.graph.bounds'],
319
348
  ['investigation.consumer.analyst-records-project', INVESTIGATION_CAPSULE_ANALYST_RECORDS_SCHEMA, INVESTIGATION_CAPSULE_ANALYST_RECORDS_VERSION, 'investigation.analyst-records.v1', 'investigation.capsule.build', 'investigation.derived.bounds'],
320
- ['investigation.consumer.brief-build', LOOKUP_INVESTIGATION_BRIEF_SCHEMA, LOOKUP_INVESTIGATION_BRIEF_VERSION, 'investigation.brief.v2', 'investigation.brief.build', 'investigation.brief.bounds'],
349
+ ['investigation.consumer.brief-build', LOOKUP_INVESTIGATION_BRIEF_SCHEMA, LOOKUP_INVESTIGATION_BRIEF_VERSION, 'investigation.brief.v2-v3', 'investigation.brief.build', 'investigation.brief.bounds'],
321
350
  ].map(([id, schema, version, shapeId, hookId, boundProfileId]) => ({
322
351
  id, plane: 'shared', operation: 'build-current-projection', acceptedContracts: [],
323
352
  emittedContract: { schema, version }, shapeIds: [shapeId], boundProfileIds: [boundProfileId],
@@ -0,0 +1,16 @@
1
+ export const CLI_INVESTIGATION_RUN_SCHEMA = 'whoisleuth.cli.investigation-run';
2
+ export const CLI_INVESTIGATION_RUN_VERSION = 3;
3
+ export const SUPPORTED_CLI_INVESTIGATION_RUN_VERSIONS = Object.freeze([1, 2, 3]);
4
+ export const MAX_INVESTIGATION_RUN_BYTES = 24 * 1024 * 1024;
5
+ export const MAX_INVESTIGATION_RUN_SELECTIONS = 16;
6
+ export const MAX_INVESTIGATION_RUN_SELECTION_LENGTH = 1_024;
7
+ // Version 3 retains partial observations and content-bound step inputs. Earlier
8
+ // readers must reject these checkpoints rather than recollect or reinterpret them.
9
+ export const INVESTIGATION_RUN_STATES = Object.freeze([
10
+ 'complete',
11
+ 'partial',
12
+ 'awaiting_network_approval',
13
+ 'awaiting_analyst_selection',
14
+ 'awaiting_review_confirmation',
15
+ 'step_failed',
16
+ ]);
@@ -0,0 +1,34 @@
1
+ export function ipv6Groups(value) {
2
+ const address = addressValue(value);
3
+ if (address?.family !== 6)
4
+ return [];
5
+ return Array.from({ length: 8 }, (_, index) => ((address.value >> BigInt((7 - index) * 16)) & 0xffffn).toString(16).padStart(4, '0'));
6
+ }
7
+ export function addressValue(value) {
8
+ if (typeof value !== 'string' || value.length > 96)
9
+ return null;
10
+ if (/^\d{1,3}(?:\.\d{1,3}){3}$/u.test(value)) {
11
+ const octets = value.split('.').map(Number);
12
+ return octets.every(octet => octet >= 0 && octet <= 255)
13
+ ? { family: 4, value: octets.reduce((total, octet) => (total << 8) + octet, 0) >>> 0 } : null;
14
+ }
15
+ if (!value.includes(':') || !/^[0-9a-f:.]+$/iu.test(value))
16
+ return null;
17
+ try {
18
+ const hostname = new URL(`https://[${value}]/`).hostname;
19
+ if (!hostname.startsWith('[') || !hostname.endsWith(']'))
20
+ return null;
21
+ const pieces = hostname.slice(1, -1).split('::');
22
+ const left = pieces[0] ? pieces[0].split(':') : [];
23
+ const right = pieces[1] ? pieces[1].split(':') : [];
24
+ const missing = 8 - left.length - right.length;
25
+ if (pieces.length > 2 || pieces.length === 1 && missing !== 0 || pieces.length === 2 && missing < 1
26
+ || [...left, ...right].some(part => !/^[0-9a-f]{1,4}$/iu.test(part)))
27
+ return null;
28
+ const parts = [...left, ...Array.from({ length: missing }, () => '0'), ...right];
29
+ return { family: 6, value: parts.reduce((total, part) => (total << 16n) + BigInt(`0x${part}`), 0n) };
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ }
@@ -0,0 +1,35 @@
1
+ import { defineSchemaCompatibility } from "./schema-compatibility.mjs";
2
+ export const LOOKUP_EVIDENCE_SCHEMA = 'whoisleuth.lookup-evidence';
3
+ export const V1_PUBLIC_LOOKUP_EVIDENCE_SCHEMA_VERSION = 26;
4
+ export const PUBLISHED_V2_LOOKUP_EVIDENCE_SCHEMA_VERSION = 27;
5
+ export const REGISTRAR_STANDING_LOOKUP_EVIDENCE_SCHEMA_VERSION = 28;
6
+ export const LATEST_PUBLIC_LOOKUP_EVIDENCE_SCHEMA_VERSION = REGISTRAR_STANDING_LOOKUP_EVIDENCE_SCHEMA_VERSION;
7
+ export const HOSTNAME_SCOPED_LOOKUP_EVIDENCE_SCHEMA_VERSION = 29;
8
+ export const PRIVACY_MINIMIZED_LOOKUP_EVIDENCE_SCHEMA_VERSION = PUBLISHED_V2_LOOKUP_EVIDENCE_SCHEMA_VERSION;
9
+ export const LOOKUP_EVIDENCE_SCHEMA_VERSION = HOSTNAME_SCOPED_LOOKUP_EVIDENCE_SCHEMA_VERSION;
10
+ export const SUPPORTED_LOOKUP_EVIDENCE_SCHEMA_VERSIONS = Object.freeze([
11
+ V1_PUBLIC_LOOKUP_EVIDENCE_SCHEMA_VERSION,
12
+ PUBLISHED_V2_LOOKUP_EVIDENCE_SCHEMA_VERSION,
13
+ LATEST_PUBLIC_LOOKUP_EVIDENCE_SCHEMA_VERSION,
14
+ LOOKUP_EVIDENCE_SCHEMA_VERSION,
15
+ ]);
16
+ export const LOOKUP_EVIDENCE_PORTABLE_MAX_BYTES = 5 * 1024 * 1024;
17
+ export const LOOKUP_EVIDENCE_PORTABLE_MAX_ENTRIES = 20_000;
18
+ export const LOOKUP_EVIDENCE_PORTABLE_MAX_DEPTH = 24;
19
+ export const LOOKUP_EVIDENCE_PORTABLE_MAX_ARRAY_ITEMS = 10_000;
20
+ export const LOOKUP_EVIDENCE_PORTABLE_MAX_KEY_LENGTH = 256;
21
+ export const LOOKUP_EVIDENCE_PORTABLE_MAX_STRING_LENGTH = 1024 * 1024;
22
+ export const LOOKUP_EVIDENCE_COMPATIBILITY = defineSchemaCompatibility({
23
+ id: 'export.lookup-evidence',
24
+ kind: 'export',
25
+ schema: LOOKUP_EVIDENCE_SCHEMA,
26
+ currentVersion: LOOKUP_EVIDENCE_SCHEMA_VERSION,
27
+ supportedVersions: SUPPORTED_LOOKUP_EVIDENCE_SCHEMA_VERSIONS,
28
+ acceptsUnversionedLegacy: false,
29
+ futureVersionBehavior: 'reject',
30
+ migration: 'read_only',
31
+ writeSemantics: 'read_only',
32
+ byteBudget: LOOKUP_EVIDENCE_PORTABLE_MAX_BYTES,
33
+ owner: 'packages/contracts/lookup-evidence.mts',
34
+ note: 'Exact published versions 26, 27 and 28 remain replayable with their original collection scope. Version 29 identifies the hostname used for supporting DNS, TLS and web observations; registration authority remains separate.',
35
+ });
@@ -10,7 +10,7 @@ export const CLI_COMPARISON_LEDGER_SCHEMA = 'whoisleuth.cli.comparison-ledger';
10
10
  export const CLI_COMPARISON_LEDGER_VERSION = 1;
11
11
  export const MAX_RETAINED_ARTIFACT_DIFF_BYTES = 8 * 1024 * 1024;
12
12
  export const RELATIONSHIP_EVIDENCE_SCHEMA = 'whoisleuth.relationship-evidence';
13
- export const RELATIONSHIP_EVIDENCE_VERSION = 2;
13
+ export const RELATIONSHIP_EVIDENCE_VERSION = 3;
14
14
  export const TLS_RELATIONSHIP_PROFILE_VERSION = 2;
15
15
  export const SUPPORTED_TLS_RELATIONSHIP_PROFILE_VERSIONS = Object.freeze([TLS_RELATIONSHIP_PROFILE_VERSION]);
16
16
  export const OFFLINE_COMPARISON_COMPATIBILITY_FACADES = Object.freeze([
@@ -114,7 +114,6 @@ export const OFFLINE_COMPARISON_LIFECYCLE_FAMILY = defineSchemaLifecycleFamily({
114
114
  contracts: OFFLINE_COMPARISON_CONTRACTS,
115
115
  fixtures: OFFLINE_COMPARISON_FIXTURES,
116
116
  metadata: {
117
- metadataVersion: 4,
118
117
  enforcement: 'declarative_only',
119
118
  shapes: [
120
119
  offlineOutputShape('offline-comparison.page-output.current', CLI_PAGE_COMPARE_SCHEMA, [4], ['schema', 'version', 'generatedAt', 'left', 'right', 'page', 'technology', 'tls', 'limitations']),
@@ -0,0 +1,15 @@
1
+ export const PAGE_FINGERPRINT_VERSION = 2;
2
+ export const PAGE_FINGERPRINT_PARSERS = Object.freeze({
3
+ 1: 'static-tag-sequence-v1',
4
+ [PAGE_FINGERPRINT_VERSION]: 'html-tree-v2',
5
+ });
6
+ export const MAX_STATIC_HTML_TAGS = 8_192;
7
+ export const MAX_STATIC_HTML_NODES = MAX_STATIC_HTML_TAGS * 3;
8
+ export const MAX_STATIC_HTML_DEPTH = 512;
9
+ // Each admitted element emits a start and end event; other nodes emit at most
10
+ // one event. The parser admits three mandatory document elements additionally.
11
+ export const MAX_STATIC_HTML_TREE_EVENTS = MAX_STATIC_HTML_NODES + MAX_STATIC_HTML_TAGS + 3;
12
+ export const PAGE_FINGERPRINT_TOKEN_LIMITS = Object.freeze({
13
+ 1: 4_096,
14
+ [PAGE_FINGERPRINT_VERSION]: MAX_STATIC_HTML_TREE_EVENTS,
15
+ });
@@ -373,7 +373,6 @@ export const RISK_CALIBRATION_SCHEMA_LIFECYCLE = defineSchemaLifecycleFamily({
373
373
  },
374
374
  ],
375
375
  metadata: {
376
- metadataVersion: 4,
377
376
  enforcement: 'declarative_only',
378
377
  shapes: [
379
378
  ...[RISK_CALIBRATION_DATASET_VERSION].map((version) => ({
@@ -1,7 +1,7 @@
1
1
  const LOCAL_SCHEMA_IDENTIFIER_SOURCE = String.raw `whoisleuth\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)*`;
2
2
  const LOCAL_SCHEMA_IDENTIFIER_PATTERN = new RegExp(`^${LOCAL_SCHEMA_IDENTIFIER_SOURCE}$`, 'u');
3
3
  const COMPATIBILITY_SCHEMA_IDENTIFIER_PATTERN = /^[-a-z0-9.]+$/u;
4
- const CONTRACT_KINDS = new Set(['browser_store', 'tab_store', 'hosted_store', 'export', 'cli_document', 'derived']);
4
+ const CONTRACT_KINDS = new Set(['browser_store', 'tab_store', 'hosted_store', 'filesystem_store', 'export', 'cli_document', 'derived']);
5
5
  const COMPATIBILITY_TIERS = new Set(['durable_interchange', 'published_output', 'internal']);
6
6
  const FUTURE_VERSION_BEHAVIORS = new Set(['reject', 'preserve_without_write', 'discard', 'not_applicable']);
7
7
  const MIGRATION_BEHAVIORS = new Set(['normalize_to_current', 'exact_current_only', 'read_only', 'none']);