@slicedearth/whoisleuth-cli 2.2.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (282) hide show
  1. package/NOTICE +6 -0
  2. package/README.md +20 -15
  3. package/bin/whoisleuth.mjs +8 -1
  4. package/cli/archive-content-digest.mjs +14 -0
  5. package/cli/archive-inspect.mjs +20 -53
  6. package/cli/arguments.mjs +454 -1748
  7. package/cli/artifact-validation/case-response.mjs +66 -35
  8. package/cli/artifact-validation/investigation-capsule.mjs +52 -31
  9. package/cli/artifact-validation/lookup-evidence.mjs +12 -6
  10. package/cli/artifact-validation/signed-review.mjs +112 -72
  11. package/cli/artifact-validation/structure-primitives.mjs +1 -105
  12. package/cli/artifact-verify.mjs +90 -24
  13. package/cli/assurance-command-runner.mjs +39 -4
  14. package/cli/bagit-folder.mjs +57 -0
  15. package/cli/bagit-review.mjs +23 -0
  16. package/cli/bounded-json.mjs +1 -1
  17. package/cli/bulk-checkpoint.mjs +34 -3
  18. package/cli/bulk-command-runner.mjs +2 -0
  19. package/cli/bulk-output.mjs +29 -3
  20. package/cli/case-command.mjs +195 -0
  21. package/cli/case-pack.mjs +66 -111
  22. package/cli/command-argument-grammar.mjs +215 -0
  23. package/cli/command-reference.mjs +342 -285
  24. package/cli/completion.mjs +303 -80
  25. package/cli/config-profile.mjs +7 -5
  26. package/cli/ct-event-intake.mjs +12 -12
  27. package/cli/discover.mjs +4 -3
  28. package/cli/discovery-observation-snapshot.mjs +59 -18
  29. package/cli/discovery-scan-command-runner.mjs +21 -0
  30. package/cli/discovery-scan.mjs +13 -4
  31. package/cli/discovery-workflow.mjs +1 -1
  32. package/cli/domain-control-monitor.mjs +50 -20
  33. package/cli/domain-control-observations.mjs +149 -102
  34. package/cli/errors.mjs +33 -1
  35. package/cli/evidence-command-runner.mjs +16 -5
  36. package/cli/evidence-signing.mjs +2 -2
  37. package/cli/external-observation-mapping.mjs +7 -8
  38. package/cli/formatters/terminal-lookup.mjs +18 -46
  39. package/cli/formatters/terminal-shared.mjs +5 -3
  40. package/cli/formatters/terminal-technology.mjs +114 -0
  41. package/cli/inline-command-families.mjs +2 -0
  42. package/cli/input.mjs +116 -0
  43. package/cli/interactive-launcher.mjs +2 -160
  44. package/cli/interchange-report.mjs +18 -8
  45. package/cli/investigation-artifacts.mjs +176 -0
  46. package/cli/investigation-checkpoint.mjs +10 -0
  47. package/cli/investigation-folder.mjs +114 -0
  48. package/cli/investigation-manifest.mjs +1 -112
  49. package/cli/investigation-package-review.mjs +103 -0
  50. package/cli/investigation-plan.mjs +32 -19
  51. package/cli/investigation-run.mjs +188 -33
  52. package/cli/local-document-checkpoint.mjs +170 -0
  53. package/cli/lookalike-calibration.mjs +7 -2
  54. package/cli/lookup-brief.mjs +4 -2
  55. package/cli/lookup-browser-view.mjs +1 -1
  56. package/cli/lookup-browser.mjs +5 -12
  57. package/cli/lookup-command-runner.mjs +17 -6
  58. package/cli/lookup-diff.mjs +37 -3
  59. package/cli/lookup-plan.mjs +11 -5
  60. package/cli/lookup-reconcile.mjs +10 -8
  61. package/cli/mail-header-review.mjs +467 -0
  62. package/cli/mail-review.mjs +66 -14
  63. package/cli/manual.mjs +1 -0
  64. package/cli/network-command-runner.mjs +2 -0
  65. package/cli/output-file.mjs +31 -5
  66. package/cli/registry-doctor.mjs +3 -1
  67. package/cli/registry-support.mjs +1 -1
  68. package/cli/retained-artifact-diff.mjs +2 -2
  69. package/cli/review-command-runner.mjs +70 -0
  70. package/cli/risk-calibration.mjs +19 -17
  71. package/cli/runner.mjs +62 -7
  72. package/cli/sharing-review.mjs +21 -34
  73. package/cli/signer-trust.mjs +104 -0
  74. package/cli/source-reliability.mjs +74 -83
  75. package/cli/terminal-input.mjs +168 -0
  76. package/cli/workflow-command-runner.mjs +68 -13
  77. package/docs/cli-reference.md +77 -24
  78. package/docs/cli.md +300 -25
  79. package/lib/abort.mjs +4 -1
  80. package/lib/availability.mjs +161 -112
  81. package/lib/bounded-file.mjs +3 -1
  82. package/lib/bounded-html-document.mjs +125 -0
  83. package/lib/bounded-json.mjs +51 -8
  84. package/lib/browser-library-profile.mjs +42 -51
  85. package/lib/browser-library-worker.mjs +112 -0
  86. package/lib/client-behavior-profile.mjs +3 -3
  87. package/lib/ct-search.mjs +11 -4
  88. package/lib/dns-convergence-review.mjs +1 -0
  89. package/lib/dns-delegation-health.mjs +24 -14
  90. package/lib/dns-inheritance-review.mjs +217 -0
  91. package/lib/dns-intelligence.mjs +73 -26
  92. package/lib/dns-mx.mjs +1 -1
  93. package/lib/dnssec-chain-validation.mjs +6 -0
  94. package/lib/dnssec-evidence-validation.mjs +11 -3
  95. package/lib/domain-change-packet.mjs +49 -16
  96. package/lib/domain-change-review.mjs +14 -8
  97. package/lib/domain-control-flight-recorder.mjs +221 -117
  98. package/lib/domain-control-manifest.mjs +129 -69
  99. package/lib/domain-posture-analysis.mjs +20 -7
  100. package/lib/domain-posture-parsers.mjs +201 -8
  101. package/lib/domain-posture.mjs +147 -29
  102. package/lib/evidence-export-privacy.mjs +561 -0
  103. package/lib/evidence-export.mjs +11 -543
  104. package/lib/evidence-report.mjs +5 -3
  105. package/lib/favicon.mjs +117 -41
  106. package/lib/generated/cisa-kev-catalog.mjs +12 -4
  107. package/lib/generated/registrar-standing-catalogue.mjs +7 -7
  108. package/lib/generated/retire-browser-catalog.mjs +2335 -940
  109. package/lib/hostname.mjs +2 -38
  110. package/lib/html-signals.mjs +111 -108
  111. package/lib/http-evidence-bounds.mjs +2 -1
  112. package/lib/http-intelligence.mjs +39 -26
  113. package/lib/local-geoip-evidence.mjs +19 -3
  114. package/lib/lookup-cache.mjs +34 -11
  115. package/lib/lookup-child-profile-contract.mjs +24 -15
  116. package/lib/lookup-diagnostics.mjs +24 -1
  117. package/lib/lookup-progress.mjs +30 -13
  118. package/lib/lookup-response-contract.mjs +1048 -0
  119. package/lib/lookup-response.mjs +8 -10
  120. package/lib/lookup-source-progress.mjs +40 -14
  121. package/lib/lookup.mjs +48 -27
  122. package/lib/nameserver-preflight-review.mjs +1 -0
  123. package/lib/observed-network-context.mjs +43 -27
  124. package/lib/outbound-request-bounds.mjs +4 -1
  125. package/lib/page-fingerprints.mjs +69 -133
  126. package/lib/page-role-profile.mjs +12 -8
  127. package/lib/perceptual-hash.mjs +43 -14
  128. package/lib/rdap-bootstrap.mjs +72 -22
  129. package/lib/rdap-capabilities.mjs +1 -1
  130. package/lib/rdap-client.mjs +15 -7
  131. package/lib/rdap-normalization.mjs +33 -18
  132. package/lib/rdap-search-workbench.mjs +1 -1
  133. package/lib/rdap-transport.mjs +47 -20
  134. package/lib/rdap-types.mjs +2 -1
  135. package/lib/rdap.mjs +18 -3
  136. package/lib/registrable-domain.mjs +21 -1
  137. package/lib/registrar-standing-contract.mjs +1 -0
  138. package/lib/registry-capabilities.mjs +5 -1
  139. package/lib/registry-capability-catalogue.mjs +2 -1
  140. package/lib/registry-dates.mjs +2 -190
  141. package/lib/registry-insights.mjs +85 -34
  142. package/lib/response-policy.mjs +86 -48
  143. package/lib/risk-calibration-summary.mjs +18 -6
  144. package/lib/rpki-evidence.mjs +8 -5
  145. package/lib/safe-fetch.mjs +54 -47
  146. package/lib/security-txt.mjs +42 -7
  147. package/lib/service-binding-dns.mjs +8 -0
  148. package/lib/smtp-transport-review.mjs +27 -15
  149. package/lib/sslbl-certificates.generated.mjs +136 -135
  150. package/lib/static-html-analysis.mjs +178 -151
  151. package/lib/structured-data-identity.mjs +1 -0
  152. package/lib/technology-markup.mjs +100 -0
  153. package/lib/threat-intelligence-manifests.mjs +11 -139
  154. package/lib/threat-intelligence-runtime.mjs +24 -52
  155. package/lib/threat-intelligence-targets.mjs +6 -34
  156. package/lib/threat-intelligence-types.mjs +39 -0
  157. package/lib/typosquat-generator.mjs +19 -15
  158. package/lib/urlhaus-intelligence.mjs +8 -3
  159. package/lib/urlscan-intelligence.mjs +1 -1
  160. package/lib/website-security-posture.mjs +30 -6
  161. package/lib/website-technology.mjs +153 -45
  162. package/lib/whois-authority.mjs +14 -2
  163. package/lib/whois-chain.mjs +9 -2
  164. package/lib/whois-common-formats.mjs +5 -5
  165. package/lib/whois-contracts.mjs +2 -1
  166. package/lib/whois-dialects.mjs +2 -2
  167. package/lib/whois-parser-support.mjs +8 -1
  168. package/lib/whois-transport.mjs +23 -7
  169. package/lib/zone-intent-review.mjs +16 -6
  170. package/package.json +2 -1
  171. package/packages/cases/case-attachment-model.mjs +83 -0
  172. package/packages/cases/case-evidence-model.mjs +156 -62
  173. package/packages/cases/case-export-input.mjs +53 -0
  174. package/packages/cases/case-migration-model.mjs +127 -44
  175. package/packages/cases/case-model.mjs +1 -0
  176. package/packages/cases/case-recheck-model.mjs +91 -0
  177. package/packages/cases/case-record-contracts.mjs +5 -20
  178. package/packages/cases/case-record-core.mjs +12 -22
  179. package/packages/cases/case-record-decisions.mjs +72 -0
  180. package/packages/cases/case-record-model.mjs +1 -0
  181. package/packages/cases/case-record-operations.mjs +95 -31
  182. package/packages/cases/case-record-projection.mjs +221 -0
  183. package/packages/cases/case-report.mjs +70 -54
  184. package/packages/cases/case-response-actions.mjs +654 -0
  185. package/packages/cases/case-response-model.mjs +64 -1115
  186. package/packages/cases/case-response-outcomes.mjs +317 -0
  187. package/packages/cases/case-response-packet-render.mjs +153 -0
  188. package/packages/cases/case-response-packet-types.mjs +1 -0
  189. package/packages/cases/case-response-packet-vocabulary.mjs +48 -0
  190. package/packages/cases/case-response-packet.mjs +74 -572
  191. package/packages/cases/case-response-records.mjs +94 -0
  192. package/packages/cases/case-response-review-inputs.mjs +371 -0
  193. package/packages/cases/case-response-values.mjs +85 -0
  194. package/packages/cases/case-review-package.mjs +45 -0
  195. package/packages/cases/case-selection.mjs +46 -0
  196. package/packages/cases/case-storage-model.mjs +31 -2
  197. package/packages/cases/response-route-freshness.mjs +16 -0
  198. package/packages/comparison/capture-context.mjs +61 -0
  199. package/packages/comparison/comparison-ledger-bulk.mjs +13 -11
  200. package/packages/comparison/page-similarity.mjs +5 -2
  201. package/packages/comparison/relationship-evidence.mjs +85 -8
  202. package/packages/comparison/relationship-provenance.mjs +93 -0
  203. package/packages/contracts/analyst-interchange.mjs +216 -0
  204. package/packages/contracts/analyst-review-state-contract.mjs +11 -0
  205. package/packages/contracts/analyst-review-state.mjs +5 -4
  206. package/packages/contracts/case-portability.mjs +366 -83
  207. package/packages/contracts/case-views-contract.mjs +12 -0
  208. package/packages/contracts/case-views.mjs +26 -0
  209. package/packages/contracts/cli-command-semantics.mjs +94 -0
  210. package/packages/contracts/cli-lookup.mjs +0 -1
  211. package/packages/contracts/domain-control-flight-recorder.mjs +80 -42
  212. package/packages/contracts/domain-control-manifest.mjs +127 -73
  213. package/packages/contracts/domain-control-monitor.mjs +48 -45
  214. package/packages/contracts/domain-control-review.mjs +179 -121
  215. package/packages/contracts/domain-name.mjs +21 -0
  216. package/packages/contracts/external-observation-interchange.mjs +2 -1
  217. package/packages/contracts/extracted-domain-lifecycle.mjs +11 -32
  218. package/packages/contracts/http-url.mjs +2 -0
  219. package/packages/contracts/investigation-package-limits.mjs +24 -0
  220. package/packages/contracts/investigation-portability.mjs +56 -27
  221. package/packages/contracts/investigation-run.mjs +16 -0
  222. package/packages/contracts/ip-address.mjs +34 -0
  223. package/packages/contracts/lookup-evidence.mjs +35 -0
  224. package/packages/contracts/offline-comparison.mjs +1 -2
  225. package/packages/contracts/page-fingerprints.mjs +15 -0
  226. package/packages/contracts/risk-calibration.mjs +15 -11
  227. package/packages/contracts/schema-compatibility.mjs +1 -1
  228. package/packages/contracts/schema-lifecycle.mjs +83 -153
  229. package/packages/contracts/selected-file-limits.mjs +6 -0
  230. package/packages/contracts/vulnerability-identifiers.mjs +5 -0
  231. package/packages/contracts/web-capture.mjs +10 -0
  232. package/packages/contracts/workspace-portability.mjs +183 -211
  233. package/packages/evidence/artifact-integrity.mjs +8 -2
  234. package/packages/evidence/artifact-structure.mjs +105 -0
  235. package/packages/evidence/decision-fact.mjs +4 -1
  236. package/packages/evidence/domain-control-runtime.mjs +201 -33
  237. package/packages/evidence/domain-posture-context.mjs +79 -0
  238. package/packages/evidence/image-regions.mjs +56 -0
  239. package/packages/evidence/latest-observations.mjs +28 -0
  240. package/packages/evidence/lookup-target.mjs +104 -0
  241. package/packages/evidence/observation.mjs +16 -1
  242. package/packages/evidence/passphrase-encryption.mjs +44 -0
  243. package/packages/evidence/record-identity.mjs +72 -0
  244. package/packages/evidence/registry-dates.mjs +190 -0
  245. package/packages/evidence/retained-file.mjs +69 -0
  246. package/packages/evidence/review-age.mjs +14 -0
  247. package/packages/interchange/bagit.mjs +342 -0
  248. package/packages/interchange/bounded-zip-extraction.mjs +278 -0
  249. package/packages/interchange/crc32.mjs +17 -0
  250. package/packages/interchange/external-findings-import.mjs +92 -51
  251. package/packages/interchange/web-capture-import.mjs +382 -0
  252. package/packages/investigation/bulk-mail-exposure.mjs +2 -1
  253. package/packages/investigation/investigation-bagit.mjs +14 -0
  254. package/packages/investigation/investigation-capsule.mjs +33 -5
  255. package/packages/investigation/investigation-manifest.mjs +200 -0
  256. package/packages/investigation/investigation-package-crypto.mjs +98 -0
  257. package/packages/investigation/investigation-package.mjs +200 -0
  258. package/packages/investigation/lookup-asset-graph-inputs.mjs +38 -0
  259. package/packages/investigation/lookup-asset-graph.mjs +193 -165
  260. package/packages/investigation/lookup-claim-readiness.mjs +18 -0
  261. package/packages/investigation/lookup-investigation-brief.mjs +3 -3
  262. package/packages/investigation/lookup-task-guidance.mjs +58 -0
  263. package/packages/monitoring/analyst-review-state.mjs +38 -90
  264. package/packages/workspace/brand-profile-model.mjs +276 -22
  265. package/packages/workspace/bulk-columns.mjs +17 -0
  266. package/packages/workspace/bulk-review-model.mjs +23 -11
  267. package/packages/workspace/bulk-session-model.mjs +100 -24
  268. package/packages/workspace/bulk-sort.mjs +1 -0
  269. package/packages/workspace/case-views.mjs +102 -0
  270. package/packages/workspace/detection-rule-model.mjs +70 -22
  271. package/packages/workspace/hostile-input.mjs +29 -8
  272. package/packages/workspace/investigation-template-model.mjs +19 -4
  273. package/packages/workspace/page-baseline.mjs +18 -15
  274. package/packages/workspace/relationship-observation-model.mjs +41 -23
  275. package/packages/workspace/watchlist-history.mjs +2 -2
  276. package/packages/workspace/watchlist-store.mjs +8 -2
  277. package/packages/workspace/website-snapshot-model.mjs +164 -26
  278. package/packages/workspace/workspace-archive-crypto.mjs +41 -61
  279. package/packages/workspace/workspace-archive.mjs +87 -21
  280. package/third-party-notices.txt +29 -2
  281. package/cli/evidence-command-arguments.mjs +0 -156
  282. package/packages/investigation/bulk-review-export.mjs +0 -63
@@ -1,11 +1,16 @@
1
1
  import { exactKeys, requireBoundedString, requireDomainName, requireIsoTimestamp, requireRecord, } from "./bounded-contract-normalizers.mjs";
2
2
  import { DOMAIN_ASSURANCE_INPUT_SCHEMA, buildDomainAssurance, } from "./domain-assurance.mjs";
3
- import { DOMAIN_CHANGE_INPUT_SCHEMA, reviewDomainChange, } from "./domain-change-review.mjs";
3
+ import { DOMAIN_CHANGE_INPUT_SCHEMA, DOMAIN_CHANGE_REVIEW_VERSION, reviewDomainChange, } from "./domain-change-review.mjs";
4
4
  import { SORTED_JSON_V2, sha256ArtifactDigestV2 } from "../packages/evidence/artifact-integrity.mjs";
5
5
  export const DOMAIN_CHANGE_PACKET_INPUT_SCHEMA = 'whoisleuth.domain-change-packet.input';
6
6
  export const DOMAIN_CHANGE_PACKET_SCHEMA = 'whoisleuth.domain-change-packet';
7
7
  export const DOMAIN_CHANGE_PACKET_INPUT_VERSION = 1;
8
- export const DOMAIN_CHANGE_PACKET_VERSION = 2;
8
+ export const DOMAIN_CHANGE_PACKET_VERSION = 3;
9
+ export const DOMAIN_CHANGE_PACKET_REVIEW_VERSIONS = Object.freeze({
10
+ 2: 1,
11
+ [DOMAIN_CHANGE_PACKET_VERSION]: DOMAIN_CHANGE_REVIEW_VERSION,
12
+ });
13
+ export const SUPPORTED_DOMAIN_CHANGE_PACKET_VERSIONS = Object.freeze(Object.keys(DOMAIN_CHANGE_PACKET_REVIEW_VERSIONS).map(Number));
9
14
  export const MAX_DOMAIN_CHANGE_PACKET_INPUT_BYTES = 6 * 1024 * 1024;
10
15
  const ROOT_KEYS = new Set([
11
16
  'schema',
@@ -38,23 +43,50 @@ function changeSummary(before, after) {
38
43
  const beforeRows = rows(before);
39
44
  const afterRows = rows(after);
40
45
  const keys = [...new Set([...beforeRows.keys(), ...afterRows.keys()])].sort();
41
- return Object.freeze(keys.slice(0, 500).flatMap((key) => {
46
+ const authorityCollectionComplete = (review) => {
47
+ const representative = review.authoritativeRecordMatrix[0];
48
+ return Boolean(representative
49
+ && representative.observations.length >= 2
50
+ && representative.observations.every((item) => item.state === 'observed'));
51
+ };
52
+ const rowEvidence = (review, row) => {
53
+ if (!row)
54
+ return Object.freeze({
55
+ state: authorityCollectionComplete(review) ? 'complete' : 'unavailable',
56
+ values: Object.freeze([]),
57
+ });
58
+ if (row.observations.some((item) => item.state !== 'observed')) {
59
+ return Object.freeze({ state: 'partial', values: Object.freeze([]) });
60
+ }
61
+ if (row.state === 'different')
62
+ return Object.freeze({ state: 'inconsistent', values: Object.freeze([]) });
63
+ if (row.state !== 'aligned')
64
+ return Object.freeze({ state: 'insufficient', values: Object.freeze([]) });
65
+ return Object.freeze({
66
+ state: 'complete',
67
+ values: Object.freeze([...new Set(row.observations.flatMap((item) => item.values))].sort()),
68
+ });
69
+ };
70
+ const changed = [];
71
+ for (const key of keys.slice(0, 500)) {
42
72
  const left = beforeRows.get(key);
43
73
  const right = afterRows.get(key);
44
- const leftValues = left?.observations.flatMap((item) => item.values) ?? [];
45
- const rightValues = right?.observations.flatMap((item) => item.values) ?? [];
46
- const beforeValues = [...new Set(leftValues)].sort();
47
- const afterValues = [...new Set(rightValues)].sort();
48
- if (JSON.stringify(beforeValues) === JSON.stringify(afterValues))
49
- return [];
50
74
  const [owner, type] = key.split('\u0000');
51
- return [Object.freeze({
52
- owner: owner ?? '',
53
- type: type ?? '',
54
- beforeValues: Object.freeze(beforeValues),
55
- afterValues: Object.freeze(afterValues),
56
- })];
57
- }));
75
+ const beforeEvidence = rowEvidence(before, left);
76
+ const afterEvidence = rowEvidence(after, right);
77
+ if (beforeEvidence.state !== 'complete' || afterEvidence.state !== 'complete') {
78
+ continue;
79
+ }
80
+ if (JSON.stringify(beforeEvidence.values) === JSON.stringify(afterEvidence.values))
81
+ continue;
82
+ changed.push(Object.freeze({
83
+ owner: owner ?? '',
84
+ type: type ?? '',
85
+ beforeValues: beforeEvidence.values,
86
+ afterValues: afterEvidence.values,
87
+ }));
88
+ }
89
+ return Object.freeze(changed);
58
90
  }
59
91
  export async function buildDomainChangePacket(inputRaw, generatedAtValue = new Date().toISOString()) {
60
92
  const input = record(inputRaw, 'Domain change packet input');
@@ -99,6 +131,7 @@ export async function buildDomainChangePacket(inputRaw, generatedAtValue = new D
99
131
  limitations: Object.freeze([
100
132
  'This packet is assembled only from analyst-supplied observations and planning metadata and makes no network request or configuration change.',
101
133
  'A ready result means the supplied bounded checks passed; it does not prove control, propagation completion, successful recovery, or absence of unobserved dependencies.',
134
+ 'Authoritative record-set changes are reported only when both sides contain complete consistent observations; gate reasons describe incomplete comparisons.',
102
135
  'The integrity digest detects later changes to this packet but does not authenticate its author. Use a separately managed signing key when signer authentication is required.',
103
136
  ]),
104
137
  });
@@ -1,8 +1,9 @@
1
1
  import { exactKeys, requireBoundedString, requireDomainName, requireIsoTimestamp, requireRecord, } from "./bounded-contract-normalizers.mjs";
2
2
  import { normaliseRdata } from "./zone-intent-review.mjs";
3
3
  export const DOMAIN_CHANGE_INPUT_SCHEMA = 'whoisleuth.domain-change.input';
4
+ export const DOMAIN_CHANGE_INPUT_VERSION = 1;
4
5
  export const DOMAIN_CHANGE_REVIEW_SCHEMA = 'whoisleuth.domain-change.review';
5
- export const DOMAIN_CHANGE_REVIEW_VERSION = 1;
6
+ export const DOMAIN_CHANGE_REVIEW_VERSION = 2;
6
7
  export const MAX_DOMAIN_CHANGE_VANTAGES = 16;
7
8
  export const MAX_DOMAIN_CHANGE_RECORDS = 500;
8
9
  const ROOT_KEYS = new Set(['schema', 'version', 'domain', 'authoritySnapshots', 'resolverSnapshots', 'acmeDependencies', 'certificate', 'hsts']);
@@ -89,6 +90,9 @@ function normaliseSnapshots(value, apex, label) {
89
90
  throw new TypeError(`${label} labels must be unique.`);
90
91
  return Object.freeze(snapshots);
91
92
  }
93
+ function snapshotProvenance(snapshot) {
94
+ return Object.freeze({ label: snapshot.label, source: snapshot.source, state: snapshot.state, observedAt: snapshot.observedAt });
95
+ }
92
96
  function recordMatrix(snapshots) {
93
97
  const keys = [...new Set(snapshots.flatMap((snapshot) => snapshot.records.map((item) => `${item.owner}\u0000${item.type}`)))].sort();
94
98
  return Object.freeze(keys.map((key) => {
@@ -97,9 +101,7 @@ function recordMatrix(snapshots) {
97
101
  const matches = snapshot.records.filter((item) => item.owner === recordOwner && item.type === type);
98
102
  const ttls = matches.map((item) => item.ttl).filter((ttl) => ttl !== null);
99
103
  return Object.freeze({
100
- label: snapshot.label,
101
- source: snapshot.source,
102
- state: snapshot.state,
104
+ ...snapshotProvenance(snapshot),
103
105
  values: Object.freeze([...new Set(matches.map((item) => item.value))].sort()),
104
106
  ttlRange: ttls.length ? Object.freeze({
105
107
  minimum: Math.min(...ttls),
@@ -200,7 +202,7 @@ function normaliseCertificate(value) {
200
202
  }
201
203
  function reviewCertificate(certificate) {
202
204
  if (!certificate)
203
- return Object.freeze({ state: 'not_supplied', continuity: 'unknown', findings: Object.freeze([]) });
205
+ return Object.freeze({ state: 'not_supplied', observedAt: null, continuity: 'unknown', findings: Object.freeze([]) });
204
206
  const continuity = !certificate.currentSpkiSha256 || !certificate.plannedSpkiSha256
205
207
  ? 'unknown'
206
208
  : certificate.currentSpkiSha256 === certificate.plannedSpkiSha256 ? 'retained' : 'changes';
@@ -210,7 +212,7 @@ function reviewCertificate(certificate) {
210
212
  ...(certificate.embeddedSctCount === 0
211
213
  ? ['No embedded certificate-transparency timestamp was observed; this does not establish that the certificate was not logged by another mechanism.'] : []),
212
214
  ];
213
- return Object.freeze({ state: certificate.state, continuity, findings: Object.freeze(findings) });
215
+ return Object.freeze({ state: certificate.state, observedAt: certificate.observedAt, continuity, findings: Object.freeze(findings) });
214
216
  }
215
217
  function normaliseHsts(value) {
216
218
  if (value === null || value === undefined)
@@ -236,8 +238,8 @@ function serviceInventory(snapshots) {
236
238
  }
237
239
  export function reviewDomainChange(inputRaw, generatedAtValue = new Date().toISOString()) {
238
240
  const input = requireRecord(inputRaw, 'Domain change input');
239
- if (input.schema !== DOMAIN_CHANGE_INPUT_SCHEMA || input.version !== 1)
240
- throw new TypeError(`Domain change input must use ${DOMAIN_CHANGE_INPUT_SCHEMA} version 1.`);
241
+ if (input.schema !== DOMAIN_CHANGE_INPUT_SCHEMA || input.version !== DOMAIN_CHANGE_INPUT_VERSION)
242
+ throw new TypeError(`Domain change input must use ${DOMAIN_CHANGE_INPUT_SCHEMA} version ${DOMAIN_CHANGE_INPUT_VERSION}.`);
241
243
  exactKeys(input, ROOT_KEYS, 'Domain change input');
242
244
  const apex = domain(input.domain, 'domain');
243
245
  const authoritySnapshots = normaliseSnapshots(input.authoritySnapshots ?? [], apex, 'authoritySnapshots');
@@ -285,6 +287,10 @@ export function reviewDomainChange(inputRaw, generatedAtValue = new Date().toISO
285
287
  generatedAt: timestamp(generatedAtValue, 'generatedAt'),
286
288
  domain: apex,
287
289
  state: reviewRequired ? 'review' : 'ready',
290
+ sourceObservations: Object.freeze({
291
+ authorities: Object.freeze(authoritySnapshots.map(snapshotProvenance)),
292
+ resolvers: Object.freeze(resolverSnapshots.map(snapshotProvenance)),
293
+ }),
288
294
  authoritativeRecordMatrix: authorityMatrix,
289
295
  resolverDivergenceMatrix: resolverMatrix,
290
296
  dnssecAutomation: reviewDnssecAutomation(authorityMatrix),