@slicedearth/whoisleuth-cli 2.4.0 → 2.6.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 (340) hide show
  1. package/DISCLOSURE +1 -1
  2. package/NOTICE +2 -2
  3. package/cli/argument-values.mjs +55 -0
  4. package/cli/arguments.mjs +24 -470
  5. package/cli/artifact-validation/case-response.mjs +67 -9
  6. package/cli/artifact-validation/lookup-evidence.mjs +18 -4
  7. package/cli/artifact-validation/signed-review.mjs +61 -64
  8. package/cli/artifact-verify.mjs +11 -2
  9. package/cli/assurance-arguments.mjs +56 -0
  10. package/cli/assurance-command-definitions.mjs +179 -0
  11. package/cli/assurance-command-runner.mjs +1 -1
  12. package/cli/bounded-json.mjs +1 -1
  13. package/cli/bulk-checkpoint.mjs +1 -1
  14. package/cli/bulk-command-runner.mjs +6 -8
  15. package/cli/bulk.mjs +2 -3
  16. package/cli/case-command.mjs +99 -6
  17. package/cli/case-pack.mjs +19 -4
  18. package/cli/ci-report.mjs +6 -3
  19. package/cli/collection-arguments.mjs +123 -0
  20. package/cli/collection-command-definitions.mjs +330 -0
  21. package/cli/collection-preflight.mjs +8 -1
  22. package/cli/command-argument-grammar.mjs +8 -210
  23. package/cli/command-definition.mjs +330 -0
  24. package/cli/command-reference.mjs +55 -1388
  25. package/cli/command-seeds.mjs +18 -0
  26. package/cli/compare.mjs +1 -1
  27. package/cli/config-profile.mjs +1 -1
  28. package/cli/ct-event-intake.mjs +4 -4
  29. package/cli/discover.mjs +2 -2
  30. package/cli/discovery-observation-snapshot.mjs +2 -2
  31. package/cli/discovery-scan-command-runner.mjs +7 -9
  32. package/cli/discovery-scan.mjs +1 -1
  33. package/cli/discovery-snapshot.mjs +2 -2
  34. package/cli/discovery-workflow.mjs +4 -4
  35. package/cli/domain-control-monitor.mjs +6 -6
  36. package/cli/domain-control-observations.mjs +2 -1
  37. package/cli/domain-feed.mjs +61 -0
  38. package/cli/errors.mjs +5 -6
  39. package/cli/evidence-arguments.mjs +46 -0
  40. package/cli/evidence-command-definitions.mjs +69 -0
  41. package/cli/evidence-command-runner.mjs +2 -3
  42. package/cli/evidence-signing.mjs +3 -2
  43. package/cli/external-observation-mapping.mjs +2 -2
  44. package/cli/formatters/json.mjs +1 -13
  45. package/cli/formatters/terminal-command-formats.mjs +6 -0
  46. package/cli/formatters/terminal-lookup.mjs +6 -0
  47. package/cli/history-arguments.mjs +39 -0
  48. package/cli/history-command-definitions.mjs +88 -0
  49. package/cli/indicator-set-command.mjs +32 -0
  50. package/cli/inline-command-families.mjs +6 -47
  51. package/cli/inline-command-runner.mjs +4 -31
  52. package/cli/input.mjs +2 -2
  53. package/cli/intake-command.mjs +147 -0
  54. package/cli/interchange-report.mjs +4 -2
  55. package/cli/investigation-artifacts.mjs +3 -4
  56. package/cli/investigation-package-review.mjs +2 -0
  57. package/cli/investigation-plan.mjs +32 -154
  58. package/cli/investigation-recipes.mjs +177 -0
  59. package/cli/investigation-run.mjs +78 -12
  60. package/cli/local-mmdb-review.mjs +220 -17
  61. package/cli/lookalike-calibration.mjs +1 -1
  62. package/cli/lookup-brief.mjs +2 -2
  63. package/cli/lookup-command-runner.mjs +19 -35
  64. package/cli/lookup-diff.mjs +10 -9
  65. package/cli/lookup-plan.mjs +10 -14
  66. package/cli/lookup-reconcile.mjs +6 -5
  67. package/cli/lookup-timeline.mjs +7 -6
  68. package/cli/mail-header-review.mjs +24 -247
  69. package/cli/mail-review.mjs +9 -7
  70. package/cli/network-arguments.mjs +51 -0
  71. package/cli/network-command-definitions.mjs +130 -0
  72. package/cli/network-command-runner.mjs +43 -31
  73. package/cli/offline-evidence-review.mjs +85 -6
  74. package/cli/posture.mjs +1 -1
  75. package/cli/registry-fixture-scaffold.mjs +1 -1
  76. package/cli/retained-artifact-diff.mjs +8 -7
  77. package/cli/review-arguments.mjs +132 -0
  78. package/cli/review-command-definitions.mjs +279 -0
  79. package/cli/review-command-runner.mjs +72 -159
  80. package/cli/risk-calibration.mjs +14 -12
  81. package/cli/runner.mjs +20 -32
  82. package/cli/saved-lookup.mjs +2 -2
  83. package/cli/selected-input-worker.mjs +73 -0
  84. package/cli/sharing-review.mjs +5 -64
  85. package/cli/source-reliability.mjs +2 -2
  86. package/cli/support-arguments.mjs +49 -0
  87. package/cli/support-command-definitions.mjs +209 -0
  88. package/cli/support-command-runner.mjs +4 -4
  89. package/cli/watchlist-review.mjs +43 -0
  90. package/cli/workflow-arguments.mjs +100 -0
  91. package/cli/workflow-command-definitions.mjs +103 -0
  92. package/cli/workflow-command-runner.mjs +40 -1
  93. package/docs/cli-reference.md +27 -0
  94. package/docs/cli.md +322 -4
  95. package/docs/contextual-reviews.md +178 -0
  96. package/docs/infrastructure-observations.md +126 -0
  97. package/docs/offline-intake-context.md +213 -0
  98. package/lib/analyst-taxonomy.mjs +2 -40
  99. package/lib/application-version.mjs +1 -1
  100. package/lib/availability.mjs +13 -789
  101. package/lib/bounded-contract-normalizers.mjs +4 -0
  102. package/lib/bounded-json.mjs +2 -330
  103. package/lib/browser-library-profile.mjs +62 -84
  104. package/lib/candidate-provenance-bounds.mjs +2 -0
  105. package/lib/classify.mjs +1 -1
  106. package/lib/credential-form-attribution.mjs +136 -0
  107. package/lib/credential-surface-profile.mjs +6 -2
  108. package/lib/ct-query.mjs +2 -27
  109. package/lib/ct-response-bounds.mjs +2 -9
  110. package/lib/ct-search.mjs +83 -47
  111. package/lib/dmarc-discovery.mjs +62 -0
  112. package/lib/dns-convergence-review.mjs +21 -8
  113. package/lib/dns-delegation-health.mjs +35 -15
  114. package/lib/dns-inheritance-review.mjs +53 -63
  115. package/lib/dns-intelligence.mjs +27 -11
  116. package/lib/dns-query-coverage.mjs +40 -0
  117. package/lib/domain-assurance.mjs +1 -1
  118. package/lib/domain-change-packet.mjs +12 -13
  119. package/lib/domain-change-review.mjs +32 -14
  120. package/lib/domain-control-flight-recorder.mjs +103 -27
  121. package/lib/domain-control-manifest.mjs +25 -17
  122. package/lib/domain-posture-analysis.mjs +75 -7
  123. package/lib/domain-posture-parsers.mjs +9 -0
  124. package/lib/domain-posture.mjs +45 -25
  125. package/lib/domain-registration.mjs +392 -0
  126. package/lib/evidence-export-privacy.mjs +27 -17
  127. package/lib/evidence-export.mjs +3 -3
  128. package/lib/evidence-report.mjs +5 -1
  129. package/lib/external-intelligence-risk.mjs +2 -124
  130. package/lib/favicon.mjs +13 -4
  131. package/lib/generated/cisa-kev-catalog.mjs +41 -3
  132. package/lib/generated/registrar-standing-catalogue.mjs +18 -10
  133. package/lib/generated/retire-browser-catalog.mjs +332 -19
  134. package/lib/generated/{unicode-confusables-17.mjs → unicode-confusables.mjs} +33 -33
  135. package/lib/html-signals.mjs +13 -2
  136. package/lib/http-evidence-bounds.mjs +2 -8
  137. package/lib/http-intelligence.mjs +13 -1
  138. package/lib/idn-confusable-policy.mjs +6 -4
  139. package/lib/idn-confusables.mjs +1 -1
  140. package/lib/interchange-fidelity-registry.mjs +10 -0
  141. package/lib/lookup-child-profile-contract.mjs +28 -11
  142. package/lib/lookup-contract-primitives.mjs +1 -1
  143. package/lib/lookup-diagnostics.mjs +1 -23
  144. package/lib/lookup-network-evidence-bounds.mjs +1 -10
  145. package/lib/lookup-progress.mjs +50 -1
  146. package/lib/lookup-response-contract.mjs +70 -40
  147. package/lib/lookup-response.mjs +5 -15
  148. package/lib/lookup-source-progress.mjs +8 -22
  149. package/lib/lookup.mjs +1 -1
  150. package/lib/operation-budget.mjs +4 -0
  151. package/lib/opportunity-scoring.mjs +2 -107
  152. package/lib/outbound-identity.mjs +1 -1
  153. package/lib/page-fingerprints.mjs +18 -1
  154. package/lib/perceptual-hash-comparison.mjs +2 -34
  155. package/lib/perceptual-hash.mjs +36 -10
  156. package/lib/portable-generator.mjs +2 -29
  157. package/lib/project-metadata.mjs +2 -8
  158. package/lib/rdap-client.mjs +11 -7
  159. package/lib/registrable-domain.mjs +2 -56
  160. package/lib/registrar-standing-contract.mjs +1 -1
  161. package/lib/registrar-standing.mjs +9 -13
  162. package/lib/registry-capabilities.mjs +2 -2
  163. package/lib/registry-capability-catalogue.mjs +27 -19
  164. package/lib/risk-calibration-summary.mjs +2 -352
  165. package/lib/risk-scoring.mjs +2 -345
  166. package/lib/scoring-evidence-quality.mjs +2 -107
  167. package/lib/semantic-version.mjs +2 -54
  168. package/lib/service-binding-dns.mjs +46 -2
  169. package/lib/smtp-transport-review.mjs +48 -19
  170. package/lib/sslbl-certificates.generated.mjs +142 -139
  171. package/lib/static-html-analysis.mjs +32 -4
  172. package/lib/structured-data-identity.mjs +1 -1
  173. package/lib/technology-evidence-role.mjs +1 -51
  174. package/lib/threat-intelligence-manifests.mjs +1 -1
  175. package/lib/threat-intelligence-runtime.mjs +1 -1
  176. package/lib/threat-intelligence-targets.mjs +1 -1
  177. package/lib/threat-intelligence-types.mjs +2 -75
  178. package/lib/tls-intelligence.mjs +30 -5
  179. package/lib/typosquat-generator.mjs +11 -2
  180. package/lib/website-enrichment.mjs +404 -0
  181. package/lib/whois-authority.mjs +43 -5
  182. package/lib/whois-chain.mjs +12 -3
  183. package/lib/whois-common-formats.mjs +3 -1
  184. package/lib/whois-contacts.mjs +13 -2
  185. package/lib/whois-normalization.mjs +3 -1
  186. package/lib/whois-parser.mjs +5 -3
  187. package/package.json +15 -14
  188. package/packages/analysis/analyst-taxonomy.mjs +40 -0
  189. package/packages/analysis/bounded-json.mjs +330 -0
  190. package/packages/analysis/candidate-provenance-bounds.mjs +4 -0
  191. package/packages/analysis/cli-argument-grammar.mjs +216 -0
  192. package/packages/analysis/ct-query.mjs +27 -0
  193. package/packages/analysis/ct-response-bounds.mjs +9 -0
  194. package/packages/analysis/external-intelligence-risk.mjs +124 -0
  195. package/packages/analysis/http-evidence-bounds.mjs +8 -0
  196. package/packages/analysis/lookup-network-evidence-bounds.mjs +10 -0
  197. package/packages/analysis/opportunity-scoring.mjs +107 -0
  198. package/packages/analysis/perceptual-hash-comparison.mjs +34 -0
  199. package/packages/analysis/portable-generator.mjs +29 -0
  200. package/packages/analysis/project-metadata.mjs +8 -0
  201. package/packages/analysis/registrable-domain.mjs +56 -0
  202. package/packages/analysis/risk-calibration-summary.mjs +352 -0
  203. package/packages/analysis/risk-scoring.mjs +348 -0
  204. package/packages/analysis/scoring-evidence-quality.mjs +107 -0
  205. package/packages/analysis/semantic-version.mjs +54 -0
  206. package/packages/analysis/threat-intelligence-types.mjs +75 -0
  207. package/packages/cases/case-containment-handoff.mjs +195 -0
  208. package/packages/cases/case-evidence-links.mjs +151 -0
  209. package/packages/cases/case-evidence-model.mjs +89 -12
  210. package/packages/cases/case-evidence-quality.mjs +5 -0
  211. package/packages/cases/case-export-input.mjs +7 -5
  212. package/packages/cases/case-incident-context.mjs +52 -0
  213. package/packages/cases/case-markdown.mjs +11 -0
  214. package/packages/cases/case-migration-model.mjs +40 -24
  215. package/packages/cases/case-object-coverage.mjs +33 -0
  216. package/packages/cases/case-packet-correction.mjs +106 -0
  217. package/packages/cases/case-recheck-model.mjs +124 -36
  218. package/packages/cases/case-record-contracts.mjs +1 -1
  219. package/packages/cases/case-record-core.mjs +1 -1
  220. package/packages/cases/case-record-operations.mjs +111 -118
  221. package/packages/cases/case-record-projection.mjs +23 -4
  222. package/packages/cases/case-record-version-input.mjs +41 -0
  223. package/packages/cases/case-report.mjs +93 -27
  224. package/packages/cases/case-requested-evidence.mjs +146 -0
  225. package/packages/cases/case-response-actions.mjs +159 -1
  226. package/packages/cases/case-response-model.mjs +16 -1
  227. package/packages/cases/case-response-object.mjs +119 -0
  228. package/packages/cases/case-response-outcomes.mjs +108 -20
  229. package/packages/cases/case-response-packet-render.mjs +35 -12
  230. package/packages/cases/case-response-packet.mjs +55 -13
  231. package/packages/cases/case-response-records.mjs +0 -1
  232. package/packages/cases/case-response-review-inputs.mjs +60 -8
  233. package/packages/cases/case-storage-model.mjs +1 -1
  234. package/packages/cases/case-workflow-metadata.mjs +217 -0
  235. package/packages/cases/case-workflow-migration.mjs +43 -0
  236. package/packages/cases/http-summary.mjs +6 -1
  237. package/packages/comparison/favicon-similarity.mjs +1 -1
  238. package/packages/comparison/page-similarity.mjs +1 -1
  239. package/packages/comparison/relationship-evidence.mjs +21 -9
  240. package/packages/comparison/relationship-provenance.mjs +7 -1
  241. package/packages/contracts/analyst-interchange.mjs +35 -15
  242. package/packages/contracts/candidate-watch-review.mjs +54 -0
  243. package/packages/contracts/case-portability.mjs +360 -49
  244. package/packages/contracts/cli-command-semantics.mjs +5 -0
  245. package/packages/contracts/cli-grammar.mjs +6 -0
  246. package/packages/contracts/cli-lookup-brief.mjs +2 -0
  247. package/packages/contracts/context-review.mjs +25 -0
  248. package/packages/contracts/document-intake.mjs +11 -0
  249. package/packages/contracts/domain-control-flight-recorder.mjs +30 -14
  250. package/packages/contracts/domain-control-manifest.mjs +1 -1
  251. package/packages/contracts/domain-control-monitor.mjs +13 -2
  252. package/packages/contracts/domain-control-review.mjs +36 -15
  253. package/packages/contracts/external-observation-interchange.mjs +96 -28
  254. package/packages/contracts/har-review.mjs +2 -0
  255. package/packages/contracts/identity-events.mjs +4 -0
  256. package/packages/contracts/investigation-run.mjs +2 -0
  257. package/packages/contracts/ip-address.mjs +10 -0
  258. package/packages/contracts/lookup-evidence.mjs +3 -2
  259. package/packages/contracts/mail-authentication.mjs +5 -0
  260. package/packages/contracts/message-intake.mjs +37 -0
  261. package/packages/contracts/offline-comparison.mjs +4 -1
  262. package/packages/contracts/passive-mail.mjs +10 -0
  263. package/packages/contracts/risk-calibration.mjs +44 -29
  264. package/packages/contracts/schema-lifecycle.mjs +3 -1
  265. package/packages/contracts/web-capture.mjs +2 -2
  266. package/packages/contracts/workspace-portability.mjs +144 -28
  267. package/packages/evidence/collection-quality.mjs +67 -0
  268. package/packages/evidence/credential-form-attribution.mjs +60 -0
  269. package/packages/evidence/dns-query-outcome.mjs +72 -0
  270. package/packages/evidence/email-recipient.mjs +18 -0
  271. package/packages/evidence/image-regions.mjs +15 -0
  272. package/packages/evidence/lookup-target.mjs +5 -1
  273. package/packages/evidence/observation.mjs +2 -0
  274. package/packages/evidence/png-pixels.mjs +117 -0
  275. package/packages/evidence/public-address-policy.mjs +83 -0
  276. package/packages/evidence/record-identity.mjs +60 -16
  277. package/packages/evidence/technology-evidence-role.mjs +51 -0
  278. package/packages/interchange/defensive-indicator-export.mjs +264 -0
  279. package/packages/interchange/external-findings-import.mjs +36 -29
  280. package/packages/interchange/json-output.mjs +14 -0
  281. package/packages/interchange/managed-indicator-export.mjs +36 -0
  282. package/packages/interchange/managed-indicator-set.mjs +223 -0
  283. package/packages/interchange/misp-indicator-export.mjs +93 -0
  284. package/packages/interchange/sharing-policy.mjs +56 -0
  285. package/packages/interchange/stix-indicator-export.mjs +129 -0
  286. package/packages/interchange/web-capture-import.mjs +22 -15
  287. package/packages/investigation/acquisition-decision-packet.mjs +79 -1
  288. package/packages/investigation/bulk-mail-exposure.mjs +7 -13
  289. package/packages/investigation/capture-coverage.mjs +57 -0
  290. package/packages/investigation/connector-provenance-review.mjs +100 -0
  291. package/packages/investigation/context-review.mjs +35 -0
  292. package/packages/investigation/document-intake.mjs +87 -0
  293. package/packages/investigation/docx-intake.mjs +133 -0
  294. package/packages/investigation/domain-history-review.mjs +132 -0
  295. package/packages/investigation/domain-transition-review.mjs +49 -0
  296. package/packages/investigation/evidence-coverage-ledger.mjs +9 -1
  297. package/packages/investigation/har-intake.mjs +68 -0
  298. package/packages/investigation/identity-event-intake.mjs +131 -0
  299. package/packages/investigation/identity-incident-review.mjs +39 -0
  300. package/packages/investigation/incident-sequence-review.mjs +58 -0
  301. package/packages/investigation/infrastructure-observation.mjs +234 -0
  302. package/packages/investigation/intake-context.mjs +284 -0
  303. package/packages/investigation/intake-indicators.mjs +116 -0
  304. package/packages/investigation/intake-phones.mjs +155 -0
  305. package/packages/investigation/intake-report.mjs +20 -0
  306. package/packages/investigation/investigation-manifest.mjs +35 -9
  307. package/packages/investigation/link-intake.mjs +209 -0
  308. package/packages/investigation/lookup-asset-graph.mjs +1 -1
  309. package/packages/investigation/mail-authentication-review.mjs +222 -0
  310. package/packages/investigation/mail-header-identity.mjs +209 -0
  311. package/packages/investigation/message-intake.mjs +235 -0
  312. package/packages/investigation/page-behaviour.mjs +113 -0
  313. package/packages/investigation/pdf-intake.mjs +155 -0
  314. package/packages/investigation/platform-continuity-review.mjs +60 -0
  315. package/packages/investigation/qr-intake.mjs +56 -0
  316. package/packages/investigation/requested-action-hints.mjs +11 -0
  317. package/packages/investigation/selected-input-review.mjs +17 -0
  318. package/packages/investigation/storefront-review.mjs +70 -0
  319. package/packages/monitoring/domain-feed.mjs +321 -0
  320. package/packages/workspace/brand-candidate-workflow.mjs +351 -0
  321. package/packages/workspace/brand-keyword-campaign.mjs +231 -0
  322. package/packages/workspace/brand-profile-model.mjs +48 -4
  323. package/packages/workspace/bulk-review-model.mjs +28 -26
  324. package/packages/workspace/bulk-session-model.mjs +27 -13
  325. package/packages/workspace/campaign-model.mjs +1 -1
  326. package/packages/workspace/candidate-watch-handoff.mjs +195 -0
  327. package/packages/workspace/detection-rule-model.mjs +11 -3
  328. package/packages/workspace/hostile-input.mjs +1 -1
  329. package/packages/workspace/investigation-guide.mjs +2 -2
  330. package/packages/workspace/investigation-template-model.mjs +26 -2
  331. package/packages/workspace/page-baseline.mjs +2 -2
  332. package/packages/workspace/relationship-observation-model.mjs +17 -13
  333. package/packages/workspace/shortlist-model.mjs +24 -7
  334. package/packages/workspace/watchlist-history.mjs +59 -16
  335. package/packages/workspace/watchlist-store.mjs +152 -11
  336. package/packages/workspace/website-snapshot-model.mjs +28 -3
  337. package/packages/workspace/workspace-archive-crypto.mjs +1 -1
  338. package/packages/workspace/workspace-archive.mjs +5 -4
  339. package/third-party-notices.txt +549 -16
  340. package/lib/hostname.mjs +0 -2
@@ -1,6 +1,12 @@
1
1
  import { createHash } from 'node:crypto';
2
+ import { readCaseEvidenceRequest, readCasePacketAmendment, assertEvidenceRequestEvent, assertEvidenceRequestHistory, assertPacketAmendmentSelection } from "../../packages/cases/case-requested-evidence.mjs";
3
+ import { readCaseDeliveryPacketReceipt, readCasePacketCorrection, assertDeliveryPacketReceipt, correctionDelivery } from "../../packages/cases/case-packet-correction.mjs";
4
+ import { CASE_SELECTED_EVIDENCE_SOURCE_LIMITATION } from "../../packages/cases/case-evidence-links.mjs";
5
+ import { OBJECT_RESPONSE_CASE_PACKET_VERSION, OBJECT_RESPONSE_CASE_REVIEW_INPUTS_VERSION } from "../../packages/contracts/case-portability.mjs";
2
6
  import { canonicalArtifactJsonV2 } from "../../packages/evidence/artifact-integrity.mjs";
3
- import { CASE_RESPONSE_PACKET_VERSION, PUBLISHED_V2_2_CASE_RESPONSE_PACKET_VERSION, PUBLISHED_V2_3_CASE_RESPONSE_PACKET_VERSION, PUBLIC_CASE_RESPONSE_PACKET_VERSION, PUBLISHED_V2_CASE_RESPONSE_PACKET_VERSION, CASE_RESPONSE_REVIEW_INPUTS_SCHEMA, CASE_RESPONSE_REVIEW_INPUTS_VERSION, PUBLISHED_V2_2_CASE_RESPONSE_REVIEW_INPUTS_VERSION, PUBLISHED_V2_3_CASE_RESPONSE_REVIEW_INPUTS_VERSION, PUBLISHED_V2_CASE_RESPONSE_REVIEW_INPUTS_VERSION, MAX_ABUSIVE_URLS, MAX_RESPONSE_ARTEFACT_REFERENCES, MAX_RESPONSE_AUTHORISATION_CLOCK_SKEW_MS, MAX_RESPONSE_ACTION_HISTORY, MAX_RESPONSE_CONTACTS, MAX_RESPONSE_CONTRADICTIONS, MAX_RESPONSE_SELECTED_EVIDENCE, RESPONSE_AUTHORISATION_CONFIRMATION_IDS, RESPONSE_PACKET_PROFILES, RESPONSE_CONTACT_KINDS, RESPONSE_PACKET_PROFILE_IDS, RESPONSE_READINESS_ROW_IDS, RESPONSE_READINESS_STATES, RESPONSE_ROUTE_STALE_AFTER_DAYS, } from "../../packages/cases/case-response-packet.mjs";
7
+ import { EVIDENCE_FOLLOW_UP_CASE_RESPONSE_PACKET_VERSION, EVIDENCE_FOLLOW_UP_CASE_RESPONSE_REVIEW_INPUTS_VERSION } from "../../packages/contracts/case-portability.mjs";
8
+ import { readCaseResponseObject, readCaseResponseObjects, readCaseResponseObjectOutcome, assertCaseObjectOutcome } from "../../packages/cases/case-response-object.mjs";
9
+ import { CASE_RESPONSE_PACKET_VERSION, PUBLISHED_V2_2_CASE_RESPONSE_PACKET_VERSION, PUBLISHED_V2_3_CASE_RESPONSE_PACKET_VERSION, PUBLIC_CASE_RESPONSE_PACKET_VERSION, PUBLISHED_V2_CASE_RESPONSE_PACKET_VERSION, CASE_RESPONSE_REVIEW_INPUTS_SCHEMA, CASE_RESPONSE_REVIEW_INPUTS_VERSION, LATEST_PUBLIC_CASE_RESPONSE_PACKET_VERSION, LATEST_PUBLIC_CASE_RESPONSE_REVIEW_INPUTS_VERSION, PUBLISHED_V2_2_CASE_RESPONSE_REVIEW_INPUTS_VERSION, PUBLISHED_V2_3_CASE_RESPONSE_REVIEW_INPUTS_VERSION, PUBLISHED_V2_CASE_RESPONSE_REVIEW_INPUTS_VERSION, MAX_ABUSIVE_URLS, MAX_RESPONSE_ARTEFACT_REFERENCES, MAX_RESPONSE_AUTHORISATION_CLOCK_SKEW_MS, MAX_RESPONSE_ACTION_HISTORY, MAX_RESPONSE_CONTACTS, MAX_RESPONSE_CONTRADICTIONS, MAX_RESPONSE_SELECTED_EVIDENCE, RESPONSE_AUTHORISATION_CONFIRMATION_IDS, RESPONSE_PACKET_PROFILES, RESPONSE_CONTACT_KINDS, RESPONSE_PACKET_PROFILE_IDS, RESPONSE_READINESS_ROW_IDS, RESPONSE_READINESS_STATES, RESPONSE_ROUTE_STALE_AFTER_DAYS, } from "../../packages/cases/case-response-packet.mjs";
4
10
  import { CASE_ACTION_EVENT_SOURCE_CLASSES, CASE_ACTION_STATES, CASE_ACTION_TYPES, CASE_CLOSURE_REASONS, CASE_OBSERVED_EFFECT_SOURCE_CLASSES, CASE_OBSERVED_EFFECT_STATES, CASE_PROVIDER_OUTCOMES, isLegalCaseActionTransition, MAX_CASE_ACTION_EVENTS_PER_ACTION, MAX_CASE_ACTIONS, MAX_CASE_ASSERTIONS, MAX_CASE_DECISIONS, MAX_CASE_EVIDENCE_PINS, MAX_RESPONSE_LABEL_LENGTH, MAX_RESPONSE_LIMITATION_LENGTH, MAX_RESPONSE_LIMITATIONS, MAX_RESPONSE_RECIPIENT_LENGTH, } from "../../packages/cases/case-response-model.mjs";
5
11
  import { HEX_DIGEST_RE, array, boolean, domain, enumeration, exact, exactOptional, fail, integer, iso, optionalText, sameValues, strings, text, } from "./structure-primitives.mjs";
6
12
  const CASE_RESPONSE_PREFLIGHT_IDS_V6 = [
@@ -281,7 +287,8 @@ function validateVersionedCaseResponsePacket(value, version) {
281
287
  fail('Case-response abusive URLs');
282
288
  text(incident.observedHarm, 'Case-response observed harm', 2_000);
283
289
  iso(incident.observedAt, 'Case-response observedAt');
284
- if (profile.subject !== `${selectedProfile?.subjectPrefix}: ${caseRecord.domain} (${incident.category})`)
290
+ if (profile.subject !== `${selectedProfile?.subjectPrefix}: ${caseRecord.domain} (${incident.category})`
291
+ && (version < 13 || profile.subject !== `Correction or retraction request: ${caseRecord.domain} (${incident.category})`))
285
292
  fail('Case-response profile subject');
286
293
  const contacts = array(root.contacts, 'Case-response contacts', MAX_RESPONSE_CONTACTS);
287
294
  const contactKeys = new Set();
@@ -378,7 +385,10 @@ function validateVersionedCaseResponsePacket(value, version) {
378
385
  const evidence = array(root.selectedEvidence, 'Case-response selected evidence', MAX_RESPONSE_SELECTED_EVIDENCE);
379
386
  const evidenceIds = new Set();
380
387
  for (const candidate of evidence) {
381
- const item = exactOptional(candidate, ['id', 'label', 'source', 'observedAt', 'completeness', 'limitations'], version > PUBLISHED_V2_3_CASE_RESPONSE_PACKET_VERSION ? ['observationHostname'] : [], 'Case-response selected evidence');
388
+ const item = exactOptional(candidate, ['id', 'label', 'source', 'observedAt', 'completeness', 'limitations'], [...(version > PUBLISHED_V2_3_CASE_RESPONSE_PACKET_VERSION ? ['observationHostname', 'webObservationMode'] : []), ...(version >= 12 ? ['responseObject'] : [])], 'Case-response selected evidence');
389
+ readCaseResponseObject(item.responseObject);
390
+ if (item.webObservationMode !== undefined && item.webObservationMode !== 'selected_url')
391
+ fail('Case-response web observation mode');
382
392
  const id = text(item.id, 'Case-response evidence id', 64);
383
393
  if (evidenceIds.has(id))
384
394
  fail('Case-response selected evidence');
@@ -543,13 +553,17 @@ function validateVersionedCaseResponsePacket(value, version) {
543
553
  const actionIdOrder = [];
544
554
  const providerEvents = [];
545
555
  for (const candidate of history) {
546
- const action = exact(candidate, [
556
+ const action = exactOptional(candidate, [
547
557
  'actionId', 'type', 'recipient', 'contactSource', 'state', 'reference',
548
558
  'providerOutcome', 'outcomeDetail', 'originActionId', 'historyOmitted',
549
559
  'historyLimitations', 'transitions', 'createdAt', 'updatedAt',
550
560
  ...(hasActionBinding ? ['routeObservedAt'] : []),
551
561
  ...(hasPlatformRoutes ? ['routeReviewAfter'] : []),
552
- ], 'Case-response escalation action');
562
+ ], [...(Number(root.schemaVersion) >= 11 ? ['amendment'] : []), ...(version >= 12 ? ['responseObjects'] : []), ...(version >= 13 ? ['correction'] : [])], 'Case-response escalation action');
563
+ readCaseResponseObjects(action.responseObjects);
564
+ readCasePacketAmendment(action.amendment);
565
+ if (readCasePacketCorrection(action.correction) && action.amendment)
566
+ throw new TypeError('Correction and provider amendment must remain separate.');
553
567
  const actionId = text(action.actionId, 'Case-response action id', 64);
554
568
  if (actionIds.has(actionId))
555
569
  fail('Case-response action identity');
@@ -571,6 +585,7 @@ function validateVersionedCaseResponsePacket(value, version) {
571
585
  integer(action.historyOmitted, 'Case-response action omitted history', 0, 1_000_000);
572
586
  strings(action.historyLimitations, 'Case-response action history limitations', 8, 240);
573
587
  const transitions = array(action.transitions, 'Case-response action transitions', MAX_CASE_ACTION_EVENTS_PER_ACTION, 1);
588
+ const requestEvents = [];
574
589
  const eventIds = new Set();
575
590
  let projectedState = null;
576
591
  let latestReference = null;
@@ -580,8 +595,23 @@ function validateVersionedCaseResponsePacket(value, version) {
580
595
  let previousTime = Number.NEGATIVE_INFINITY;
581
596
  let previousEventId = '';
582
597
  for (const eventCandidate of transitions) {
583
- const event = exact(eventCandidate, ['id', 'previousState', 'nextState', 'occurredAt', 'sourceClass', 'provenance', 'reference', 'evidencePinId', 'limitations', 'providerOutcome', 'outcomeDetail', 'originActionId', 'applied'], 'Case-response action transition');
598
+ const event = exactOptional(eventCandidate, ['id', 'previousState', 'nextState', 'occurredAt', 'sourceClass', 'provenance', 'reference', 'evidencePinId', 'limitations', 'providerOutcome', 'outcomeDetail', 'originActionId', 'applied'], [...(Number(root.schemaVersion) >= 11 ? ['evidenceRequest'] : []), ...(version >= 12 ? ['responseObjects', 'objectOutcome'] : []), ...(version >= 13 ? ['packetReceipt'] : [])], 'Case-response action transition');
599
+ const receipt = readCaseDeliveryPacketReceipt(event.packetReceipt);
600
+ assertDeliveryPacketReceipt(receipt, event, actionId);
601
+ if (receipt && (receipt.caseId !== caseRecord.id || receipt.target !== caseRecord.domain || receipt.recipient !== action.recipient))
602
+ fail('Case-response receipt Case, target or recipient');
603
+ const responseObjects = readCaseResponseObjects(event.responseObjects);
604
+ const objectOutcome = readCaseResponseObjectOutcome(event.objectOutcome);
605
+ if (objectOutcome) {
606
+ if (!responseObjects?.length || !['submitted', 'acknowledged', 'terminal'].includes(String(event.nextState))
607
+ || !['analyst', 'provider'].includes(String(event.sourceClass)) || event.previousState === 'authorised')
608
+ fail('Case-response object outcome scope');
609
+ responseObjects.forEach(object => assertCaseObjectOutcome(objectOutcome, object));
610
+ }
611
+ const evidenceRequest = readCaseEvidenceRequest(event.evidenceRequest);
612
+ assertEvidenceRequestEvent(evidenceRequest, event);
584
613
  const eventId = text(event.id, 'Case-response action event id', 64);
614
+ requestEvents.push({ id: eventId, ...(evidenceRequest ? { evidenceRequest } : {}) });
585
615
  if (eventIds.has(eventId))
586
616
  fail('Case-response action event identity');
587
617
  eventIds.add(eventId);
@@ -650,6 +680,7 @@ function validateVersionedCaseResponsePacket(value, version) {
650
680
  }
651
681
  }
652
682
  }
683
+ assertEvidenceRequestHistory(requestEvents, Number(action.historyOmitted) > 0);
653
684
  if ((projectedState ?? 'drafting') !== action.state
654
685
  || latestReference !== action.reference
655
686
  || latestProviderOutcome !== action.providerOutcome
@@ -667,6 +698,20 @@ function validateVersionedCaseResponsePacket(value, version) {
667
698
  if (recipientRoute && !actionIds.has(recipientRoute.actionId))
668
699
  fail('Case-response recipient action');
669
700
  }
701
+ if (version >= 11)
702
+ assertPacketAmendmentSelection(history.map(candidate => {
703
+ const action = candidate;
704
+ return { id: action.actionId, history: action.transitions, originActionId: action.originActionId, amendment: action.amendment };
705
+ }), selectedActionId, evidenceIds);
706
+ if (version >= 13) {
707
+ const actions = history.map(candidate => { const action = candidate; return { ...action, id: action.actionId, history: action.transitions }; });
708
+ const selected = actions.find(action => action.id === selectedActionId);
709
+ if (selected?.correction)
710
+ correctionDelivery(actions, selected, { caseId: caseRecord.id, target: caseRecord.domain, profile: profile.id, selectedPinIds: evidenceIds });
711
+ const expectedPrefix = selected?.correction ? 'Correction or retraction request' : selectedProfile?.subjectPrefix;
712
+ if (profile.subject !== `${expectedPrefix}: ${caseRecord.domain} (${incident.category})`)
713
+ fail('Case-response correction subject');
714
+ }
670
715
  const lifecycle = exact(root.responseLifecycle, ['providerOutcomeState', 'latestProviderOutcome', 'observedChangeState', 'latestObservedEffect', 'latestObservedChangeAt', 'closure', 'limitations'], 'Case-response lifecycle');
671
716
  const providerOutcomeState = enumeration(lifecycle.providerOutcomeState, ['available', 'missing', 'ambiguous'], 'Case-response lifecycle provider state');
672
717
  let actualLatestProvider = null;
@@ -709,7 +754,10 @@ function validateVersionedCaseResponsePacket(value, version) {
709
754
  fail('Case-response lifecycle provider state');
710
755
  const observedChangeState = enumeration(lifecycle.observedChangeState, ['available', 'missing', 'ambiguous'], 'Case-response lifecycle observed-change state');
711
756
  if (lifecycle.latestObservedEffect !== null) {
712
- const effect = exact(lifecycle.latestObservedEffect, ['reviewId', 'state', 'observedAt', 'sourceClass', 'source'], 'Case-response lifecycle observed effect');
757
+ const effect = exactOptional(lifecycle.latestObservedEffect, ['reviewId', 'state', 'observedAt', 'sourceClass', 'source'], version >= 12 ? ['responseObject', 'objectOutcome'] : [], 'Case-response lifecycle observed effect');
758
+ const responseObject = readCaseResponseObject(effect.responseObject);
759
+ const objectOutcome = readCaseResponseObjectOutcome(effect.objectOutcome);
760
+ assertCaseObjectOutcome(objectOutcome, responseObject);
713
761
  text(effect.reviewId, 'Case-response effect review id', 64);
714
762
  enumeration(effect.state, CASE_OBSERVED_EFFECT_STATES, 'Case-response effect state');
715
763
  iso(effect.observedAt, 'Case-response effect observedAt');
@@ -731,8 +779,11 @@ function validateVersionedCaseResponsePacket(value, version) {
731
779
  contract: CASE_RESPONSE_REVIEW_INPUTS_SCHEMA,
732
780
  version: version === CASE_RESPONSE_PACKET_VERSION
733
781
  ? CASE_RESPONSE_REVIEW_INPUTS_VERSION
734
- : hasPlatformRoutes ? PUBLISHED_V2_3_CASE_RESPONSE_REVIEW_INPUTS_VERSION
735
- : hasActionBinding ? PUBLISHED_V2_2_CASE_RESPONSE_REVIEW_INPUTS_VERSION : PUBLISHED_V2_CASE_RESPONSE_REVIEW_INPUTS_VERSION,
782
+ : version === OBJECT_RESPONSE_CASE_PACKET_VERSION ? OBJECT_RESPONSE_CASE_REVIEW_INPUTS_VERSION
783
+ : version === EVIDENCE_FOLLOW_UP_CASE_RESPONSE_PACKET_VERSION ? EVIDENCE_FOLLOW_UP_CASE_RESPONSE_REVIEW_INPUTS_VERSION
784
+ : version === LATEST_PUBLIC_CASE_RESPONSE_PACKET_VERSION ? LATEST_PUBLIC_CASE_RESPONSE_REVIEW_INPUTS_VERSION
785
+ : hasPlatformRoutes ? PUBLISHED_V2_3_CASE_RESPONSE_REVIEW_INPUTS_VERSION
786
+ : hasActionBinding ? PUBLISHED_V2_2_CASE_RESPONSE_REVIEW_INPUTS_VERSION : PUBLISHED_V2_CASE_RESPONSE_REVIEW_INPUTS_VERSION,
736
787
  profile: {
737
788
  id: profile.id,
738
789
  label: profile.label,
@@ -755,6 +806,7 @@ function validateVersionedCaseResponsePacket(value, version) {
755
806
  escalationHistoryOmitted: root.escalationHistoryOmitted,
756
807
  escalationHistoryLimitations: root.escalationHistoryLimitations,
757
808
  responseLifecycle: root.responseLifecycle,
809
+ ...(version >= 13 ? { sourceQualifications: strings(root.provenance.limitations, 'Case-response limitations', 8, 600).filter(value => value === CASE_SELECTED_EVIDENCE_SOURCE_LIMITATION) } : {}),
758
810
  };
759
811
  const expectedReviewDigest = createHash('sha256')
760
812
  .update(canonicalArtifactJsonV2(reviewedInputs))
@@ -781,6 +833,12 @@ function validateVersionedCaseResponsePacket(value, version) {
781
833
  fail('Case-response integrity');
782
834
  }
783
835
  export function validateCaseResponsePacket(value) {
836
+ if (value.schemaVersion === OBJECT_RESPONSE_CASE_PACKET_VERSION)
837
+ return validateVersionedCaseResponsePacket(value, OBJECT_RESPONSE_CASE_PACKET_VERSION);
838
+ if (value.schemaVersion === EVIDENCE_FOLLOW_UP_CASE_RESPONSE_PACKET_VERSION)
839
+ return validateVersionedCaseResponsePacket(value, EVIDENCE_FOLLOW_UP_CASE_RESPONSE_PACKET_VERSION);
840
+ if (value.schemaVersion === LATEST_PUBLIC_CASE_RESPONSE_PACKET_VERSION)
841
+ return validateVersionedCaseResponsePacket(value, LATEST_PUBLIC_CASE_RESPONSE_PACKET_VERSION);
784
842
  if (value.schemaVersion === PUBLIC_CASE_RESPONSE_PACKET_VERSION)
785
843
  return validateCaseResponsePacketV6(value);
786
844
  if (value.schemaVersion === PUBLISHED_V2_CASE_RESPONSE_PACKET_VERSION) {
@@ -1,10 +1,12 @@
1
1
  import { isDeepStrictEqual } from 'node:util';
2
2
  import { assertLookupEvidencePrivacySafeTree, assertLookupEvidencePortableTree, LOOKUP_EVIDENCE_SCHEMA, LOOKUP_EVIDENCE_SCHEMA_VERSION, HOSTNAME_SCOPED_LOOKUP_EVIDENCE_SCHEMA_VERSION, PRIVACY_MINIMIZED_LOOKUP_EVIDENCE_SCHEMA_VERSION, REGISTRAR_STANDING_LOOKUP_EVIDENCE_SCHEMA_VERSION, V1_PUBLIC_LOOKUP_EVIDENCE_SCHEMA_VERSION, projectLookupEvidenceAvailability, projectLookupEvidenceAvailabilityPublic, projectLookupEvidenceQuery, projectLookupEvidenceRdapPublication, projectLookupEvidenceRegistryInsights, projectLookupEvidenceWhoisPublication, SUPPORTED_LOOKUP_EVIDENCE_SCHEMA_VERSIONS, } from "../../lib/evidence-export.mjs";
3
3
  import { validLookupObservationScope } from "../../packages/evidence/lookup-target.mjs";
4
+ import { externalPasswordFormObservation } from "../../packages/evidence/credential-form-attribution.mjs";
5
+ import { credentialSurfaceContractState } from "../../lib/lookup-child-profile-contract.mjs";
4
6
  import { registrarStandingObservedBy, resolveRegistrarIanaId, validRegistrarStanding, } from "../../lib/registrar-standing-contract.mjs";
5
7
  import { compareRegistrySources } from "../../lib/registry-comparison.mjs";
6
8
  import { buildRegistryInsights } from "../../lib/registry-insights.mjs";
7
- import { WHOISLEUTH_SOURCE_REPOSITORY_URL } from "../../lib/project-metadata.mjs";
9
+ import { WHOISLEUTH_SOURCE_REPOSITORY_URL } from "../../packages/analysis/project-metadata.mjs";
8
10
  import { validHttpDeliveryMetadata, validPagePublicationMetadata, } from "../../lib/homepage-metadata-contract.mjs";
9
11
  import { HEX_DIGEST_RE, SEMANTIC_VERSION_RE, absoluteUrl, array, boolean, domain, enumeration, exact, exactOptional, fail, integer, iso, nullableRecord, optionalText, record, strings, text, } from "./structure-primitives.mjs";
10
12
  const LOOKUP_RDAP_ATTEMPT_OUTCOMES = [
@@ -24,7 +26,7 @@ const PUBLIC_LOOKUP_AVAILABILITY_ANALYSIS_KEYS = [
24
26
  'expiresInDays', 'privacyProtected', 'dnssec', 'activityStatus',
25
27
  'websiteProbeStatus', 'websiteProbeDetail', 'http', 'deepScanComplete',
26
28
  'faviconHash', 'faviconPHash', 'pageTitle', 'hasPasswordField',
27
- 'phishingLanguageMatch', 'hasExternalFormAction', 'externalAssetHosts',
29
+ 'phishingLanguageMatch', 'hasExternalFormAction', 'hasExternalPasswordForm', 'externalAssetHosts',
28
30
  'pageIdentity', 'credentialSurfaceProfile', 'structuredDataIdentity',
29
31
  'technologyProfile', 'pageRoleProfile', 'clientBehaviorProfile',
30
32
  'securityPosture', 'dns', 'tls', 'hasMx', 'hasNullMx', 'mxHosts', 'hasSpf',
@@ -42,6 +44,18 @@ const LOOKUP_REGISTRAR_COMPARISON_STATUSES = [
42
44
  'registrar_unavailable', 'registry_incomplete', 'registrar_incomplete',
43
45
  ];
44
46
  function validateLookupEvidenceHomepageMetadata(availability, version) {
47
+ const credentialProfile = availability.credentialSurfaceProfile === null || availability.credentialSurfaceProfile === undefined
48
+ ? null : record(availability.credentialSurfaceProfile, 'Lookup evidence credential profile');
49
+ if (version < 30 && (Object.hasOwn(availability, 'hasExternalPasswordForm') || credentialProfile?.credentialSurfaceVersion === 2)) {
50
+ fail('Lookup evidence form-attribution epoch');
51
+ }
52
+ if (Object.hasOwn(availability, 'hasExternalPasswordForm') && availability.hasExternalPasswordForm !== null)
53
+ boolean(availability.hasExternalPasswordForm, 'Lookup evidence password-form destination');
54
+ if (credentialProfile?.credentialSurfaceVersion === 2 && credentialSurfaceContractState(credentialProfile) !== 'supported')
55
+ fail('Lookup evidence form-attribution profile');
56
+ if (credentialProfile?.credentialSurfaceVersion === 2 && availability.hasExternalPasswordForm !== undefined
57
+ && availability.hasExternalPasswordForm !== externalPasswordFormObservation(credentialProfile.formAttribution))
58
+ fail('Lookup evidence form-attribution consistency');
45
59
  const pageIdentity = availability.pageIdentity === null || availability.pageIdentity === undefined
46
60
  ? null
47
61
  : record(availability.pageIdentity, 'Lookup evidence page identity');
@@ -675,7 +689,7 @@ export function validateLookupEvidenceArtifactStructure(value) {
675
689
  }
676
690
  validateLookupEvidenceHomepageMetadata(availability, version);
677
691
  if (!isDeepStrictEqual(analysis.availability, currentAvailability
678
- ? projectLookupEvidenceAvailability(analysis.availability)
692
+ ? projectLookupEvidenceAvailability(analysis.availability, { legacyUris: version < HOSTNAME_SCOPED_LOOKUP_EVIDENCE_SCHEMA_VERSION })
679
693
  : projectLookupEvidenceAvailabilityPublic(analysis.availability)))
680
694
  fail('Lookup evidence availability analysis');
681
695
  }
@@ -715,7 +729,7 @@ export function validateLookupEvidenceArtifactStructure(value) {
715
729
  if (!isDeepStrictEqual(registryInsights, expectedInsights))
716
730
  fail('Lookup evidence registry insight derivation');
717
731
  try {
718
- assertLookupEvidencePrivacySafeTree(root);
732
+ assertLookupEvidencePrivacySafeTree(root, { legacyUris: version < HOSTNAME_SCOPED_LOOKUP_EVIDENCE_SCHEMA_VERSION });
719
733
  }
720
734
  catch {
721
735
  fail('Lookup evidence privacy boundary');
@@ -1,8 +1,10 @@
1
- import { ACQUISITION_DECISIONS, ACQUISITION_MANUAL_CHECKS, ACQUISITION_DECISION_PACKET_SCHEMA, ACQUISITION_DECISION_PACKET_VERSION, } from "../../packages/investigation/acquisition-decision-packet.mjs";
1
+ import { ACQUISITION_DECISION_PACKET_SCHEMA, validateAcquisitionDecisionPacketStructure, } from "../../packages/investigation/acquisition-decision-packet.mjs";
2
2
  import { BULK_DOMAIN_COMPARISON_EXPORT_VERSION, BULK_DOMAIN_COMPARISON_SCHEMA, } from "../../packages/investigation/bulk-domain-comparison.mjs";
3
3
  import { BULK_MAIL_EXPOSURE_EXPORT_VERSION, BULK_MAIL_EXPOSURE_SCHEMA, MAX_BULK_MAIL_EXPOSURE_ROWS, } from "../../packages/investigation/bulk-mail-exposure.mjs";
4
4
  import { BULK_REVIEW_MANIFEST_SCHEMA, BULK_REVIEW_MANIFEST_VERSION, SUPPORTED_BULK_REVIEW_MANIFEST_VERSIONS, } from "../../packages/contracts/investigation-portability.mjs";
5
5
  import { MAX_BULK_SESSION_ROWS, MAX_BULK_SESSION_SOURCES } from "../../packages/contracts/workspace-portability.mjs";
6
+ import { MANAGED_INDICATOR_SET_SCHEMA } from "../../packages/contracts/analyst-interchange.mjs";
7
+ import { validateManagedIndicatorSet } from "../../packages/interchange/managed-indicator-set.mjs";
6
8
  import { BULK_SORT_KEYS } from "../../packages/workspace/bulk-sort.mjs";
7
9
  import { normalizeExplicitIsoTimestamp } from "../../packages/evidence/observation.mjs";
8
10
  import { LOOKUP_CLAIM_PASSPORT_SCHEMA, LOOKUP_CLAIM_PASSPORT_TARGET_TYPES, LOOKUP_CLAIM_PASSPORT_VERSION, MAX_LOOKUP_CLAIM_PASSPORT_LIMITATIONS, MAX_LOOKUP_CLAIM_PASSPORT_REQUIREMENTS, } from "../../packages/investigation/lookup-claim-passport.mjs";
@@ -24,55 +26,6 @@ const DOMAIN_COMPARISON_ROW_IDS = [
24
26
  ];
25
27
  const MAIL_STATES = ['authenticated_mail', 'evidence_incomplete', 'mail_auth_gap', 'mail_auth_incomplete', 'no_explicit_mx', 'null_mx'];
26
28
  const DNS_TYPES = ['A', 'AAAA', 'CAA', 'CDNSKEY', 'CDS', 'CNAME', 'CSYNC', 'HTTPS', 'MX', 'NS', 'SRV', 'SVCB', 'TLSA', 'TXT'];
27
- function validateAcquisitionItem(value, label) {
28
- const item = exact(value, ['id', 'label', 'state', 'detail', 'provenance'], label);
29
- const id = enumeration(item.id, ['availability', 'contacts', 'lifecycle', 'mail', 'nameservers', 'operations', 'policy_eligibility', 'policy_lifecycle', 'policy_transfer', 'tls', 'transfer', 'web'], label);
30
- enumeration(item.state, ['authoritative', 'observed', 'review', 'unavailable'], label);
31
- text(item.label, label, 200);
32
- text(item.detail, label, 2_000);
33
- text(item.provenance, label, 300);
34
- return id;
35
- }
36
- function validateAcquisition(value) {
37
- const root = exact(value, ['schema', 'version', 'generatedAt', 'target', 'synthetic', 'evidenceObservedAt', 'analystReview', 'evidenceReview', 'limitations', 'integrity'], 'Acquisition decision packet');
38
- iso(root.generatedAt, 'Acquisition decision packet generatedAt');
39
- domain(root.target, 'Acquisition decision packet target');
40
- boolean(root.synthetic, 'Acquisition decision packet synthetic');
41
- iso(root.evidenceObservedAt, 'Acquisition decision packet evidenceObservedAt', true);
42
- const review = exact(root.analystReview, ['decision', 'rationale', 'reviewedChecks', 'outstandingChecks', 'state'], 'Acquisition analyst review');
43
- const decision = enumeration(review.decision, ACQUISITION_DECISIONS, 'Acquisition analyst review decision');
44
- text(review.rationale, 'Acquisition analyst review rationale', 2_000, true);
45
- const reviewed = strings(review.reviewedChecks, 'Acquisition reviewed checks', ACQUISITION_MANUAL_CHECKS.length, 40);
46
- const outstanding = strings(review.outstandingChecks, 'Acquisition outstanding checks', ACQUISITION_MANUAL_CHECKS.length, 40);
47
- const expectedReviewed = ACQUISITION_MANUAL_CHECKS.filter((item) => reviewed.includes(item));
48
- const expectedOutstanding = ACQUISITION_MANUAL_CHECKS.filter((item) => !reviewed.includes(item));
49
- if (!sameValues(reviewed, expectedReviewed) || !sameValues(outstanding, expectedOutstanding))
50
- fail('Acquisition analyst review checks');
51
- const expectedState = decision === 'unresolved' || reviewed.length < ACQUISITION_MANUAL_CHECKS.length ? 'draft' : 'reviewed';
52
- if (review.state !== expectedState)
53
- fail('Acquisition analyst review state');
54
- const evidence = exact(root.evidenceReview, ['version', 'label', 'state', 'items', 'transitionDependencies', 'policyChecks', 'nextSteps', 'limitations'], 'Acquisition evidence review');
55
- if (evidence.version !== 2)
56
- fail('Acquisition evidence review');
57
- text(evidence.label, 'Acquisition evidence review label', 200);
58
- enumeration(evidence.state, ['incomplete', 'registered', 'review_transition', 'sale_signal', 'unregistered_observation'], 'Acquisition evidence review state');
59
- const expectedEvidenceIds = {
60
- items: ['availability', 'lifecycle', 'transfer', 'operations', 'contacts'],
61
- transitionDependencies: ['nameservers', 'web', 'mail', 'tls'],
62
- policyChecks: ['policy_eligibility', 'policy_lifecycle', 'policy_transfer'],
63
- };
64
- for (const key of ['items', 'transitionDependencies', 'policyChecks']) {
65
- const expected = expectedEvidenceIds[key];
66
- const values = array(evidence[key], `Acquisition evidence review ${key}`, expected.length, expected.length);
67
- const ids = values.map((item, index) => validateAcquisitionItem(item, `Acquisition evidence review ${key}[${index}]`));
68
- if (!sameValues(ids, expected))
69
- fail(`Acquisition evidence review ${key}`);
70
- }
71
- strings(evidence.nextSteps, 'Acquisition next steps', 6, 600);
72
- strings(evidence.limitations, 'Acquisition evidence limitations', 12, 600);
73
- strings(root.limitations, 'Acquisition limitations', 8, 600);
74
- validateIntegrity(root.integrity, 'Acquisition integrity', root.version, ACQUISITION_DECISION_PACKET_VERSION);
75
- }
76
29
  function validateClaimPassportTarget(type, value) {
77
30
  const candidate = text(value, 'Claim passport target', 253);
78
31
  if (type === 'domain') {
@@ -334,10 +287,11 @@ function validateBulkReviewManifest(value) {
334
287
  strings(root.limitations, 'Bulk review manifest limitations', 8, 600);
335
288
  validateIntegrity(root.integrity, 'Bulk review manifest integrity', root.version, version);
336
289
  }
337
- function validateSourceObservations(value, label) {
290
+ function validateSourceObservations(value, label, queryCoverage) {
338
291
  const names = new Set();
292
+ let queryCount = 0;
339
293
  return array(value, label, 16).map((candidate) => {
340
- const source = exact(candidate, ['label', 'source', 'state', 'observedAt'], label);
294
+ const source = exact(candidate, ['label', 'source', 'state', 'observedAt', ...(queryCoverage ? ['queries'] : [])], label);
341
295
  const name = text(source.label, `${label} label`, 120).toLowerCase();
342
296
  if (names.has(name))
343
297
  fail(`${label} duplicate label`);
@@ -345,14 +299,37 @@ function validateSourceObservations(value, label) {
345
299
  text(source.source, `${label} source`, 240);
346
300
  enumeration(source.state, ['observed', 'partial', 'unavailable'], `${label} state`);
347
301
  iso(source.observedAt, `${label} observedAt`);
302
+ if (queryCoverage) {
303
+ const keys = new Set();
304
+ const queries = array(source.queries, `${label} queries`, 500 - queryCount);
305
+ queryCount += queries.length;
306
+ for (const candidate of queries) {
307
+ const query = exact(candidate, ['owner', 'type', 'state'], `${label} query`);
308
+ text(query.owner, `${label} query owner`, 253);
309
+ enumeration(query.type, DNS_TYPES, `${label} query type`);
310
+ enumeration(query.state, ['observed', 'partial', 'unavailable'], `${label} query state`);
311
+ const key = `${query.owner}\u0000${query.type}`;
312
+ if (keys.has(key))
313
+ fail(`${label} duplicate query`);
314
+ keys.add(key);
315
+ if ((source.state === 'observed' && query.state !== 'observed')
316
+ || (source.state === 'unavailable' && query.state !== 'unavailable'))
317
+ fail(`${label} query state`);
318
+ }
319
+ }
348
320
  return source;
349
321
  });
350
322
  }
351
- function validateReviewMatrix(value, label, sources) {
323
+ function validateReviewMatrix(value, label, sources, queryCoverage = false) {
324
+ const rowKeys = new Set();
352
325
  for (const [rowIndex, candidate] of array(value, label, 500).entries()) {
353
326
  const row = exact(candidate, ['owner', 'type', 'state', 'observations'], `${label} row ${rowIndex + 1}`);
354
327
  text(row.owner, `${label} owner`, 253);
355
328
  enumeration(row.type, DNS_TYPES, `${label} type`);
329
+ const key = `${row.owner}\u0000${row.type}`;
330
+ if (queryCoverage && rowKeys.has(key))
331
+ fail(`${label} duplicate row`);
332
+ rowKeys.add(key);
356
333
  const observations = array(row.observations, `${label} observations`, 16);
357
334
  if (sources && observations.length !== sources.length)
358
335
  fail(`${label} source coverage`);
@@ -363,7 +340,7 @@ function validateReviewMatrix(value, label, sources) {
363
340
  const observation = exact(observationCandidate, ['label', 'source', 'state', 'values', 'ttlRange', ...(sources ? ['observedAt'] : [])], `${label} observation`);
364
341
  text(observation.label, `${label} observation label`, 120);
365
342
  text(observation.source, `${label} observation source`, 240);
366
- const state = enumeration(observation.state, ['observed', 'partial', 'unavailable'], `${label} observation state`);
343
+ const state = enumeration(observation.state, ['observed', 'partial', 'unavailable', ...(queryCoverage ? ['not_queried'] : [])], `${label} observation state`);
367
344
  const values = strings(observation.values, `${label} observation values`, 500, 16_384);
368
345
  if (sources) {
369
346
  const name = String(observation.label).toLowerCase();
@@ -371,8 +348,16 @@ function validateReviewMatrix(value, label, sources) {
371
348
  fail(`${label} duplicate observation`);
372
349
  names.add(name);
373
350
  const source = sources.find((item) => item.label === observation.label);
374
- if (!source || ['label', 'source', 'state', 'observedAt'].some((key) => observation[key] !== source[key]))
351
+ const provenance = queryCoverage ? ['label', 'source', 'observedAt'] : ['label', 'source', 'state', 'observedAt'];
352
+ if (!source || provenance.some((key) => observation[key] !== source[key]))
375
353
  fail(`${label} source provenance`);
354
+ if (queryCoverage) {
355
+ const query = source.queries.find((item) => item.owner === row.owner && item.type === row.type);
356
+ if (state !== (query?.state ?? 'not_queried'))
357
+ fail(`${label} query provenance`);
358
+ if (['not_queried', 'unavailable'].includes(state) && (values.length || observation.ttlRange !== null))
359
+ fail(`${label} unavailable values`);
360
+ }
376
361
  iso(observation.observedAt, `${label} observation time`);
377
362
  }
378
363
  if (state === 'observed') {
@@ -387,13 +372,19 @@ function validateReviewMatrix(value, label, sources) {
387
372
  fail(`${label} TTL range`);
388
373
  }
389
374
  }
390
- const expected = complete < 2 ? 'insufficient' : signatures.size === 1 ? 'aligned' : 'different';
375
+ const expected = complete < 2 || (queryCoverage && complete !== observations.length) ? 'insufficient' : signatures.size === 1 ? 'aligned' : 'different';
391
376
  if (row.state !== expected)
392
377
  fail(`${label} state`);
393
378
  }
379
+ if (queryCoverage && sources) {
380
+ const expectedKeys = new Set(sources.flatMap((source) => source.queries.map((item) => `${item.owner}\u0000${item.type}`)));
381
+ if (rowKeys.size !== expectedKeys.size || [...rowKeys].some((key) => !expectedKeys.has(key)))
382
+ fail(`${label} query coverage`);
383
+ }
394
384
  }
395
385
  function validateDomainChangeReview(value, label, version) {
396
386
  const retainedTimes = version >= 2;
387
+ const queryCoverage = version >= 3;
397
388
  const review = exact(value, ['schema', 'version', 'generatedAt', 'domain', 'state', 'authoritativeRecordMatrix', 'resolverDivergenceMatrix', 'dnssecAutomation', 'acmeDependencies', 'certificate', 'services', 'hsts', 'gate', 'limitations', ...(retainedTimes ? ['sourceObservations'] : [])], label);
398
389
  if (review.schema !== DOMAIN_CHANGE_REVIEW_SCHEMA || review.version !== version)
399
390
  fail(label);
@@ -401,8 +392,8 @@ function validateDomainChangeReview(value, label, version) {
401
392
  domain(review.domain, `${label} domain`);
402
393
  enumeration(review.state, ['ready', 'review'], `${label} state`);
403
394
  const sources = retainedTimes ? exact(review.sourceObservations, ['authorities', 'resolvers'], `${label} source observations`) : null;
404
- validateReviewMatrix(review.authoritativeRecordMatrix, `${label} authority matrix`, sources ? validateSourceObservations(sources.authorities, `${label} authority sources`) : undefined);
405
- validateReviewMatrix(review.resolverDivergenceMatrix, `${label} resolver matrix`, sources ? validateSourceObservations(sources.resolvers, `${label} resolver sources`) : undefined);
395
+ validateReviewMatrix(review.authoritativeRecordMatrix, `${label} authority matrix`, sources ? validateSourceObservations(sources.authorities, `${label} authority sources`, queryCoverage) : undefined, queryCoverage);
396
+ validateReviewMatrix(review.resolverDivergenceMatrix, `${label} resolver matrix`, sources ? validateSourceObservations(sources.resolvers, `${label} resolver sources`, queryCoverage) : undefined, queryCoverage);
406
397
  const automation = exact(review.dnssecAutomation, ['state', 'cdsObserved', 'cdnskeyObserved', 'csyncObserved', 'conflictingTypes', 'detail'], `${label} DNSSEC automation`);
407
398
  enumeration(automation.state, ['not_observed', 'conflict', 'partial', 'review_ready'], `${label} DNSSEC automation`);
408
399
  boolean(automation.cdsObserved, `${label} CDS observed`);
@@ -514,7 +505,7 @@ function validatePlannedAssurance(value, label) {
514
505
  strings(assurance.limitations, `${label} limitations`, 8, 600);
515
506
  return assurance;
516
507
  }
517
- function expectedDomainChangeSummary(before, after) {
508
+ function expectedDomainChangeSummary(before, after, queryCoverage) {
518
509
  const rows = (review) => new Map(review.authoritativeRecordMatrix.map((row) => [`${row.owner}\u0000${row.type}`, row]));
519
510
  const beforeRows = rows(before);
520
511
  const afterRows = rows(after);
@@ -526,7 +517,7 @@ function expectedDomainChangeSummary(before, after) {
526
517
  };
527
518
  const rowEvidence = (review, row) => {
528
519
  if (!row)
529
- return { state: collectionComplete(review) ? 'complete' : 'unavailable', values: [] };
520
+ return { state: !queryCoverage && collectionComplete(review) ? 'complete' : 'unavailable', values: [] };
530
521
  const observations = row.observations;
531
522
  if (observations.some((item) => item.state !== 'observed'))
532
523
  return { state: 'partial', values: [] };
@@ -537,18 +528,21 @@ function expectedDomainChangeSummary(before, after) {
537
528
  return { state: 'complete', values: [...new Set(observations.flatMap((item) => item.values))].sort() };
538
529
  };
539
530
  const changed = [];
531
+ const reasons = queryCoverage && keys.length > 500 ? ['The comparison exceeds 500 owner and record-type pairs.'] : [];
540
532
  for (const key of keys.slice(0, 500)) {
541
533
  const left = rowEvidence(before, beforeRows.get(key));
542
534
  const right = rowEvidence(after, afterRows.get(key));
543
535
  const [owner, type] = key.split('\u0000');
544
536
  if (left.state !== 'complete' || right.state !== 'complete') {
537
+ if (queryCoverage)
538
+ reasons.push(`Comparison evidence is incomplete for ${owner} ${type}.`);
545
539
  continue;
546
540
  }
547
541
  else if (!sameValues(left.values, right.values)) {
548
542
  changed.push({ owner: owner ?? '', type: type ?? '', beforeValues: left.values, afterValues: right.values });
549
543
  }
550
544
  }
551
- return changed;
545
+ return { changed, reasons };
552
546
  }
553
547
  function validateDomainChangePacket(value) {
554
548
  const root = exact(value, ['schema', 'version', 'generatedAt', 'domain', 'reference', 'state', 'gate', 'summary', 'evidence', 'limitations', 'integrity'], 'Domain change packet');
@@ -590,13 +584,14 @@ function validateDomainChangePacket(value) {
590
584
  const expectedReasons = [
591
585
  ...pre.gate.reasons,
592
586
  ].map((reason) => `Pre-change evidence: ${reason}`);
593
- expectedReasons.push(...post.gate.reasons.map((reason) => `Post-change evidence: ${reason}`), ...(assuranceResult.review.reasons.map((reason) => `Change plan: ${reason}`)));
587
+ const comparison = expectedDomainChangeSummary(pre, post, reviewVersion >= 3);
588
+ expectedReasons.push(...post.gate.reasons.map((reason) => `Post-change evidence: ${reason}`), ...(assuranceResult.review.reasons.map((reason) => `Change plan: ${reason}`)), ...comparison.reasons);
594
589
  const boundedReasons = expectedReasons.slice(0, 100);
595
590
  if (!sameValues(gateReasons, boundedReasons)
596
591
  || gate.pass !== (boundedReasons.length === 0)
597
592
  || root.state !== (boundedReasons.length === 0 ? 'ready' : 'review'))
598
593
  fail('Domain change packet gate');
599
- const expectedChanges = expectedDomainChangeSummary(pre, post);
594
+ const expectedChanges = comparison.changed;
600
595
  if (changed.length !== expectedChanges.length || changed.some((candidate, index) => {
601
596
  const item = candidate;
602
597
  const expected = expectedChanges[index];
@@ -612,7 +607,9 @@ export function validateSignedDigestArtifactStructure(schema, value) {
612
607
  if (value.schema !== schema)
613
608
  fail('Signed review artefact');
614
609
  if (schema === ACQUISITION_DECISION_PACKET_SCHEMA)
615
- validateAcquisition(value);
610
+ validateAcquisitionDecisionPacketStructure(value);
611
+ else if (schema === MANAGED_INDICATOR_SET_SCHEMA)
612
+ validateManagedIndicatorSet(value);
616
613
  else if (schema === LOOKUP_CLAIM_PASSPORT_SCHEMA)
617
614
  validateLookupClaimPassport(value);
618
615
  else if (schema === BULK_DOMAIN_COMPARISON_SCHEMA)
@@ -1,4 +1,5 @@
1
1
  import { Buffer } from 'node:buffer';
2
+ import { MANAGED_INDICATOR_SET_SCHEMA, MANAGED_INDICATOR_SET_VERSION } from "../packages/contracts/analyst-interchange.mjs";
2
3
  import { createHash } from 'node:crypto';
3
4
  import { readEditableCaseExport } from "../packages/cases/case-export-input.mjs";
4
5
  import { boundedJsonLimitsForBytes, parseBoundedJsonObject } from "./bounded-json.mjs";
@@ -18,7 +19,7 @@ import { INVESTIGATION_MANIFEST_SCHEMA, SUPPORTED_INVESTIGATION_MANIFEST_VERSION
18
19
  import { verifyCliCasePack } from "./case-pack.mjs";
19
20
  import { LOOKUP_EVIDENCE_PORTABLE_MAX_BYTES, LOOKUP_EVIDENCE_SCHEMA, SUPPORTED_LOOKUP_EVIDENCE_SCHEMA_VERSIONS, } from "../lib/evidence-export.mjs";
20
21
  export const OFFLINE_ARTIFACT_VERIFICATION_SCHEMA = 'whoisleuth.offline-artifact-verification';
21
- export const OFFLINE_ARTIFACT_VERIFICATION_VERSION = 4;
22
+ export const OFFLINE_ARTIFACT_VERIFICATION_VERSION = 5;
22
23
  export const MAX_OFFLINE_ARTIFACT_BYTES = Math.max(MAX_DOMAIN_CONTROL_MANIFEST_BYTES, MAX_ENCRYPTED_WORKSPACE_ARCHIVE_BYTES);
23
24
  export const MAX_OFFLINE_PASSPHRASE_FILE_BYTES = 1024;
24
25
  export class UnsupportedOfflineArtifactError extends TypeError {
@@ -36,6 +37,7 @@ function currentCanonicalizationRoutes(versions) {
36
37
  })));
37
38
  }
38
39
  const SIGNED_ARTIFACT_ROUTES = Object.freeze({
40
+ [MANAGED_INDICATOR_SET_SCHEMA]: currentCanonicalizationRoutes([MANAGED_INDICATOR_SET_VERSION]),
39
41
  [ACQUISITION_DECISION_PACKET_SCHEMA]: currentCanonicalizationRoutes(SUPPORTED_ACQUISITION_DECISION_PACKET_VERSIONS),
40
42
  [LOOKUP_CLAIM_PASSPORT_SCHEMA]: currentCanonicalizationRoutes([LOOKUP_CLAIM_PASSPORT_VERSION]),
41
43
  [BULK_DOMAIN_COMPARISON_SCHEMA]: currentCanonicalizationRoutes(SUPPORTED_BULK_DOMAIN_COMPARISON_EXPORT_VERSIONS),
@@ -588,8 +590,15 @@ export function formatOfflineArtifactVerification(report) {
588
590
  }
589
591
  if (report.package) {
590
592
  lines.push(`Package bytes: ${report.package.digestSha256}`, 'Storage: unchanged; inspection only', 'Audience: private; review selected file contents before sharing', 'Signature trust: not checked', 'Trusted timestamp: not checked', 'Factual accuracy: not established');
591
- for (const entry of report.package.entries)
593
+ for (const entry of report.package.entries) {
592
594
  lines.push(`${entry.id}: ${entry.state} · ${entry.byteLength} bytes · ${entry.mediaType}${entry.issue ? ` · ${entry.issue}` : ''}`);
595
+ if (entry.imageDerivation) {
596
+ const declaration = entry.imageDerivation;
597
+ lines.push(` Declared image derivation: ${declaration.method}; ${declaration.operations.join(', ')}`, ` Parent: ${declaration.source.digestSha256} · ${declaration.source.byteLength} bytes`);
598
+ }
599
+ else if (entry.mediaType.startsWith('image/'))
600
+ lines.push(' Editing history: not declared');
601
+ }
593
602
  for (const link of report.package.links)
594
603
  lines.push(`Source link ${link.capsuleEntryId}: ${link.state}${link.sourceEntryId ? ` (${link.sourceEntryId})` : ''}`);
595
604
  for (const capture of report.package.captureManifests) {
@@ -0,0 +1,56 @@
1
+ import {} from "./command-argument-grammar.mjs";
2
+ import { CliUsageError } from "./errors.mjs";
3
+ import { MAX_INVESTIGATION_MANIFEST_ARTIFACTS } from "../packages/contracts/investigation-package-limits.mjs";
4
+ import { terminalOptions, jsonOutput, singleInputAction } from "./argument-values.mjs";
5
+ function parseManifestArguments(parsed) {
6
+ const sources = parsed.positionalValues('artefacts');
7
+ if (sources.some((source) => !source))
8
+ throw new CliUsageError(`manifest requires from 1 to ${MAX_INVESTIGATION_MANIFEST_ARTIFACTS} artefact files.`);
9
+ if (new Set(sources).size !== sources.length)
10
+ throw new CliUsageError('manifest artefact files must be different.');
11
+ const configurationDigestSha256 = parsed.optionValue('--configuration-digest');
12
+ if (configurationDigestSha256 !== null && !/^sha256:[a-f0-9]{64}$/u.test(configurationDigestSha256)) {
13
+ throw new CliUsageError('--configuration-digest requires a sha256: hexadecimal digest.');
14
+ }
15
+ return {
16
+ action: 'manifest',
17
+ sources,
18
+ workflow: parsed.optionValue('--workflow'),
19
+ configurationDigestSha256,
20
+ ...(parsed.optionValue('--passphrase-file')
21
+ ? { passphraseSource: parsed.optionValue('--passphrase-file') }
22
+ : {}),
23
+ ...(parsed.hasOption('--package') ? { package: true } : {}),
24
+ ...(parsed.hasOption('--bagit') ? { bagit: true } : {}),
25
+ ...(parsed.optionValue('--folder') ? { folder: parsed.optionValue('--folder') } : {}),
26
+ output: jsonOutput(parsed),
27
+ ...terminalOptions(parsed),
28
+ };
29
+ }
30
+ function parseSharingReviewArguments(parsed) {
31
+ const purpose = parsed.optionValue('--purpose').replace(/\s+/gu, ' ').trim();
32
+ if (!purpose || purpose.length > 200)
33
+ throw new CliUsageError('--purpose is limited to 200 characters.');
34
+ return {
35
+ action: 'sharing-review',
36
+ source: parsed.positionalValue('source'),
37
+ output: jsonOutput(parsed),
38
+ marking: parsed.optionValue('--marking'),
39
+ recipientScope: parsed.optionValue('--recipient-scope'),
40
+ purpose,
41
+ humanReviewed: parsed.hasOption('--human-reviewed'),
42
+ personalDataReviewed: parsed.hasOption('--personal-data-reviewed'),
43
+ redactionsConfirmed: parsed.hasOption('--redactions-confirmed'),
44
+ ...terminalOptions(parsed),
45
+ };
46
+ }
47
+ export const ASSURANCE_ARGUMENT_PARSERS = Object.freeze({
48
+ manifest: parseManifestArguments,
49
+ 'map-observations': (parsed) => singleInputAction('map-observations', parsed),
50
+ 'oam-export': (parsed) => singleInputAction('oam-export', parsed),
51
+ 'ct-intake': (parsed) => singleInputAction('ct-intake', parsed),
52
+ 'domain-control': (parsed) => singleInputAction('domain-control', parsed),
53
+ assurance: (parsed) => singleInputAction('assurance', parsed),
54
+ 'change-packet': (parsed) => singleInputAction('change-packet', parsed),
55
+ 'sharing-review': parseSharingReviewArguments,
56
+ });