@slicedearth/whoisleuth-cli 1.47.4 → 2.0.1

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 (236) hide show
  1. package/README.md +74 -177
  2. package/SECURITY.md +6 -3
  3. package/bin/whoisleuth.mjs +2 -1
  4. package/cli/archive-inspect.mjs +28 -10
  5. package/cli/arguments.mjs +204 -174
  6. package/cli/artifact-structure.mjs +837 -163
  7. package/cli/artifact-verify.mjs +90 -61
  8. package/cli/bounded-json.mjs +1 -1
  9. package/cli/bulk-checkpoint.mjs +33 -20
  10. package/cli/bulk.mjs +9 -2
  11. package/cli/case-pack.mjs +180 -576
  12. package/cli/ci-report.mjs +6 -1
  13. package/cli/collection-preflight.mjs +2 -2
  14. package/cli/command-catalogue.mjs +12 -3
  15. package/cli/command-reference.mjs +910 -92
  16. package/cli/completion.mjs +504 -226
  17. package/cli/config-profile.mjs +13 -3
  18. package/cli/discover.mjs +7 -1
  19. package/cli/discovery-observation-snapshot.mjs +24 -28
  20. package/cli/discovery-scan-command-runner.mjs +9 -9
  21. package/cli/discovery-scan.mjs +17 -3
  22. package/cli/discovery-snapshot.mjs +15 -7
  23. package/cli/domain-control-monitor.mjs +203 -30
  24. package/cli/domain-control-observations.mjs +15 -21
  25. package/cli/errors.mjs +8 -3
  26. package/cli/evidence-signing.mjs +10 -20
  27. package/cli/external-observation-mapping.mjs +3 -3
  28. package/cli/formatters/json.mjs +20 -32
  29. package/cli/formatters/terminal.mjs +583 -22
  30. package/cli/input.mjs +7 -2
  31. package/cli/interactive-launcher.mjs +226 -0
  32. package/cli/interchange-report.mjs +19 -19
  33. package/cli/investigation-manifest.mjs +1 -1
  34. package/cli/investigation-plan.mjs +167 -16
  35. package/cli/investigation-run.mjs +9 -1
  36. package/cli/lookalike-calibration.mjs +3 -1
  37. package/cli/lookup-brief.mjs +13 -8
  38. package/cli/lookup-browser.mjs +844 -0
  39. package/cli/lookup-command-runner.mjs +94 -31
  40. package/cli/lookup-diff.mjs +2 -2
  41. package/cli/lookup-plan.mjs +1 -0
  42. package/cli/mail-review.mjs +206 -94
  43. package/cli/manual.mjs +5 -5
  44. package/cli/offline-evidence-review.mjs +18 -7
  45. package/cli/open-asset-model-bridge.mjs +1 -1
  46. package/cli/output-file.mjs +70 -14
  47. package/cli/page-compare.mjs +11 -6
  48. package/cli/progress-events.mjs +2 -1
  49. package/cli/progress.mjs +3 -3
  50. package/cli/registry-cohort.mjs +9 -51
  51. package/cli/registry-doctor.mjs +67 -28
  52. package/cli/retained-artifact-diff.mjs +7 -8
  53. package/cli/risk-calibration.mjs +85 -38
  54. package/cli/runner.mjs +189 -75
  55. package/cli/saved-lookup.mjs +424 -57
  56. package/cli/sharing-review.mjs +6 -1
  57. package/cli/source-reliability.mjs +75 -36
  58. package/cli/terminal-presentation.mjs +43 -18
  59. package/docs/cli-reference.md +125 -1708
  60. package/docs/cli.md +86 -254
  61. package/frontend/src/lib/analysis/domain-control-manifest-core.js +3 -145
  62. package/frontend/src/lib/analysis/domain-control-records.js +3 -88
  63. package/lib/availability.mjs +58 -40
  64. package/lib/bounded-contract-normalizers.mjs +2 -4
  65. package/lib/bounded-file.mjs +78 -4
  66. package/lib/bounded-json.mjs +51 -13
  67. package/lib/browser-library-profile.mjs +1 -1
  68. package/lib/classify.mjs +56 -5
  69. package/lib/client-behavior-profile.mjs +1 -1
  70. package/lib/credential-surface-profile.mjs +1 -1
  71. package/lib/cryptographic-assurance.mjs +35 -17
  72. package/lib/ct-query.mjs +3 -3
  73. package/lib/ct-search.mjs +37 -21
  74. package/lib/distributed-operation-budget.mjs +4 -2
  75. package/lib/dns-delegation-health.mjs +9 -6
  76. package/lib/dns-intelligence.mjs +1 -1
  77. package/lib/dnssec-chain-validation.mjs +2 -3
  78. package/lib/dnssec-evidence-validation.mjs +13 -7
  79. package/lib/domain-assurance.mjs +12 -12
  80. package/lib/domain-change-packet.mjs +1 -1
  81. package/lib/domain-control-flight-recorder.mjs +442 -78
  82. package/lib/domain-control-manifest.mjs +314 -59
  83. package/lib/domain-posture-analysis.mjs +5 -2
  84. package/lib/encrypted-dns-contract.mjs +2 -3
  85. package/lib/evidence-export.mjs +518 -36
  86. package/lib/external-intelligence-risk.mjs +26 -10
  87. package/lib/feature-policy.mjs +1 -1
  88. package/lib/homepage-metadata-contract.mjs +290 -0
  89. package/lib/hostname.mjs +1 -1
  90. package/lib/html-signals.mjs +89 -5
  91. package/lib/http-intelligence.mjs +331 -2
  92. package/lib/interchange-fidelity-registry.mjs +28 -26
  93. package/lib/lookup-cache.mjs +6 -4
  94. package/lib/lookup.mjs +25 -2
  95. package/lib/observed-network-context.mjs +1 -1
  96. package/lib/page-fingerprints.mjs +2 -2
  97. package/lib/page-role-profile.mjs +1 -1
  98. package/lib/portable-generator.mjs +8 -0
  99. package/lib/project-metadata.mjs +1 -1
  100. package/lib/provider-policy-admission.mjs +3 -1
  101. package/lib/rdap-bootstrap.mjs +76 -16
  102. package/lib/rdap-client.mjs +20 -12
  103. package/lib/rdap-normalization.mjs +306 -75
  104. package/lib/rdap-transport.mjs +2 -2
  105. package/lib/rdap-validation.mjs +65 -14
  106. package/lib/rdap.mjs +2 -2
  107. package/lib/registrable-domain.mjs +36 -0
  108. package/lib/registry-insights.mjs +7 -0
  109. package/lib/response-policy.mjs +27 -4
  110. package/lib/risk-calibration-summary.mjs +35 -33
  111. package/lib/risk-scoring.mjs +4 -3
  112. package/lib/safe-fetch.mjs +62 -40
  113. package/lib/security-txt.mjs +1 -1
  114. package/lib/smtp-transport-review.mjs +8 -5
  115. package/lib/sslbl-intelligence.mjs +6 -8
  116. package/lib/static-html-analysis.mjs +301 -4
  117. package/lib/structured-data-identity.mjs +51 -19
  118. package/lib/threat-intelligence-fixtures.mjs +2 -4
  119. package/lib/threat-intelligence-manifests.mjs +2 -7
  120. package/lib/threat-intelligence-runtime.mjs +3 -5
  121. package/lib/threat-intelligence-types.mjs +3 -0
  122. package/lib/threatfox-intelligence.mjs +7 -3
  123. package/lib/tls-intelligence.mjs +4 -4
  124. package/lib/urlhaus-intelligence.mjs +4 -2
  125. package/lib/urlscan-intelligence.mjs +6 -5
  126. package/lib/website-security-posture.mjs +1 -1
  127. package/lib/website-technology.mjs +1 -1
  128. package/lib/whois-authority.mjs +43 -8
  129. package/lib/whois-chain.mjs +25 -3
  130. package/lib/whois-normalization.mjs +3 -1
  131. package/lib/whois-transport.mjs +12 -3
  132. package/lib/zone-intent-review.mjs +1 -1
  133. package/package.json +13 -5
  134. package/{frontend/src/lib/analysis/case-brand-profile-references.js → packages/cases/case-brand-profile-references.mjs} +2 -2
  135. package/{frontend/src/lib/analysis/case-evidence-model.js → packages/cases/case-evidence-model.mjs} +35 -14
  136. package/{frontend/src/lib/analysis/case-investigation-branch-model.js → packages/cases/case-investigation-branch-model.mjs} +10 -12
  137. package/packages/cases/case-migration-model.mjs +476 -0
  138. package/{frontend/src/lib/analysis/case-model.js → packages/cases/case-model.mjs} +3 -3
  139. package/{frontend/src/lib/analysis/case-record-contracts.js → packages/cases/case-record-contracts.mjs} +3 -21
  140. package/{frontend/src/lib/analysis/case-record-core.js → packages/cases/case-record-core.mjs} +58 -31
  141. package/packages/cases/case-record-model.mjs +9 -0
  142. package/{frontend/src/lib/analysis/case-record-operations.js → packages/cases/case-record-operations.mjs} +72 -29
  143. package/{frontend/src/lib/analysis/case-report.js → packages/cases/case-report.mjs} +108 -14
  144. package/packages/cases/case-response-model.mjs +1604 -0
  145. package/packages/cases/case-response-packet.mjs +1312 -0
  146. package/{frontend/src/lib/analysis/case-storage-model.js → packages/cases/case-storage-model.mjs} +2 -2
  147. package/{frontend/src/lib/analysis/http-summary.js → packages/cases/http-summary.mjs} +1 -1
  148. package/{frontend/src/lib/analysis/comparison-ledger-bulk.js → packages/comparison/comparison-ledger-bulk.mjs} +2 -2
  149. package/{frontend/src/lib/analysis/comparison-ledger-contract.js → packages/comparison/comparison-ledger-contract.mjs} +4 -4
  150. package/{frontend/src/lib/analysis/comparison-ledger-json.js → packages/comparison/comparison-ledger-json.mjs} +1 -0
  151. package/{frontend/src/lib/analysis/comparison-ledger-serialization.js → packages/comparison/comparison-ledger-serialization.mjs} +1 -0
  152. package/packages/comparison/favicon-similarity.mjs +78 -0
  153. package/{frontend/src/lib/analysis/page-similarity.js → packages/comparison/page-similarity.mjs} +4 -3
  154. package/{frontend/src/lib/analysis/relationship-evidence.js → packages/comparison/relationship-evidence.mjs} +6 -7
  155. package/packages/contracts/analyst-review-state-contract.mjs +61 -0
  156. package/packages/contracts/analyst-review-state.mjs +50 -0
  157. package/packages/contracts/case-portability.mjs +1310 -0
  158. package/packages/contracts/cli-command-catalogue.mjs +2 -0
  159. package/packages/contracts/cli-lookup.mjs +394 -0
  160. package/packages/contracts/domain-control-flight-recorder.mjs +461 -0
  161. package/packages/contracts/domain-control-manifest.mjs +1027 -0
  162. package/packages/contracts/domain-control-monitor.mjs +310 -0
  163. package/packages/contracts/domain-control-review.mjs +828 -0
  164. package/packages/contracts/domain-name.mjs +4 -0
  165. package/packages/contracts/external-observation-interchange.mjs +224 -0
  166. package/packages/contracts/extracted-domain-lifecycle.mjs +218 -0
  167. package/packages/contracts/investigation-portability.mjs +331 -0
  168. package/packages/contracts/offline-comparison.mjs +160 -0
  169. package/packages/contracts/risk-calibration.mjs +1288 -0
  170. package/packages/contracts/schema-compatibility.mjs +71 -0
  171. package/packages/contracts/schema-lifecycle.mjs +1702 -0
  172. package/packages/contracts/sslbl-snapshot.mjs +17 -0
  173. package/packages/contracts/workspace-portability.mjs +1347 -0
  174. package/{frontend/src/lib/analysis/artifact-integrity.js → packages/evidence/artifact-integrity.mjs} +13 -11
  175. package/packages/evidence/decision-fact.mjs +735 -0
  176. package/packages/evidence/domain-control-runtime.mjs +547 -0
  177. package/packages/evidence/domain-name.mjs +41 -0
  178. package/packages/evidence/observation.mjs +384 -0
  179. package/{frontend/src/lib/analysis/external-findings-import.js → packages/interchange/external-findings-import.mjs} +24 -22
  180. package/packages/interchange/retained-text.mjs +8 -0
  181. package/{frontend/src/lib/analysis/acquisition-decision-packet.js → packages/investigation/acquisition-decision-packet.mjs} +9 -6
  182. package/{frontend/src/lib/analysis/bulk-domain-comparison.js → packages/investigation/bulk-domain-comparison.mjs} +5 -6
  183. package/{frontend/src/lib/analysis/bulk-mail-exposure.js → packages/investigation/bulk-mail-exposure.mjs} +4 -6
  184. package/{frontend/src/lib/analysis/bulk-review-export.js → packages/investigation/bulk-review-export.mjs} +5 -5
  185. package/packages/investigation/bulk-review-policy.mjs +1 -0
  186. package/{frontend/src/lib/analysis/investigation-capsule.js → packages/investigation/investigation-capsule.mjs} +30 -32
  187. package/packages/investigation/lookup-artefact-inputs.mjs +1 -0
  188. package/{frontend/src/lib/analysis/lookup-asset-graph.js → packages/investigation/lookup-asset-graph.mjs} +4 -2
  189. package/{frontend/src/lib/analysis/lookup-claim-passport.js → packages/investigation/lookup-claim-passport.mjs} +6 -10
  190. package/{frontend/src/lib/analysis/lookup-claim-readiness.js → packages/investigation/lookup-claim-readiness.mjs} +9 -2
  191. package/packages/investigation/lookup-investigation-brief.mjs +172 -0
  192. package/packages/monitoring/analyst-review-state.mjs +680 -0
  193. package/{frontend/src/lib/analysis/brand-profile-model.js → packages/workspace/brand-profile-model.mjs} +71 -48
  194. package/{frontend/src/lib/analysis/bulk-review-model.js → packages/workspace/bulk-review-model.mjs} +13 -14
  195. package/{frontend/src/lib/analysis/bulk-session-model.js → packages/workspace/bulk-session-model.mjs} +33 -59
  196. package/{frontend/src/lib/analysis/bulk-sort.js → packages/workspace/bulk-sort.mjs} +8 -5
  197. package/{frontend/src/lib/analysis/campaign-model.js → packages/workspace/campaign-model.mjs} +20 -22
  198. package/{frontend/src/lib/analysis/detection-rule-model.js → packages/workspace/detection-rule-model.mjs} +15 -17
  199. package/packages/workspace/hostile-input.mjs +172 -0
  200. package/{frontend/src/lib/analysis/investigation-guide.js → packages/workspace/investigation-guide.mjs} +8 -45
  201. package/{frontend/src/lib/analysis/investigation-template-model.js → packages/workspace/investigation-template-model.mjs} +18 -21
  202. package/{frontend/src/lib/analysis/page-baseline.js → packages/workspace/page-baseline.mjs} +6 -13
  203. package/{frontend/src/lib/analysis/relationship-observation-model.js → packages/workspace/relationship-observation-model.mjs} +13 -17
  204. package/{frontend/src/lib/analysis/shortlist-model.js → packages/workspace/shortlist-model.mjs} +20 -18
  205. package/{frontend/src/lib/analysis/watchlist-history.js → packages/workspace/watchlist-history.mjs} +9 -16
  206. package/{frontend/src/lib/analysis/watchlist-store.js → packages/workspace/watchlist-store.mjs} +24 -17
  207. package/{frontend/src/lib/analysis/website-snapshot-model.js → packages/workspace/website-snapshot-model.mjs} +13 -13
  208. package/{frontend/src/lib/analysis/workspace-archive-crypto.js → packages/workspace/workspace-archive-crypto.mjs} +49 -19
  209. package/{frontend/src/lib/analysis/workspace-archive.js → packages/workspace/workspace-archive.mjs} +110 -89
  210. package/third-party-notices.txt +14 -14
  211. package/frontend/src/lib/analysis/bulk-retry-plan.js +0 -79
  212. package/frontend/src/lib/analysis/bulk-source-coverage.js +0 -76
  213. package/frontend/src/lib/analysis/case-migration-model.js +0 -305
  214. package/frontend/src/lib/analysis/case-record-model.js +0 -9
  215. package/frontend/src/lib/analysis/case-response-model.js +0 -728
  216. package/frontend/src/lib/analysis/case-response-packet.js +0 -583
  217. package/frontend/src/lib/analysis/lookup-decision-support.js +0 -542
  218. package/frontend/src/lib/analysis/lookup-investigation-brief.js +0 -153
  219. package/frontend/src/lib/analysis/lookup-presentation.js +0 -120
  220. package/frontend/src/lib/analysis/lookup-response.js +0 -2
  221. package/frontend/src/lib/analysis/lookup-source-refresh.js +0 -277
  222. package/frontend/src/lib/analysis/lookup-summary-model.js +0 -398
  223. package/frontend/src/lib/analysis/scoring.js +0 -55
  224. package/frontend/src/lib/analysis/utils.js +0 -255
  225. package/frontend/src/lib/analysis/visualization-bounds.js +0 -26
  226. package/frontend/src/lib/analysis/visualization-force-graph.js +0 -352
  227. package/frontend/src/lib/analysis/visualization-matrix.js +0 -131
  228. package/frontend/src/lib/analysis/visualization-models.js +0 -338
  229. package/frontend/src/lib/analysis/visualization-time-series.js +0 -185
  230. package/lib/bounded-json-response.mjs +0 -170
  231. package/lib/lookup-response-contract.mjs +0 -1034
  232. package/lib/observation.mjs +0 -115
  233. /package/{frontend/src/lib/analysis/opaque-reference-id.js → packages/cases/opaque-reference-id.mjs} +0 -0
  234. /package/{frontend/src/lib/analysis/acquisition-due-diligence.js → packages/investigation/acquisition-due-diligence.mjs} +0 -0
  235. /package/{frontend/src/lib/analysis/evidence-coverage-ledger.js → packages/investigation/evidence-coverage-ledger.mjs} +0 -0
  236. /package/{frontend/src/lib/analysis/dns-record-normalization.js → packages/workspace/dns-record-normalization.mjs} +0 -0
@@ -1,53 +1,27 @@
1
1
  // Pure, bounded workspace archive composition. Existing typed store models remain the
2
2
  // authority for normalization and merge semantics; this module only packages
3
3
  // their portable contracts, verifies integrity, and previews conflicts.
4
- import { buildCaseExport, CASE_SCHEMA_VERSION, CASE_IMPORT_VERSIONS, enforceStoreBudget, mergeCases, } from "./case-model.js";
5
- import { assertBrandProfileStoreBudget, BRAND_PROFILE_SCHEMA, BRAND_PROFILE_SCHEMA_VERSION, SUPPORTED_BRAND_PROFILE_SCHEMA_VERSIONS, buildBrandProfileExport, mergeBrandProfiles, normalizeBrandProfileId, } from "./brand-profile-model.js";
6
- import { assertCampaignStoreBudget, buildCampaignExport, CAMPAIGN_SCHEMA, CAMPAIGN_SCHEMA_VERSION, mergeCampaigns, } from "./campaign-model.js";
7
- import { assertWatchlistStoreBudget, buildWatchlistExport, mergeWatchlistStores, WATCHLIST_SCHEMA, WATCHLIST_SCHEMA_VERSION, } from "./watchlist-store.js";
8
- import { assertShortlistStoreBudget, buildShortlistExport, mergeShortlistStores, SHORTLIST_SCHEMA, SHORTLIST_SCHEMA_VERSION, SUPPORTED_SHORTLIST_SCHEMA_VERSIONS, } from "./shortlist-model.js";
9
- import { assertDetectionRuleStoreBudget, buildDetectionRuleExport, DETECTION_RULE_SCHEMA, DETECTION_RULE_SCHEMA_VERSION, mergeDetectionRules, } from "./detection-rule-model.js";
10
- import { buildRelationshipObservationExport, mergeRelationshipObservations, RELATIONSHIP_OBSERVATION_SCHEMA, RELATIONSHIP_OBSERVATION_SCHEMA_VERSION, } from "./relationship-observation-model.js";
11
- import { BULK_SESSION_SCHEMA, BULK_SESSION_SCHEMA_VERSION, SUPPORTED_BULK_SESSION_SCHEMA_VERSIONS, buildBulkSessionExport, enforceBulkSessionStoreBudget, mergeBulkSessions, } from "./bulk-session-model.js";
12
- import { WEBSITE_SNAPSHOT_SCHEMA, WEBSITE_SNAPSHOT_SCHEMA_VERSION, SUPPORTED_WEBSITE_SNAPSHOT_SCHEMA_VERSIONS, buildWebsiteSnapshotExport, mergeWebsiteSnapshots, } from "./website-snapshot-model.js";
13
- import { INVESTIGATION_TEMPLATE_SCHEMA, INVESTIGATION_TEMPLATE_VERSION, buildInvestigationTemplateExport, mergeInvestigationTemplates, } from "./investigation-template-model.js";
14
- import { BULK_REVIEW_SCHEMA, BULK_REVIEW_SCHEMA_VERSION, buildBulkReviewExport, mergeBulkReviewStores, } from "./bulk-review-model.js";
15
- export const WORKSPACE_ARCHIVE_SCHEMA = 'whoisleuth.workspace-archive';
16
- export const WORKSPACE_ARCHIVE_VERSION = 5;
17
- export const SUPPORTED_WORKSPACE_ARCHIVE_VERSIONS = [1, 2, 3, 4, WORKSPACE_ARCHIVE_VERSION];
18
- export const WORKSPACE_SETTINGS_SCHEMA = 'whoisleuth.workspace-settings';
19
- export const WORKSPACE_SETTINGS_VERSION = 1;
20
- export const MAX_WORKSPACE_ARCHIVE_BYTES = 10 * 1024 * 1024;
21
- export const MAX_WORKSPACE_ARCHIVE_SECTION_BYTES = 5 * 1024 * 1024;
22
- export const MAX_WORKSPACE_ARCHIVE_SECTIONS = 12;
23
- export const WORKSPACE_ARCHIVE_SECTION_IDS = [
24
- 'cases',
25
- 'campaigns',
26
- 'brandProfiles',
27
- 'watchlists',
28
- 'shortlist',
29
- 'detectionRules',
30
- 'relationshipObservations',
31
- 'bulkSessions',
32
- 'websiteSnapshots',
33
- 'investigationTemplates',
34
- 'bulkReview',
35
- 'settings',
36
- ];
37
- export function isSupportedWorkspaceArchiveVersion(value) {
38
- return typeof value === 'number'
39
- && Number.isSafeInteger(value)
40
- && SUPPORTED_WORKSPACE_ARCHIVE_VERSIONS.some((version) => version === value);
41
- }
4
+ import { buildCaseExport, enforceStoreBudget, mergeCases, } from "../cases/case-model.mjs";
5
+ import { assertBrandProfileStoreBudget, buildBrandProfileExport, mergeBrandProfiles, normalizeBrandProfileId, } from "./brand-profile-model.mjs";
6
+ import { assertCampaignStoreBudget, buildCampaignExport, mergeCampaigns, } from "./campaign-model.mjs";
7
+ import { normalizeExplicitIsoTimestamp } from "../evidence/observation.mjs";
8
+ import { assertWorkspaceInputGraph } from "./hostile-input.mjs";
9
+ import { assertWatchlistStoreBudget, buildWatchlistExport, mergeWatchlistStores, } from "./watchlist-store.mjs";
10
+ import { assertShortlistStoreBudget, buildShortlistExport, mergeShortlistStores, } from "./shortlist-model.mjs";
11
+ import { assertDetectionRuleStoreBudget, buildDetectionRuleExport, mergeDetectionRules, } from "./detection-rule-model.mjs";
12
+ import { buildRelationshipObservationExport, mergeRelationshipObservations, } from "./relationship-observation-model.mjs";
13
+ import { buildBulkSessionExport, enforceBulkSessionStoreBudget, mergeBulkSessions, } from "./bulk-session-model.mjs";
14
+ import { buildWebsiteSnapshotExport, mergeWebsiteSnapshots, } from "./website-snapshot-model.mjs";
15
+ import { buildInvestigationTemplateExport, mergeInvestigationTemplates, } from "./investigation-template-model.mjs";
16
+ import { buildBulkReviewExport, mergeBulkReviewStores, } from "./bulk-review-model.mjs";
17
+ import { buildAnalystReviewStateExport, emptyAnalystReviewStateStore, mergeAnalystReviewStateStores, } from "../monitoring/analyst-review-state.mjs";
18
+ import { isSupportedWorkspaceArchiveVersion, MAX_WORKSPACE_ARCHIVE_BYTES, MAX_WORKSPACE_ARCHIVE_SECTION_BYTES, MAX_WORKSPACE_ARCHIVE_SECTIONS, serialiseWorkspaceArchiveSection, SUPPORTED_WORKSPACE_ARCHIVE_VERSIONS, WORKSPACE_ARCHIVE_CASE_SECTION, WORKSPACE_ARCHIVE_SCHEMA, WORKSPACE_ARCHIVE_SECTION_IDS, PUBLIC_WORKSPACE_ARCHIVE_SECTION_IDS, PUBLIC_WORKSPACE_ARCHIVE_VERSION, WORKSPACE_ARCHIVE_VERSION, WORKSPACE_SETTINGS_COMPATIBILITY, WORKSPACE_SETTINGS_SCHEMA, WORKSPACE_SETTINGS_VERSION, } from "../contracts/case-portability.mjs";
19
+ import { ANALYST_REVIEW_STATE_COMPATIBILITY } from "../contracts/analyst-review-state.mjs";
20
+ import { WORKSPACE_PORTABILITY_ARCHIVE_SECTION_REFERENCES } from "../contracts/workspace-portability.mjs";
21
+ export { isSupportedWorkspaceArchiveVersion, MAX_WORKSPACE_ARCHIVE_BYTES, MAX_WORKSPACE_ARCHIVE_SECTION_BYTES, MAX_WORKSPACE_ARCHIVE_SECTIONS, SUPPORTED_WORKSPACE_ARCHIVE_VERSIONS, WORKSPACE_ARCHIVE_CASE_SECTION, WORKSPACE_ARCHIVE_SCHEMA, WORKSPACE_ARCHIVE_SECTION_IDS, PUBLIC_WORKSPACE_ARCHIVE_SECTION_IDS, PUBLIC_WORKSPACE_ARCHIVE_VERSION, WORKSPACE_ARCHIVE_VERSION, WORKSPACE_SETTINGS_SCHEMA, WORKSPACE_SETTINGS_VERSION, };
42
22
  const CONTROL_RE = /[\x00-\x1f\x7f]/;
43
23
  const CHECKSUM_RE = /^sha256:[a-f0-9]{64}$/;
44
- const WORKSPACE_ARCHIVE_ROOT_KEYS = Object.freeze({
45
- 1: Object.freeze(['schema', 'version', 'generatedAt', 'manifest', 'sections', 'limitations']),
46
- 2: Object.freeze(['schema', 'version', 'generatedAt', 'manifest', 'sections', 'limitations']),
47
- 3: Object.freeze(['schema', 'version', 'generatedAt', 'manifest', 'sections', 'limitations']),
48
- 4: Object.freeze(['schema', 'version', 'generatedAt', 'manifest', 'sections', 'limitations']),
49
- 5: Object.freeze(['schema', 'version', 'generatedAt', 'manifest', 'sections', 'limitations']),
50
- });
24
+ const WORKSPACE_ARCHIVE_ROOT_KEYS = Object.freeze(['schema', 'version', 'generatedAt', 'manifest', 'sections', 'limitations']);
51
25
  const WORKSPACE_ARCHIVE_MANIFEST_KEYS = Object.freeze(['sectionCount', 'totalRecords', 'sections']);
52
26
  const WORKSPACE_ARCHIVE_MANIFEST_ENTRY_KEYS = Object.freeze(['id', 'schema', 'version', 'recordCount', 'bytes', 'checksum']);
53
27
  function record(value) {
@@ -64,10 +38,7 @@ function byteLength(value) {
64
38
  }
65
39
  function serialize(value, message = 'The workspace archive contains data that cannot be serialised.') {
66
40
  try {
67
- const serialized = JSON.stringify(value);
68
- if (typeof serialized !== 'string')
69
- throw new Error(message);
70
- return serialized;
41
+ return serialiseWorkspaceArchiveSection(value);
71
42
  }
72
43
  catch {
73
44
  throw new Error(message);
@@ -77,10 +48,10 @@ function clone(value) {
77
48
  return JSON.parse(serialize(value));
78
49
  }
79
50
  function timestamp(value, fallback = null) {
80
- if (typeof value !== 'string' || value.length > 64 || CONTROL_RE.test(value))
81
- return fallback;
82
- const parsed = Date.parse(value);
83
- return Number.isFinite(parsed) ? new Date(parsed).toISOString() : fallback;
51
+ const explicit = normalizeExplicitIsoTimestamp(value);
52
+ if (explicit)
53
+ return explicit;
54
+ return fallback;
84
55
  }
85
56
  function boundedText(value, maximum = 300) {
86
57
  if (typeof value !== 'string' || value.length > maximum * 4 || CONTROL_RE.test(value))
@@ -139,6 +110,7 @@ function workspaceArchiveSections(input, now) {
139
110
  websiteSnapshots: buildWebsiteSnapshotExport(input.websiteSnapshots, now),
140
111
  investigationTemplates: buildInvestigationTemplateExport(input.investigationTemplates, now),
141
112
  bulkReview: buildBulkReviewExport(input.bulkReview),
113
+ analystReviewState: buildAnalystReviewStateExport(input.analystReviewState),
142
114
  settings: settingsDocument(input),
143
115
  };
144
116
  }
@@ -151,10 +123,29 @@ function objectCount(data, key) {
151
123
  const nested = value ? record(value[key]) : null;
152
124
  return nested ? Object.keys(nested).length : 0;
153
125
  }
126
+ function sectionContract(sectionId) {
127
+ const reference = WORKSPACE_PORTABILITY_ARCHIVE_SECTION_REFERENCES.find((entry) => entry.sectionId === sectionId);
128
+ if (!reference)
129
+ throw new TypeError(`Workspace archive section ${sectionId} has no canonical contract reference.`);
130
+ return Object.freeze({
131
+ id: reference.sectionId,
132
+ schema: reference.schema,
133
+ version: reference.version,
134
+ supportedVersions: reference.supportedVersions,
135
+ });
136
+ }
137
+ function compatibilitySchema(descriptor) {
138
+ if (!descriptor.schema)
139
+ throw new TypeError(`Workspace compatibility ${descriptor.id} must name a schema.`);
140
+ return descriptor.schema;
141
+ }
154
142
  const SECTION_DEFINITIONS = [
155
143
  {
156
- id: 'cases', label: 'Cases', schema: null, version: CASE_SCHEMA_VERSION,
157
- supportedVersions: CASE_IMPORT_VERSIONS,
144
+ id: WORKSPACE_ARCHIVE_CASE_SECTION.id,
145
+ label: 'Cases',
146
+ schema: WORKSPACE_ARCHIVE_CASE_SECTION.schema,
147
+ version: WORKSPACE_ARCHIVE_CASE_SECTION.currentVersion,
148
+ supportedVersions: WORKSPACE_ARCHIVE_CASE_SECTION.supportedVersions,
158
149
  count: (data) => arrayCount(data, 'cases'),
159
150
  merge: (local, data) => {
160
151
  const result = mergeCases(local.cases, data);
@@ -163,7 +154,7 @@ const SECTION_DEFINITIONS = [
163
154
  },
164
155
  },
165
156
  {
166
- id: 'campaigns', label: 'Campaigns', schema: CAMPAIGN_SCHEMA, version: CAMPAIGN_SCHEMA_VERSION,
157
+ ...sectionContract('campaigns'), label: 'Campaigns',
167
158
  count: (data) => arrayCount(data, 'campaigns'),
168
159
  merge: (local, data) => {
169
160
  const result = mergeCampaigns(local.campaigns, data);
@@ -171,8 +162,7 @@ const SECTION_DEFINITIONS = [
171
162
  },
172
163
  },
173
164
  {
174
- id: 'brandProfiles', label: 'Brand profiles', schema: BRAND_PROFILE_SCHEMA, version: BRAND_PROFILE_SCHEMA_VERSION,
175
- supportedVersions: SUPPORTED_BRAND_PROFILE_SCHEMA_VERSIONS,
165
+ ...sectionContract('brandProfiles'), label: 'Brand profiles',
176
166
  count: (data) => arrayCount(data, 'profiles'),
177
167
  merge: (local, data, now) => {
178
168
  const result = mergeBrandProfiles(local.brandProfiles, data, { nowIso: now });
@@ -180,7 +170,7 @@ const SECTION_DEFINITIONS = [
180
170
  },
181
171
  },
182
172
  {
183
- id: 'watchlists', label: 'Watchlists', schema: WATCHLIST_SCHEMA, version: WATCHLIST_SCHEMA_VERSION,
173
+ ...sectionContract('watchlists'), label: 'Watchlists',
184
174
  count: (data) => objectCount(data, 'watchlists'),
185
175
  merge: (local, data) => {
186
176
  const result = mergeWatchlistStores(local.watchlists, data);
@@ -188,8 +178,7 @@ const SECTION_DEFINITIONS = [
188
178
  },
189
179
  },
190
180
  {
191
- id: 'shortlist', label: 'Shortlist', schema: SHORTLIST_SCHEMA, version: SHORTLIST_SCHEMA_VERSION,
192
- supportedVersions: SUPPORTED_SHORTLIST_SCHEMA_VERSIONS,
181
+ ...sectionContract('shortlist'), label: 'Shortlist',
193
182
  count: (data) => arrayCount(data, 'entries'),
194
183
  merge: (local, data) => {
195
184
  const result = mergeShortlistStores(local.shortlist, data);
@@ -197,7 +186,7 @@ const SECTION_DEFINITIONS = [
197
186
  },
198
187
  },
199
188
  {
200
- id: 'detectionRules', label: 'Detection rules', schema: DETECTION_RULE_SCHEMA, version: DETECTION_RULE_SCHEMA_VERSION,
189
+ ...sectionContract('detectionRules'), label: 'Detection rules',
201
190
  count: (data) => arrayCount(data, 'rules'),
202
191
  merge: (local, data) => {
203
192
  const result = mergeDetectionRules(local.detectionRules, data);
@@ -205,19 +194,14 @@ const SECTION_DEFINITIONS = [
205
194
  },
206
195
  },
207
196
  {
208
- id: 'relationshipObservations',
197
+ ...sectionContract('relationshipObservations'),
209
198
  label: 'Retained relationship observations',
210
- schema: RELATIONSHIP_OBSERVATION_SCHEMA,
211
- version: RELATIONSHIP_OBSERVATION_SCHEMA_VERSION,
212
199
  count: (data) => arrayCount(data, 'observations'),
213
200
  merge: (local, data) => mergeRelationshipObservations(local.relationshipObservations, data),
214
201
  },
215
202
  {
216
- id: 'bulkSessions',
203
+ ...sectionContract('bulkSessions'),
217
204
  label: 'Saved Bulk sessions',
218
- schema: BULK_SESSION_SCHEMA,
219
- version: BULK_SESSION_SCHEMA_VERSION,
220
- supportedVersions: SUPPORTED_BULK_SESSION_SCHEMA_VERSIONS,
221
205
  count: (data) => arrayCount(data, 'sessions'),
222
206
  merge: (local, data) => {
223
207
  const result = mergeBulkSessions(local.bulkSessions, data);
@@ -226,32 +210,36 @@ const SECTION_DEFINITIONS = [
226
210
  },
227
211
  },
228
212
  {
229
- id: 'websiteSnapshots',
213
+ ...sectionContract('websiteSnapshots'),
230
214
  label: 'Website profile snapshots',
231
- schema: WEBSITE_SNAPSHOT_SCHEMA,
232
- version: WEBSITE_SNAPSHOT_SCHEMA_VERSION,
233
- supportedVersions: SUPPORTED_WEBSITE_SNAPSHOT_SCHEMA_VERSIONS,
234
215
  count: (data) => arrayCount(data, 'snapshots'),
235
216
  merge: (local, data) => mergeWebsiteSnapshots(local.websiteSnapshots, data),
236
217
  },
237
218
  {
238
- id: 'investigationTemplates',
219
+ ...sectionContract('investigationTemplates'),
239
220
  label: 'Investigation templates',
240
- schema: INVESTIGATION_TEMPLATE_SCHEMA,
241
- version: INVESTIGATION_TEMPLATE_VERSION,
242
221
  count: (data) => arrayCount(data, 'templates'),
243
222
  merge: (local, data) => mergeInvestigationTemplates(local.investigationTemplates, data),
244
223
  },
245
224
  {
246
- id: 'bulkReview',
225
+ ...sectionContract('bulkReview'),
247
226
  label: 'Bulk saved views and review queue',
248
- schema: BULK_REVIEW_SCHEMA,
249
- version: BULK_REVIEW_SCHEMA_VERSION,
250
227
  count: (data) => arrayCount(data, 'presets') + arrayCount(data, 'rows'),
251
228
  merge: (local, data) => mergeBulkReviewStores(local.bulkReview, data),
252
229
  },
253
230
  {
254
- id: 'settings', label: 'Workspace settings', schema: WORKSPACE_SETTINGS_SCHEMA, version: WORKSPACE_SETTINGS_VERSION,
231
+ id: 'analystReviewState',
232
+ label: 'Analyst Review Item lifecycle',
233
+ schema: compatibilitySchema(ANALYST_REVIEW_STATE_COMPATIBILITY),
234
+ version: ANALYST_REVIEW_STATE_COMPATIBILITY.currentVersion,
235
+ supportedVersions: ANALYST_REVIEW_STATE_COMPATIBILITY.supportedVersions,
236
+ count: (data) => arrayCount(data, 'records'),
237
+ merge: (local, data) => mergeAnalystReviewStateStores(local.analystReviewState, data),
238
+ },
239
+ {
240
+ id: 'settings', label: 'Workspace settings', schema: compatibilitySchema(WORKSPACE_SETTINGS_COMPATIBILITY),
241
+ version: WORKSPACE_SETTINGS_COMPATIBILITY.currentVersion,
242
+ supportedVersions: WORKSPACE_SETTINGS_COMPATIBILITY.supportedVersions,
255
243
  count: () => 1,
256
244
  merge: null,
257
245
  },
@@ -277,6 +265,7 @@ function normalizedInput(input) {
277
265
  websiteSnapshots: Array.isArray(value.websiteSnapshots) ? value.websiteSnapshots : [],
278
266
  investigationTemplates: Array.isArray(value.investigationTemplates) ? value.investigationTemplates : [],
279
267
  bulkReview: record(value.bulkReview) || {},
268
+ analystReviewState: record(value.analystReviewState) || emptyAnalystReviewStateStore(),
280
269
  settings: record(value.settings) || {},
281
270
  };
282
271
  }
@@ -290,7 +279,9 @@ function ensureArchiveBudget(value) {
290
279
  }
291
280
  /** Build one deterministic, unencrypted archive from normalized local stores. */
292
281
  export async function buildWorkspaceArchive(input, options = {}) {
293
- const now = timestamp(options.generatedAt) || new Date().toISOString();
282
+ const now = timestamp(options.generatedAt ?? new Date().toISOString());
283
+ if (!now)
284
+ throw new Error('Workspace archive generation time must use an explicit timezone.');
294
285
  const source = normalizedInput(input);
295
286
  const sections = workspaceArchiveSections(source, now);
296
287
  const manifestSections = [];
@@ -347,19 +338,29 @@ function manifestEntry(raw) {
347
338
  }
348
339
  /** Validate structure, section byte counts, and checksums without applying data. */
349
340
  export async function readWorkspaceArchive(raw, options = {}) {
341
+ assertWorkspaceInputGraph(raw, 'Workspace archive');
350
342
  const value = record(raw);
351
343
  if (!value || value.schema !== WORKSPACE_ARCHIVE_SCHEMA) {
352
344
  throw new Error('This file is not a WHOISleuth workspace archive.');
353
345
  }
354
346
  if (!isSupportedWorkspaceArchiveVersion(value.version)) {
347
+ if (typeof value.version === 'number' && Number.isSafeInteger(value.version) && value.version < WORKSPACE_ARCHIVE_VERSION) {
348
+ throw new Error(`Workspace archive schema ${value.version} is retired. Export it as schema ${WORKSPACE_ARCHIVE_VERSION} with the last broad-reader release before importing; no data was changed.`);
349
+ }
355
350
  if (typeof value.version === 'number' && Number.isSafeInteger(value.version) && value.version > WORKSPACE_ARCHIVE_VERSION) {
356
- throw new Error(`This workspace archive uses newer schema ${value.version}. Update the app before importing it.`);
351
+ throw new Error(`Workspace archive schema ${value.version} is newer than the supported schema ${WORKSPACE_ARCHIVE_VERSION}; no data was changed.`);
357
352
  }
358
- throw new Error(`Expected workspace archive schema 1, 2, 3, 4, or ${WORKSPACE_ARCHIVE_VERSION}.`);
353
+ throw new Error(`Expected a well-formed workspace archive using schema ${WORKSPACE_ARCHIVE_VERSION}; no data was changed.`);
359
354
  }
360
355
  const sourceVersion = value.version;
356
+ const expectedSectionIds = sourceVersion === PUBLIC_WORKSPACE_ARCHIVE_VERSION
357
+ ? PUBLIC_WORKSPACE_ARCHIVE_SECTION_IDS
358
+ : WORKSPACE_ARCHIVE_SECTION_IDS;
359
+ const generatedAt = timestamp(value.generatedAt);
360
+ if (!generatedAt)
361
+ throw new Error('The workspace archive generation time is invalid.');
361
362
  const { bytes } = ensureArchiveBudget(value);
362
- assertExactKeys(value, WORKSPACE_ARCHIVE_ROOT_KEYS[sourceVersion] ?? [], `version ${sourceVersion} envelope`);
363
+ assertExactKeys(value, WORKSPACE_ARCHIVE_ROOT_KEYS, `version ${sourceVersion} envelope`);
363
364
  const manifest = record(value.manifest);
364
365
  const sectionValues = record(value.sections);
365
366
  if (!manifest || !sectionValues || !Array.isArray(manifest.sections)) {
@@ -413,9 +414,11 @@ export async function readWorkspaceArchive(raw, options = {}) {
413
414
  }
414
415
  else if (!(definition.supportedVersions ?? [definition.version]).includes(entry.version) || entry.schema !== definition.schema) {
415
416
  status = 'unsupported';
416
- reason = entry.version > definition.version
417
- ? `This section uses newer schema ${entry.version}.`
418
- : 'This section uses an unsupported schema contract.';
417
+ reason = entry.schema !== definition.schema
418
+ ? 'This section uses an unsupported schema contract.'
419
+ : entry.version > definition.version
420
+ ? `This section uses newer schema ${entry.version}.`
421
+ : `This section uses retired schema ${entry.version}. Export it with the last broad-reader release before importing; no data was changed.`;
419
422
  }
420
423
  else if (definition.count(data) !== entry.recordCount) {
421
424
  throw new Error(`${entry.id} does not match its manifest record count.`);
@@ -429,12 +432,30 @@ export async function readWorkspaceArchive(raw, options = {}) {
429
432
  if (manifest.sectionCount !== sections.length || manifest.totalRecords !== totalRecords) {
430
433
  throw new Error('The workspace archive manifest totals do not match its sections.');
431
434
  }
435
+ if (seen.size !== expectedSectionIds.length || expectedSectionIds.some((id) => !seen.has(id))) {
436
+ throw new Error(`Workspace archive schema ${sourceVersion} does not contain its exact required section set.`);
437
+ }
438
+ if (sourceVersion === PUBLIC_WORKSPACE_ARCHIVE_VERSION) {
439
+ const data = emptyAnalystReviewStateStore();
440
+ sections.push({
441
+ id: 'analystReviewState',
442
+ label: 'Analyst Review Item lifecycle',
443
+ schema: compatibilitySchema(ANALYST_REVIEW_STATE_COMPATIBILITY),
444
+ version: ANALYST_REVIEW_STATE_COMPATIBILITY.currentVersion,
445
+ recordCount: 0,
446
+ bytes: byteLength(serialize(data)),
447
+ checksum: await checksum(data, options.cryptoProvider),
448
+ status: 'ready',
449
+ reason: `Workspace schema ${PUBLIC_WORKSPACE_ARCHIVE_VERSION} did not contain Review Item lifecycle state. Import supplies an empty section and invents no analyst decisions.`,
450
+ data,
451
+ });
452
+ }
432
453
  sections.sort(canonicalSectionOrder);
433
454
  return {
434
455
  schema: WORKSPACE_ARCHIVE_SCHEMA,
435
456
  version: WORKSPACE_ARCHIVE_VERSION,
436
457
  sourceVersion,
437
- generatedAt: timestamp(value.generatedAt),
458
+ generatedAt,
438
459
  bytes,
439
460
  sections,
440
461
  limitations: Array.isArray(value.limitations)
@@ -501,7 +522,7 @@ export async function previewWorkspaceArchive(raw, localInput, options = {}) {
501
522
  status: 'ready',
502
523
  reason: definition.id === 'settings' && selected && (result.skipped ?? 0) > 0
503
524
  ? result.reason ?? 'The imported workspace preference was skipped. Existing browser-local context is preserved.'
504
- : '',
525
+ : section.reason,
505
526
  added: result.added ?? 0,
506
527
  updated: result.updated ?? 0,
507
528
  skipped: result.skipped ?? 0,
@@ -7,7 +7,7 @@ The inventory is the exact transitive closure of the packaged CLI runtime depend
7
7
  Package count: 27
8
8
 
9
9
  ================================================================================
10
- @peculiar/asn1-cms@2.8.0
10
+ @peculiar/asn1-cms@2.9.4
11
11
  Relationship: transitive production dependency
12
12
  Declared licence: MIT
13
13
  Licence source: LICENSE
@@ -34,7 +34,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
34
34
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
35
35
  SOFTWARE.
36
36
  ================================================================================
37
- @peculiar/asn1-csr@2.8.0
37
+ @peculiar/asn1-csr@2.9.4
38
38
  Relationship: transitive production dependency
39
39
  Declared licence: MIT
40
40
  Licence source: LICENSE
@@ -61,7 +61,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
61
61
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
62
62
  SOFTWARE.
63
63
  ================================================================================
64
- @peculiar/asn1-ecc@2.8.0
64
+ @peculiar/asn1-ecc@2.9.4
65
65
  Relationship: transitive production dependency
66
66
  Declared licence: MIT
67
67
  Licence source: LICENSE
@@ -88,7 +88,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
88
88
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
89
89
  SOFTWARE.
90
90
  ================================================================================
91
- @peculiar/asn1-pfx@2.8.0
91
+ @peculiar/asn1-pfx@2.9.4
92
92
  Relationship: transitive production dependency
93
93
  Declared licence: MIT
94
94
  Licence source: LICENSE
@@ -115,7 +115,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
115
115
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
116
116
  SOFTWARE.
117
117
  ================================================================================
118
- @peculiar/asn1-pkcs8@2.8.0
118
+ @peculiar/asn1-pkcs8@2.9.4
119
119
  Relationship: transitive production dependency
120
120
  Declared licence: MIT
121
121
  Licence source: LICENSE
@@ -142,7 +142,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
142
142
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
143
143
  SOFTWARE.
144
144
  ================================================================================
145
- @peculiar/asn1-pkcs9@2.8.0
145
+ @peculiar/asn1-pkcs9@2.9.4
146
146
  Relationship: transitive production dependency
147
147
  Declared licence: MIT
148
148
  Licence source: LICENSE
@@ -169,7 +169,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
169
169
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
170
170
  SOFTWARE.
171
171
  ================================================================================
172
- @peculiar/asn1-rsa@2.8.0
172
+ @peculiar/asn1-rsa@2.9.4
173
173
  Relationship: transitive production dependency
174
174
  Declared licence: MIT
175
175
  Licence source: LICENSE
@@ -196,7 +196,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
196
196
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
197
197
  SOFTWARE.
198
198
  ================================================================================
199
- @peculiar/asn1-schema@2.8.0
199
+ @peculiar/asn1-schema@2.9.4
200
200
  Relationship: transitive production dependency
201
201
  Declared licence: MIT
202
202
  Licence source: LICENSE
@@ -223,7 +223,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
223
223
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
224
224
  SOFTWARE.
225
225
  ================================================================================
226
- @peculiar/asn1-x509@2.8.0
226
+ @peculiar/asn1-x509@2.9.4
227
227
  Relationship: transitive production dependency
228
228
  Declared licence: MIT
229
229
  Licence source: LICENSE
@@ -250,7 +250,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
250
250
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
251
251
  SOFTWARE.
252
252
  ================================================================================
253
- @peculiar/asn1-x509-attr@2.8.0
253
+ @peculiar/asn1-x509-attr@2.9.4
254
254
  Relationship: transitive production dependency
255
255
  Declared licence: MIT
256
256
  Licence source: LICENSE
@@ -488,7 +488,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
488
488
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
489
489
  SOFTWARE.
490
490
  ================================================================================
491
- pvutils@1.1.5
491
+ pvutils@1.2.0
492
492
  Relationship: transitive production dependency
493
493
  Declared licence: MIT
494
494
  Licence source: LICENSE
@@ -612,7 +612,7 @@ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
612
612
  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
613
613
  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
614
614
  ================================================================================
615
- tldts@7.4.10
615
+ tldts@7.4.11
616
616
  Relationship: direct production dependency
617
617
  Declared licence: MIT
618
618
  Licence source: LICENSE
@@ -631,7 +631,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
631
631
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
632
632
  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
633
633
  ================================================================================
634
- tldts-core@7.4.10
634
+ tldts-core@7.4.11
635
635
  Relationship: transitive production dependency
636
636
  Declared licence: MIT
637
637
  Licence source: LICENSE
@@ -713,7 +713,7 @@ MIT License
713
713
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
714
714
  SOFTWARE
715
715
  ================================================================================
716
- undici@8.9.0
716
+ undici@8.10.0
717
717
  Relationship: direct production dependency
718
718
  Declared licence: MIT
719
719
  Licence source: LICENSE
@@ -1,79 +0,0 @@
1
- import { normalizeBulkSessionResult, } from "./bulk-session-model.js";
2
- import { limitedBulkSources } from "./bulk-source-coverage.js";
3
- export const MAX_BULK_RETRY_ROWS = 200;
4
- export const BULK_REVIEW_STALE_AFTER_DAYS = 7;
5
- function freshness(value, now = Date.now()) {
6
- if (typeof value !== 'string' || !Number.isFinite(Date.parse(value))) {
7
- return { observedAt: null, ageDays: null, state: 'unknown' };
8
- }
9
- const observedAt = new Date(value).toISOString();
10
- const ageDays = Math.max(0, Math.floor((now - Date.parse(observedAt)) / 86_400_000));
11
- return {
12
- observedAt,
13
- ageDays,
14
- state: ageDays >= BULK_REVIEW_STALE_AFTER_DAYS ? 'stale' : 'current',
15
- };
16
- }
17
- function retryRow(value, isStale) {
18
- const limitedSources = limitedBulkSources(value.domain, value.sourceCoverage)
19
- .sort()
20
- .slice(0, 12);
21
- const reasons = [];
22
- if (value.status === 'error')
23
- reasons.push('error');
24
- if (limitedSources.length)
25
- reasons.push('limited_source');
26
- if (isStale)
27
- reasons.push('stale');
28
- return reasons.length ? { domain: value.domain, reasons, limitedSources } : null;
29
- }
30
- export function buildBulkRetryPlan(values, mode, observedAtRaw = null, now = Date.now()) {
31
- const observationFreshness = freshness(observedAtRaw, now);
32
- const candidates = values
33
- .map((row) => normalizeBulkSessionResult(row))
34
- .filter((value) => Boolean(value))
35
- .map((value) => retryRow(value, observationFreshness.state === 'stale'))
36
- .filter((value) => Boolean(value));
37
- const rows = candidates.slice(0, MAX_BULK_RETRY_ROWS);
38
- return {
39
- mode,
40
- lookupRequests: rows.length,
41
- rows,
42
- destinations: mode === 'deep'
43
- ? ['registry RDAP', 'registrar RDAP when advertised', 'WHOIS', 'DNS resolvers', 'target HTTP(S)', 'target TLS']
44
- : ['registry RDAP', 'authoritative DNS delegation when registration sources are inconclusive'],
45
- capped: candidates.length > rows.length,
46
- freshness: observationFreshness,
47
- limitations: [
48
- 'This repeats the selected lookup profile; the current API does not request only one upstream source.',
49
- `Evidence is marked stale after ${BULK_REVIEW_STALE_AFTER_DAYS} days for review purposes; this is a local observation age, not an upstream record age.`,
50
- 'Optional third-party intelligence providers are not contacted by Bulk.',
51
- 'Sources deliberately skipped by the selected scan profile or deployment policy do not independently trigger a retry.',
52
- 'A retry can remain partial or fail and does not convert unavailable evidence into absence.',
53
- ],
54
- };
55
- }
56
- export function preservePriorBulkResult(previousRaw, currentRaw) {
57
- const previous = normalizeBulkSessionResult(previousRaw);
58
- const current = normalizeBulkSessionResult(currentRaw);
59
- if (!previous || !current || previous.domain !== current.domain || previous.status !== 'complete') {
60
- return { preserve: false, reason: '' };
61
- }
62
- if (current.status === 'error') {
63
- return { preserve: true, reason: 'The retry failed, so the prior settled result was retained.' };
64
- }
65
- if (previous.scanDepth === 'deep' && current.scanDepth === 'fast') {
66
- return { preserve: true, reason: 'The retry returned only Fast evidence, so the prior Deep result was retained.' };
67
- }
68
- const currentSources = new Map(current.sourceCoverage.map((item) => [item.source, item.state]));
69
- const regressed = previous.sourceCoverage
70
- .filter((item) => item.state === 'complete')
71
- .map((item) => item.source)
72
- .filter((source) => currentSources.get(source) !== 'complete');
73
- return regressed.length
74
- ? {
75
- preserve: true,
76
- reason: `The retry reduced complete coverage for ${regressed.slice(0, 6).join(', ')}, so the prior settled result was retained.`,
77
- }
78
- : { preserve: false, reason: '' };
79
- }
@@ -1,76 +0,0 @@
1
- // Authority-aware interpretation for compact Bulk source observations. Raw
2
- // source states remain unchanged; this layer only decides whether an observed
3
- // limitation was unexpected for the registry's published service paths.
4
- import { registryCapabilityFor } from "../../../../lib/registry-capabilities.mjs";
5
- const MAX_SOURCE_OBSERVATIONS = 32;
6
- const MAX_SOURCE_TEXT_LENGTH = 40;
7
- const CONTROL_RE = /[\u0000-\u001f\u007f]/u;
8
- const LIMITED_SOURCE_STATES = new Set([
9
- 'error',
10
- 'failed',
11
- 'inconclusive',
12
- 'partial',
13
- 'rate_limited',
14
- 'unavailable',
15
- 'unsupported',
16
- ]);
17
- function text(value) {
18
- return typeof value === 'string'
19
- && value.length <= MAX_SOURCE_TEXT_LENGTH
20
- && !CONTROL_RE.test(value)
21
- ? value.trim().toLowerCase()
22
- : '';
23
- }
24
- function record(value) {
25
- return value && typeof value === 'object' && !Array.isArray(value)
26
- ? value
27
- : null;
28
- }
29
- function observations(value) {
30
- return Array.isArray(value)
31
- ? value.slice(0, MAX_SOURCE_OBSERVATIONS).flatMap((entry) => {
32
- const item = record(entry);
33
- const source = text(item?.source);
34
- const state = text(item?.state);
35
- return source && state ? [{ source, state }] : [];
36
- })
37
- : [];
38
- }
39
- function expectedUnsupportedSources(domain) {
40
- const capability = registryCapabilityFor(domain);
41
- if (!capability)
42
- return new Set();
43
- const expected = new Set();
44
- if (capability.rdapAccessProfile === 'no-iana-service')
45
- expected.add('rdap');
46
- if (capability.whoisAccessProfile === 'no-iana-service')
47
- expected.add('whois');
48
- return expected;
49
- }
50
- export function isExpectedUnsupportedBulkSource(domain, observation) {
51
- const source = text(observation.source);
52
- const state = text(observation.state);
53
- return state === 'unsupported' && expectedUnsupportedSources(domain).has(source);
54
- }
55
- export function limitedBulkSources(domain, coverage) {
56
- const expectedUnsupported = expectedUnsupportedSources(domain);
57
- return observations(coverage)
58
- .filter(({ source, state }) => (LIMITED_SOURCE_STATES.has(state)
59
- && !(state === 'unsupported' && expectedUnsupported.has(source))))
60
- .map(({ source }) => source);
61
- }
62
- export function classifyBulkSourceCoverage(domain, coverage) {
63
- const retained = observations(coverage);
64
- if (!retained.length)
65
- return 'unrecorded';
66
- return limitedBulkSources(domain, retained).length ? 'limited' : 'complete';
67
- }
68
- export function describeBulkSourceCoverage(domain, observation) {
69
- const source = text(observation.source);
70
- const state = text(observation.state);
71
- if (!source || !state)
72
- return 'unrecorded source';
73
- return isExpectedUnsupportedBulkSource(domain, observation)
74
- ? `${source}: unsupported (no IANA-published service)`
75
- : `${source}: ${state}`;
76
- }