@slicedearth/whoisleuth-cli 2.3.0 → 2.5.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.
- package/NOTICE +8 -2
- package/README.md +19 -15
- package/bin/whoisleuth.mjs +8 -1
- package/cli/archive-content-digest.mjs +14 -0
- package/cli/archive-inspect.mjs +20 -53
- package/cli/argument-values.mjs +55 -0
- package/cli/arguments.mjs +24 -408
- package/cli/artifact-validation/case-response.mjs +65 -44
- package/cli/artifact-validation/investigation-capsule.mjs +52 -31
- package/cli/artifact-validation/lookup-evidence.mjs +15 -9
- package/cli/artifact-validation/signed-review.mjs +136 -68
- package/cli/artifact-validation/structure-primitives.mjs +1 -105
- package/cli/artifact-verify.mjs +92 -24
- package/cli/assurance-arguments.mjs +56 -0
- package/cli/assurance-command-definitions.mjs +179 -0
- package/cli/assurance-command-runner.mjs +40 -5
- package/cli/bagit-folder.mjs +57 -0
- package/cli/bagit-review.mjs +23 -0
- package/cli/bounded-json.mjs +1 -1
- package/cli/bulk-checkpoint.mjs +1 -1
- package/cli/bulk-command-runner.mjs +4 -2
- package/cli/bulk-output.mjs +29 -3
- package/cli/bulk.mjs +2 -3
- package/cli/case-command.mjs +212 -0
- package/cli/case-pack.mjs +50 -32
- package/cli/ci-report.mjs +6 -3
- package/cli/collection-arguments.mjs +123 -0
- package/cli/collection-command-definitions.mjs +330 -0
- package/cli/collection-preflight.mjs +8 -1
- package/cli/command-argument-grammar.mjs +8 -206
- package/cli/command-definition.mjs +329 -0
- package/cli/command-reference.mjs +94 -1357
- package/cli/command-seeds.mjs +18 -0
- package/cli/compare.mjs +1 -1
- package/cli/completion.mjs +298 -79
- package/cli/config-profile.mjs +8 -6
- package/cli/ct-event-intake.mjs +16 -16
- package/cli/discover.mjs +5 -4
- package/cli/discovery-observation-snapshot.mjs +46 -18
- package/cli/discovery-scan-command-runner.mjs +5 -3
- package/cli/discovery-scan.mjs +7 -4
- package/cli/discovery-snapshot.mjs +2 -2
- package/cli/discovery-workflow.mjs +5 -5
- package/cli/domain-control-monitor.mjs +50 -20
- package/cli/domain-control-observations.mjs +151 -103
- package/cli/errors.mjs +37 -6
- package/cli/evidence-arguments.mjs +46 -0
- package/cli/evidence-command-definitions.mjs +69 -0
- package/cli/evidence-command-runner.mjs +18 -8
- package/cli/evidence-signing.mjs +5 -4
- package/cli/external-observation-mapping.mjs +9 -10
- package/cli/formatters/terminal-command-formats.mjs +6 -0
- package/cli/formatters/terminal-lookup.mjs +24 -46
- package/cli/formatters/terminal-shared.mjs +5 -3
- package/cli/formatters/terminal-technology.mjs +114 -0
- package/cli/history-arguments.mjs +39 -0
- package/cli/history-command-definitions.mjs +88 -0
- package/cli/indicator-set-command.mjs +33 -0
- package/cli/inline-command-families.mjs +6 -46
- package/cli/inline-command-runner.mjs +4 -31
- package/cli/input.mjs +2 -2
- package/cli/intake-command.mjs +107 -0
- package/cli/interactive-launcher.mjs +2 -160
- package/cli/interchange-report.mjs +8 -10
- package/cli/investigation-artifacts.mjs +176 -0
- package/cli/investigation-checkpoint.mjs +10 -0
- package/cli/investigation-folder.mjs +114 -0
- package/cli/investigation-manifest.mjs +1 -112
- package/cli/investigation-package-review.mjs +103 -0
- package/cli/investigation-plan.mjs +38 -147
- package/cli/investigation-recipes.mjs +177 -0
- package/cli/investigation-run.mjs +256 -35
- package/cli/local-document-checkpoint.mjs +170 -0
- package/cli/local-mmdb-review.mjs +197 -17
- package/cli/lookalike-calibration.mjs +1 -1
- package/cli/lookup-brief.mjs +2 -2
- package/cli/lookup-browser.mjs +5 -12
- package/cli/lookup-command-runner.mjs +33 -39
- package/cli/lookup-diff.mjs +47 -12
- package/cli/lookup-plan.mjs +16 -14
- package/cli/lookup-reconcile.mjs +15 -12
- package/cli/lookup-timeline.mjs +7 -6
- package/cli/mail-header-review.mjs +24 -247
- package/cli/mail-review.mjs +75 -21
- package/cli/manual.mjs +1 -0
- package/cli/network-arguments.mjs +51 -0
- package/cli/network-command-definitions.mjs +130 -0
- package/cli/network-command-runner.mjs +45 -31
- package/cli/offline-evidence-review.mjs +39 -6
- package/cli/output-file.mjs +31 -5
- package/cli/posture.mjs +1 -1
- package/cli/registry-doctor.mjs +3 -1
- package/cli/registry-fixture-scaffold.mjs +1 -1
- package/cli/registry-support.mjs +1 -1
- package/cli/retained-artifact-diff.mjs +10 -9
- package/cli/review-arguments.mjs +131 -0
- package/cli/review-command-definitions.mjs +273 -0
- package/cli/review-command-runner.mjs +104 -149
- package/cli/risk-calibration.mjs +8 -8
- package/cli/runner.mjs +74 -37
- package/cli/saved-lookup.mjs +2 -2
- package/cli/selected-input-worker.mjs +63 -0
- package/cli/sharing-review.mjs +8 -80
- package/cli/signer-trust.mjs +104 -0
- package/cli/source-reliability.mjs +75 -84
- package/cli/support-arguments.mjs +49 -0
- package/cli/support-command-definitions.mjs +209 -0
- package/cli/support-command-runner.mjs +2 -2
- package/cli/terminal-input.mjs +168 -0
- package/cli/workflow-arguments.mjs +86 -0
- package/cli/workflow-command-definitions.mjs +78 -0
- package/cli/workflow-command-runner.mjs +75 -13
- package/docs/cli-reference.md +103 -30
- package/docs/cli.md +510 -25
- package/docs/contextual-reviews.md +155 -0
- package/lib/abort.mjs +4 -1
- package/lib/analyst-taxonomy.mjs +2 -40
- package/lib/application-version.mjs +1 -1
- package/lib/availability.mjs +27 -754
- package/lib/bounded-contract-normalizers.mjs +4 -0
- package/lib/bounded-file.mjs +3 -1
- package/lib/bounded-html-document.mjs +125 -0
- package/lib/bounded-json.mjs +2 -290
- package/lib/browser-library-profile.mjs +99 -130
- package/lib/browser-library-worker.mjs +112 -0
- package/lib/candidate-provenance-bounds.mjs +2 -0
- package/lib/classify.mjs +1 -1
- package/lib/client-behavior-profile.mjs +3 -3
- package/lib/ct-query.mjs +2 -27
- package/lib/ct-response-bounds.mjs +2 -9
- package/lib/ct-search.mjs +89 -46
- package/lib/dmarc-discovery.mjs +62 -0
- package/lib/dns-convergence-review.mjs +21 -8
- package/lib/dns-delegation-health.mjs +36 -22
- package/lib/dns-inheritance-review.mjs +207 -0
- package/lib/dns-intelligence.mjs +44 -29
- package/lib/dns-mx.mjs +1 -1
- package/lib/dns-query-coverage.mjs +40 -0
- package/lib/dnssec-evidence-validation.mjs +11 -3
- package/lib/domain-assurance.mjs +1 -1
- package/lib/domain-change-packet.mjs +18 -14
- package/lib/domain-change-review.mjs +40 -16
- package/lib/domain-control-flight-recorder.mjs +301 -124
- package/lib/domain-control-manifest.mjs +126 -58
- package/lib/domain-posture-analysis.mjs +41 -11
- package/lib/domain-posture-parsers.mjs +88 -6
- package/lib/domain-posture.mjs +120 -32
- package/lib/domain-registration.mjs +392 -0
- package/lib/evidence-export-privacy.mjs +568 -0
- package/lib/evidence-export.mjs +12 -593
- package/lib/evidence-report.mjs +7 -2
- package/lib/external-intelligence-risk.mjs +2 -124
- package/lib/favicon.mjs +117 -41
- package/lib/generated/cisa-kev-catalog.mjs +50 -4
- package/lib/generated/registrar-standing-catalogue.mjs +18 -10
- package/lib/generated/retire-browser-catalog.mjs +2742 -1034
- package/lib/generated/{unicode-confusables-17.mjs → unicode-confusables.mjs} +33 -33
- package/lib/html-signals.mjs +87 -85
- package/lib/http-evidence-bounds.mjs +2 -7
- package/lib/http-intelligence.mjs +52 -27
- package/lib/idn-confusable-policy.mjs +6 -4
- package/lib/idn-confusables.mjs +1 -1
- package/lib/interchange-fidelity-registry.mjs +10 -0
- package/lib/lookup-cache.mjs +34 -11
- package/lib/lookup-child-profile-contract.mjs +24 -15
- package/lib/lookup-contract-primitives.mjs +1 -1
- package/lib/lookup-diagnostics.mjs +2 -1
- package/lib/lookup-network-evidence-bounds.mjs +1 -10
- package/lib/lookup-progress.mjs +79 -13
- package/lib/lookup-response-contract.mjs +1077 -0
- package/lib/lookup-response.mjs +11 -24
- package/lib/lookup-source-progress.mjs +24 -35
- package/lib/lookup.mjs +49 -28
- package/lib/observed-network-context.mjs +43 -27
- package/lib/opportunity-scoring.mjs +2 -107
- package/lib/outbound-identity.mjs +1 -1
- package/lib/outbound-request-bounds.mjs +4 -1
- package/lib/page-fingerprints.mjs +87 -134
- package/lib/page-role-profile.mjs +12 -8
- package/lib/perceptual-hash-comparison.mjs +2 -34
- package/lib/perceptual-hash.mjs +79 -24
- package/lib/portable-generator.mjs +2 -29
- package/lib/project-metadata.mjs +2 -8
- package/lib/rdap-bootstrap.mjs +72 -22
- package/lib/rdap-client.mjs +26 -14
- package/lib/rdap-normalization.mjs +33 -18
- package/lib/rdap-search-workbench.mjs +1 -1
- package/lib/rdap-transport.mjs +47 -20
- package/lib/rdap-types.mjs +2 -1
- package/lib/rdap.mjs +18 -3
- package/lib/registrable-domain.mjs +2 -36
- package/lib/registrar-standing-contract.mjs +2 -1
- package/lib/registrar-standing.mjs +9 -13
- package/lib/registry-capabilities.mjs +7 -3
- package/lib/registry-capability-catalogue.mjs +29 -20
- package/lib/registry-dates.mjs +2 -190
- package/lib/registry-insights.mjs +56 -18
- package/lib/response-policy.mjs +86 -48
- package/lib/risk-calibration-summary.mjs +2 -338
- package/lib/risk-scoring.mjs +2 -345
- package/lib/rpki-evidence.mjs +8 -5
- package/lib/safe-fetch.mjs +54 -47
- package/lib/scoring-evidence-quality.mjs +2 -107
- package/lib/security-txt.mjs +10 -7
- package/lib/semantic-version.mjs +2 -54
- package/lib/service-binding-dns.mjs +35 -0
- package/lib/smtp-transport-review.mjs +75 -34
- package/lib/sslbl-certificates.generated.mjs +141 -138
- package/lib/static-html-analysis.mjs +117 -146
- package/lib/structured-data-identity.mjs +2 -1
- package/lib/technology-markup.mjs +100 -0
- package/lib/threat-intelligence-manifests.mjs +11 -139
- package/lib/threat-intelligence-runtime.mjs +24 -52
- package/lib/threat-intelligence-targets.mjs +6 -34
- package/lib/threat-intelligence-types.mjs +2 -36
- package/lib/typosquat-generator.mjs +29 -16
- package/lib/urlhaus-intelligence.mjs +8 -3
- package/lib/urlscan-intelligence.mjs +1 -1
- package/lib/website-enrichment.mjs +388 -0
- package/lib/website-security-posture.mjs +30 -6
- package/lib/website-technology.mjs +111 -71
- package/lib/whois-authority.mjs +55 -5
- package/lib/whois-chain.mjs +9 -2
- package/lib/whois-common-formats.mjs +5 -5
- package/lib/whois-contracts.mjs +2 -1
- package/lib/whois-dialects.mjs +2 -2
- package/lib/whois-normalization.mjs +3 -1
- package/lib/whois-parser-support.mjs +8 -1
- package/lib/whois-parser.mjs +5 -3
- package/lib/whois-transport.mjs +23 -7
- package/package.json +14 -14
- package/packages/analysis/analyst-taxonomy.mjs +40 -0
- package/packages/analysis/bounded-json.mjs +330 -0
- package/packages/analysis/candidate-provenance-bounds.mjs +4 -0
- package/packages/analysis/cli-argument-grammar.mjs +216 -0
- package/packages/analysis/ct-query.mjs +27 -0
- package/packages/analysis/ct-response-bounds.mjs +9 -0
- package/packages/analysis/external-intelligence-risk.mjs +124 -0
- package/packages/analysis/http-evidence-bounds.mjs +8 -0
- package/packages/analysis/lookup-network-evidence-bounds.mjs +10 -0
- package/packages/analysis/opportunity-scoring.mjs +107 -0
- package/packages/analysis/perceptual-hash-comparison.mjs +34 -0
- package/packages/analysis/portable-generator.mjs +29 -0
- package/packages/analysis/project-metadata.mjs +8 -0
- package/packages/analysis/registrable-domain.mjs +56 -0
- package/packages/analysis/risk-calibration-summary.mjs +352 -0
- package/packages/analysis/risk-scoring.mjs +345 -0
- package/packages/analysis/scoring-evidence-quality.mjs +107 -0
- package/packages/analysis/semantic-version.mjs +54 -0
- package/packages/analysis/threat-intelligence-types.mjs +75 -0
- package/packages/cases/case-attachment-model.mjs +83 -0
- package/packages/cases/case-evidence-links.mjs +135 -0
- package/packages/cases/case-evidence-model.mjs +229 -69
- package/packages/cases/case-evidence-quality.mjs +5 -0
- package/packages/cases/case-export-input.mjs +55 -0
- package/packages/cases/case-incident-context.mjs +52 -0
- package/packages/cases/case-markdown.mjs +11 -0
- package/packages/cases/case-migration-model.mjs +98 -48
- package/packages/cases/case-model.mjs +1 -0
- package/packages/cases/case-recheck-model.mjs +149 -0
- package/packages/cases/case-record-contracts.mjs +1 -1
- package/packages/cases/case-record-core.mjs +4 -3
- package/packages/cases/case-record-operations.mjs +123 -133
- package/packages/cases/case-record-projection.mjs +30 -20
- package/packages/cases/case-record-version-input.mjs +19 -0
- package/packages/cases/case-report.mjs +114 -67
- package/packages/cases/case-requested-evidence.mjs +146 -0
- package/packages/cases/case-response-actions.mjs +756 -0
- package/packages/cases/case-response-model.mjs +49 -1129
- package/packages/cases/case-response-outcomes.mjs +338 -0
- package/packages/cases/case-response-packet-render.mjs +157 -0
- package/packages/cases/case-response-packet-types.mjs +1 -0
- package/packages/cases/case-response-packet-vocabulary.mjs +48 -0
- package/packages/cases/case-response-packet.mjs +58 -593
- package/packages/cases/case-response-records.mjs +93 -0
- package/packages/cases/case-response-review-inputs.mjs +382 -0
- package/packages/cases/case-response-values.mjs +85 -0
- package/packages/cases/case-review-package.mjs +45 -0
- package/packages/cases/case-selection.mjs +46 -0
- package/packages/cases/case-storage-model.mjs +26 -1
- package/packages/cases/case-workflow-metadata.mjs +225 -0
- package/packages/cases/case-workflow-migration.mjs +43 -0
- package/packages/cases/http-summary.mjs +6 -1
- package/packages/cases/response-route-freshness.mjs +16 -0
- package/packages/comparison/capture-context.mjs +61 -0
- package/packages/comparison/comparison-ledger-bulk.mjs +4 -1
- package/packages/comparison/favicon-similarity.mjs +1 -1
- package/packages/comparison/page-similarity.mjs +3 -2
- package/packages/comparison/relationship-evidence.mjs +103 -14
- package/packages/comparison/relationship-provenance.mjs +99 -0
- package/packages/contracts/analyst-interchange.mjs +234 -0
- package/packages/contracts/analyst-review-state-contract.mjs +11 -0
- package/packages/contracts/analyst-review-state.mjs +5 -4
- package/packages/contracts/case-portability.mjs +370 -114
- package/packages/contracts/case-views-contract.mjs +12 -0
- package/packages/contracts/case-views.mjs +26 -0
- package/packages/contracts/cli-command-semantics.mjs +3 -0
- package/packages/contracts/cli-grammar.mjs +6 -0
- package/packages/contracts/cli-lookup-brief.mjs +2 -0
- package/packages/contracts/cli-lookup.mjs +0 -1
- package/packages/contracts/context-review.mjs +24 -0
- package/packages/contracts/document-intake.mjs +11 -0
- package/packages/contracts/domain-control-flight-recorder.mjs +98 -44
- package/packages/contracts/domain-control-manifest.mjs +128 -74
- package/packages/contracts/domain-control-monitor.mjs +59 -45
- package/packages/contracts/domain-control-review.mjs +200 -121
- package/packages/contracts/domain-name.mjs +21 -0
- package/packages/contracts/external-observation-interchange.mjs +2 -1
- package/packages/contracts/extracted-domain-lifecycle.mjs +11 -32
- package/packages/contracts/har-review.mjs +2 -0
- package/packages/contracts/http-url.mjs +2 -0
- package/packages/contracts/identity-events.mjs +4 -0
- package/packages/contracts/investigation-package-limits.mjs +24 -0
- package/packages/contracts/investigation-portability.mjs +56 -27
- package/packages/contracts/investigation-run.mjs +18 -0
- package/packages/contracts/ip-address.mjs +44 -0
- package/packages/contracts/lookup-evidence.mjs +35 -0
- package/packages/contracts/mail-authentication.mjs +5 -0
- package/packages/contracts/message-intake.mjs +31 -0
- package/packages/contracts/offline-comparison.mjs +4 -2
- package/packages/contracts/page-fingerprints.mjs +15 -0
- package/packages/contracts/passive-mail.mjs +10 -0
- package/packages/contracts/risk-calibration.mjs +0 -1
- package/packages/contracts/schema-compatibility.mjs +1 -1
- package/packages/contracts/schema-lifecycle.mjs +83 -153
- package/packages/contracts/selected-file-limits.mjs +6 -0
- package/packages/contracts/vulnerability-identifiers.mjs +5 -0
- package/packages/contracts/web-capture.mjs +10 -0
- package/packages/contracts/workspace-portability.mjs +219 -238
- package/packages/evidence/artifact-integrity.mjs +8 -2
- package/packages/evidence/artifact-structure.mjs +105 -0
- package/packages/evidence/collection-quality.mjs +67 -0
- package/packages/evidence/decision-fact.mjs +4 -1
- package/packages/evidence/dns-query-outcome.mjs +72 -0
- package/packages/evidence/domain-control-runtime.mjs +155 -27
- package/packages/evidence/domain-posture-context.mjs +79 -0
- package/packages/evidence/email-recipient.mjs +18 -0
- package/packages/evidence/image-regions.mjs +56 -0
- package/packages/evidence/latest-observations.mjs +28 -0
- package/packages/evidence/lookup-target.mjs +108 -0
- package/packages/evidence/observation.mjs +18 -1
- package/packages/evidence/passphrase-encryption.mjs +44 -0
- package/packages/evidence/png-pixels.mjs +117 -0
- package/packages/evidence/public-address-policy.mjs +83 -0
- package/packages/evidence/record-identity.mjs +72 -0
- package/packages/evidence/registry-dates.mjs +190 -0
- package/packages/evidence/retained-file.mjs +69 -0
- package/packages/evidence/review-age.mjs +14 -0
- package/packages/interchange/bagit.mjs +342 -0
- package/packages/interchange/bounded-zip-extraction.mjs +278 -0
- package/packages/interchange/crc32.mjs +17 -0
- package/packages/interchange/defensive-indicator-export.mjs +264 -0
- package/packages/interchange/external-findings-import.mjs +93 -61
- package/packages/interchange/managed-indicator-export.mjs +35 -0
- package/packages/interchange/managed-indicator-set.mjs +210 -0
- package/packages/interchange/misp-indicator-export.mjs +93 -0
- package/packages/interchange/sharing-policy.mjs +56 -0
- package/packages/interchange/stix-indicator-export.mjs +129 -0
- package/packages/interchange/web-capture-import.mjs +389 -0
- package/packages/investigation/acquisition-decision-packet.mjs +1 -1
- package/packages/investigation/bulk-mail-exposure.mjs +9 -14
- package/packages/investigation/capture-coverage.mjs +57 -0
- package/packages/investigation/connector-provenance-review.mjs +100 -0
- package/packages/investigation/context-review.mjs +35 -0
- package/packages/investigation/document-intake.mjs +81 -0
- package/packages/investigation/docx-intake.mjs +133 -0
- package/packages/investigation/domain-history-review.mjs +100 -0
- package/packages/investigation/domain-transition-review.mjs +47 -0
- package/packages/investigation/evidence-coverage-ledger.mjs +9 -1
- package/packages/investigation/har-intake.mjs +68 -0
- package/packages/investigation/identity-event-intake.mjs +131 -0
- package/packages/investigation/identity-incident-review.mjs +39 -0
- package/packages/investigation/incident-sequence-review.mjs +58 -0
- package/packages/investigation/intake-report.mjs +19 -0
- package/packages/investigation/investigation-bagit.mjs +14 -0
- package/packages/investigation/investigation-capsule.mjs +33 -5
- package/packages/investigation/investigation-manifest.mjs +200 -0
- package/packages/investigation/investigation-package-crypto.mjs +98 -0
- package/packages/investigation/investigation-package.mjs +200 -0
- package/packages/investigation/link-intake.mjs +179 -0
- package/packages/investigation/lookup-asset-graph-inputs.mjs +38 -0
- package/packages/investigation/lookup-asset-graph.mjs +161 -141
- package/packages/investigation/lookup-claim-readiness.mjs +18 -0
- package/packages/investigation/lookup-investigation-brief.mjs +3 -3
- package/packages/investigation/lookup-task-guidance.mjs +58 -0
- package/packages/investigation/mail-authentication-review.mjs +222 -0
- package/packages/investigation/mail-header-identity.mjs +209 -0
- package/packages/investigation/message-intake.mjs +195 -0
- package/packages/investigation/page-behaviour.mjs +113 -0
- package/packages/investigation/pdf-intake.mjs +155 -0
- package/packages/investigation/platform-continuity-review.mjs +60 -0
- package/packages/investigation/qr-intake.mjs +56 -0
- package/packages/investigation/requested-action-hints.mjs +11 -0
- package/packages/investigation/selected-input-review.mjs +17 -0
- package/packages/investigation/storefront-review.mjs +70 -0
- package/packages/monitoring/analyst-review-state.mjs +38 -90
- package/packages/workspace/brand-profile-model.mjs +160 -22
- package/packages/workspace/bulk-columns.mjs +17 -0
- package/packages/workspace/bulk-review-model.mjs +39 -31
- package/packages/workspace/bulk-session-model.mjs +112 -27
- package/packages/workspace/bulk-sort.mjs +1 -0
- package/packages/workspace/campaign-model.mjs +1 -1
- package/packages/workspace/case-views.mjs +102 -0
- package/packages/workspace/detection-rule-model.mjs +1 -1
- package/packages/workspace/hostile-input.mjs +29 -8
- package/packages/workspace/investigation-guide.mjs +2 -2
- package/packages/workspace/investigation-template-model.mjs +45 -6
- package/packages/workspace/page-baseline.mjs +20 -17
- package/packages/workspace/relationship-observation-model.mjs +54 -35
- package/packages/workspace/shortlist-model.mjs +14 -5
- package/packages/workspace/watchlist-history.mjs +30 -14
- package/packages/workspace/watchlist-store.mjs +24 -6
- package/packages/workspace/website-snapshot-model.mjs +98 -33
- package/packages/workspace/workspace-archive-crypto.mjs +41 -61
- package/packages/workspace/workspace-archive.mjs +90 -25
- package/third-party-notices.txt +577 -17
- package/lib/hostname.mjs +0 -38
- package/packages/investigation/bulk-review-export.mjs +0 -63
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readCaseEvidenceRequest, readCasePacketAmendment, assertEvidenceRequestEvent, assertEvidenceRequestHistory, assertPacketAmendmentSelection } from "../../packages/cases/case-requested-evidence.mjs";
|
|
2
3
|
import { canonicalArtifactJsonV2 } from "../../packages/evidence/artifact-integrity.mjs";
|
|
3
|
-
import { CASE_RESPONSE_PACKET_VERSION,
|
|
4
|
+
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
5
|
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
|
-
import { HEX_DIGEST_RE, array, boolean, domain, enumeration, exact, fail, integer, iso, optionalText, sameValues, strings, text, } from "./structure-primitives.mjs";
|
|
6
|
+
import { HEX_DIGEST_RE, array, boolean, domain, enumeration, exact, exactOptional, fail, integer, iso, optionalText, sameValues, strings, text, } from "./structure-primitives.mjs";
|
|
6
7
|
const CASE_RESPONSE_PREFLIGHT_IDS_V6 = [
|
|
7
8
|
'required_incident_fields',
|
|
8
9
|
'evidence_pins',
|
|
@@ -224,15 +225,15 @@ function validateCurrentActionSummary(value, label) {
|
|
|
224
225
|
}
|
|
225
226
|
return total;
|
|
226
227
|
}
|
|
227
|
-
function
|
|
228
|
-
const
|
|
229
|
-
const
|
|
228
|
+
function validateVersionedCaseResponsePacket(value, version) {
|
|
229
|
+
const hasPlatformRoutes = version >= PUBLISHED_V2_3_CASE_RESPONSE_PACKET_VERSION;
|
|
230
|
+
const hasActionBinding = version >= PUBLISHED_V2_2_CASE_RESPONSE_PACKET_VERSION;
|
|
230
231
|
const root = exact(value, [
|
|
231
232
|
'schema', 'schemaVersion', 'generatedAt', 'reviewRequired', 'submissionPerformed', 'profile', 'case',
|
|
232
233
|
'incident', 'contacts', 'selectedEvidence', 'contradictions', 'readiness', 'artefactReferences',
|
|
233
234
|
'authorisation', 'preflight', 'escalationHistory', 'escalationHistoryOmitted',
|
|
234
235
|
'escalationHistoryLimitations', 'responseLifecycle', 'provenance', 'integrity',
|
|
235
|
-
...(
|
|
236
|
+
...(hasActionBinding ? ['recipientRoute', 'actionBinding'] : []),
|
|
236
237
|
], `Case-response packet v${version}`);
|
|
237
238
|
if (root.schemaVersion !== version)
|
|
238
239
|
fail(`Case-response packet v${version}`);
|
|
@@ -240,14 +241,14 @@ function validateCaseResponsePacketV7ToV9(value, version) {
|
|
|
240
241
|
if (root.reviewRequired !== true || root.submissionPerformed !== false)
|
|
241
242
|
fail('Case-response packet review state');
|
|
242
243
|
const profile = exact(root.profile, ['id', 'label', 'audience', 'subject', 'checklist', 'evidenceOrder', 'includedEvidence', 'excludedEvidence', 'redactions', 'attachments', 'followUpFields'], 'Case-response profile');
|
|
243
|
-
const profileId = enumeration(profile.id,
|
|
244
|
+
const profileId = enumeration(profile.id, hasPlatformRoutes ? RESPONSE_PACKET_PROFILE_IDS : PRE_PLATFORM_PROFILE_IDS, 'Case-response profile id');
|
|
244
245
|
text(profile.label, 'Case-response profile label', 200);
|
|
245
246
|
text(profile.audience, 'Case-response profile audience', 300);
|
|
246
247
|
text(profile.subject, 'Case-response profile subject', 500);
|
|
247
248
|
for (const key of ['checklist', 'evidenceOrder', 'includedEvidence', 'excludedEvidence', 'redactions', 'attachments', 'followUpFields'])
|
|
248
249
|
strings(profile[key], `Case-response profile ${key}`, 24, 500);
|
|
249
250
|
const selectedProfile = RESPONSE_PACKET_PROFILES.find((candidate) => candidate.id === profileId);
|
|
250
|
-
const expectedRedactions =
|
|
251
|
+
const expectedRedactions = hasActionBinding ? selectedProfile?.redactions : legacyProfileRedactions(profileId);
|
|
251
252
|
if (!selectedProfile
|
|
252
253
|
|| profile.label !== selectedProfile.label
|
|
253
254
|
|| profile.audience !== selectedProfile.audience
|
|
@@ -287,9 +288,9 @@ function validateCaseResponsePacketV7ToV9(value, version) {
|
|
|
287
288
|
const contactKeys = new Set();
|
|
288
289
|
for (const candidate of contacts) {
|
|
289
290
|
const contact = exact(candidate, [
|
|
290
|
-
'kind', 'contact', 'source', 'observedAt', ...(
|
|
291
|
+
'kind', 'contact', 'source', 'observedAt', ...(hasPlatformRoutes ? ['reviewAfter'] : []), 'freshness', 'limitations',
|
|
291
292
|
], 'Case-response contact');
|
|
292
|
-
const kind = enumeration(contact.kind,
|
|
293
|
+
const kind = enumeration(contact.kind, hasPlatformRoutes ? RESPONSE_CONTACT_KINDS : PRE_PLATFORM_CONTACT_KINDS, 'Case-response contact kind');
|
|
293
294
|
const contactValue = text(contact.contact, 'Case-response contact value', MAX_RESPONSE_RECIPIENT_LENGTH);
|
|
294
295
|
const key = `${kind}\u0000${contactValue.toLowerCase()}`;
|
|
295
296
|
if (contactKeys.has(key))
|
|
@@ -297,13 +298,13 @@ function validateCaseResponsePacketV7ToV9(value, version) {
|
|
|
297
298
|
contactKeys.add(key);
|
|
298
299
|
text(contact.source, 'Case-response contact source', 120);
|
|
299
300
|
iso(contact.observedAt, 'Case-response contact observedAt', true);
|
|
300
|
-
if (
|
|
301
|
+
if (hasPlatformRoutes)
|
|
301
302
|
iso(contact.reviewAfter, 'Case-response contact reviewAfter', true);
|
|
302
303
|
const freshness = enumeration(contact.freshness, ['current', 'stale', 'unknown'], 'Case-response contact freshness');
|
|
303
304
|
const routeAge = contact.observedAt === null
|
|
304
305
|
? null
|
|
305
306
|
: Date.parse(root.generatedAt) - Date.parse(contact.observedAt);
|
|
306
|
-
const reviewExpired =
|
|
307
|
+
const reviewExpired = hasPlatformRoutes && contact.reviewAfter !== null
|
|
307
308
|
&& Date.parse(root.generatedAt) >= Date.parse(contact.reviewAfter);
|
|
308
309
|
const expectedFreshness = routeAge === null
|
|
309
310
|
? 'unknown'
|
|
@@ -319,23 +320,23 @@ function validateCaseResponsePacketV7ToV9(value, version) {
|
|
|
319
320
|
let selectedActionId = null;
|
|
320
321
|
let lineageActionIds = [];
|
|
321
322
|
let lineageComplete = true;
|
|
322
|
-
if (
|
|
323
|
+
if (hasActionBinding) {
|
|
323
324
|
if (root.recipientRoute !== null) {
|
|
324
325
|
recipientRoute = exact(root.recipientRoute, [
|
|
325
|
-
'actionId', 'kind', 'contact', 'source', 'observedAt', ...(
|
|
326
|
+
'actionId', 'kind', 'contact', 'source', 'observedAt', ...(hasPlatformRoutes ? ['reviewAfter'] : []), 'freshness', 'limitations',
|
|
326
327
|
], 'Case-response recipient route');
|
|
327
328
|
text(recipientRoute.actionId, 'Case-response recipient action id', 64);
|
|
328
|
-
enumeration(recipientRoute.kind, [...(
|
|
329
|
+
enumeration(recipientRoute.kind, [...(hasPlatformRoutes ? RESPONSE_CONTACT_KINDS : PRE_PLATFORM_CONTACT_KINDS), 'manual'], 'Case-response recipient kind');
|
|
329
330
|
text(recipientRoute.contact, 'Case-response recipient value', MAX_RESPONSE_RECIPIENT_LENGTH);
|
|
330
331
|
text(recipientRoute.source, 'Case-response recipient source', 120);
|
|
331
332
|
iso(recipientRoute.observedAt, 'Case-response recipient observedAt', true);
|
|
332
|
-
if (
|
|
333
|
+
if (hasPlatformRoutes)
|
|
333
334
|
iso(recipientRoute.reviewAfter, 'Case-response recipient reviewAfter', true);
|
|
334
335
|
const freshness = enumeration(recipientRoute.freshness, ['current', 'stale', 'unknown'], 'Case-response recipient freshness');
|
|
335
336
|
const routeAge = recipientRoute.observedAt === null
|
|
336
337
|
? null
|
|
337
338
|
: Date.parse(root.generatedAt) - Date.parse(recipientRoute.observedAt);
|
|
338
|
-
const reviewExpired =
|
|
339
|
+
const reviewExpired = hasPlatformRoutes && recipientRoute.reviewAfter !== null
|
|
339
340
|
&& Date.parse(root.generatedAt) >= Date.parse(recipientRoute.reviewAfter);
|
|
340
341
|
const expectedFreshness = routeAge === null
|
|
341
342
|
? 'unknown'
|
|
@@ -370,7 +371,7 @@ function validateCaseResponsePacketV7ToV9(value, version) {
|
|
|
370
371
|
|| selectedContact.contact !== recipientRoute.contact
|
|
371
372
|
|| selectedContact.source !== recipientRoute.source
|
|
372
373
|
|| selectedContact.observedAt !== recipientRoute.observedAt
|
|
373
|
-
|| (
|
|
374
|
+
|| (hasPlatformRoutes && selectedContact.reviewAfter !== recipientRoute.reviewAfter)
|
|
374
375
|
|| selectedContact.freshness !== recipientRoute.freshness)
|
|
375
376
|
fail('Case-response selected contact projection');
|
|
376
377
|
}
|
|
@@ -378,14 +379,16 @@ function validateCaseResponsePacketV7ToV9(value, version) {
|
|
|
378
379
|
const evidence = array(root.selectedEvidence, 'Case-response selected evidence', MAX_RESPONSE_SELECTED_EVIDENCE);
|
|
379
380
|
const evidenceIds = new Set();
|
|
380
381
|
for (const candidate of evidence) {
|
|
381
|
-
const item =
|
|
382
|
+
const item = exactOptional(candidate, ['id', 'label', 'source', 'observedAt', 'completeness', 'limitations'], version > PUBLISHED_V2_3_CASE_RESPONSE_PACKET_VERSION ? ['observationHostname'] : [], 'Case-response selected evidence');
|
|
382
383
|
const id = text(item.id, 'Case-response evidence id', 64);
|
|
383
384
|
if (evidenceIds.has(id))
|
|
384
385
|
fail('Case-response selected evidence');
|
|
385
386
|
evidenceIds.add(id);
|
|
386
387
|
text(item.label, 'Case-response evidence label', 80);
|
|
387
388
|
text(item.source, 'Case-response evidence source', 120);
|
|
388
|
-
|
|
389
|
+
if (item.observationHostname !== undefined)
|
|
390
|
+
domain(item.observationHostname, 'Case-response evidence hostname');
|
|
391
|
+
iso(item.observedAt, 'Case-response evidence observedAt', version > PUBLISHED_V2_3_CASE_RESPONSE_PACKET_VERSION);
|
|
389
392
|
enumeration(item.completeness, ['complete', 'partial', 'inconclusive', 'unknown'], 'Case-response evidence completeness');
|
|
390
393
|
strings(item.limitations, 'Case-response evidence limitations', 8, 240);
|
|
391
394
|
}
|
|
@@ -412,7 +415,7 @@ function validateCaseResponsePacketV7ToV9(value, version) {
|
|
|
412
415
|
...(profileId === 'internal_soc' ? [] : ['recipient_route']),
|
|
413
416
|
...(['registrar', 'registry', 'network_hosting'].includes(profileId) ? ['infrastructure_responsibility'] : []),
|
|
414
417
|
]);
|
|
415
|
-
const requiredContact =
|
|
418
|
+
const requiredContact = hasActionBinding
|
|
416
419
|
? recipientRoute ?? undefined
|
|
417
420
|
: selectedProfile?.requiredContactKind
|
|
418
421
|
? contacts.find((candidate) => candidate.kind === selectedProfile.requiredContactKind)
|
|
@@ -421,7 +424,7 @@ function validateCaseResponsePacketV7ToV9(value, version) {
|
|
|
421
424
|
observed_behaviour: 'complete',
|
|
422
425
|
exact_url: 'complete',
|
|
423
426
|
observation_time: expectedObservationAge(incident.observedAt, root.generatedAt).refreshRecommended ? 'stale' : 'complete',
|
|
424
|
-
capture_provenance: evidence.length ? 'complete' : '
|
|
427
|
+
capture_provenance: !evidence.length ? 'unavailable' : evidence.every((candidate) => candidate.observedAt !== null) ? 'complete' : 'partial',
|
|
425
428
|
recipient_route: !requiredContact
|
|
426
429
|
? 'not_provided'
|
|
427
430
|
: requiredContact.freshness === 'stale'
|
|
@@ -507,8 +510,8 @@ function validateCaseResponsePacketV7ToV9(value, version) {
|
|
|
507
510
|
|| (authorisationStatus === 'draft' && authorisation.confirmedAt !== null))
|
|
508
511
|
fail('Case-response authorisation');
|
|
509
512
|
const preflight = exact(root.preflight, ['version', 'status', 'canExport', 'counts', 'checks', 'actionSummary'], 'Case-response preflight');
|
|
510
|
-
const preflightIds =
|
|
511
|
-
if (preflight.version !== (
|
|
513
|
+
const preflightIds = hasActionBinding ? CASE_RESPONSE_PREFLIGHT_IDS_V8 : CASE_RESPONSE_PREFLIGHT_IDS_V7;
|
|
514
|
+
if (preflight.version !== (hasActionBinding ? 3 : 2))
|
|
512
515
|
fail('Case-response preflight');
|
|
513
516
|
const checks = array(preflight.checks, 'Case-response preflight checks', preflightIds.length, preflightIds.length);
|
|
514
517
|
const actualCounts = { block: 0, caution: 0, pass: 0 };
|
|
@@ -532,8 +535,8 @@ function validateCaseResponsePacketV7ToV9(value, version) {
|
|
|
532
535
|
const history = array(root.escalationHistory, 'Case-response escalation history', MAX_RESPONSE_ACTION_HISTORY);
|
|
533
536
|
const escalationHistoryOmitted = integer(root.escalationHistoryOmitted, 'Case-response omitted actions', 0, MAX_CASE_ACTIONS);
|
|
534
537
|
const escalationHistoryLimitations = strings(root.escalationHistoryLimitations, 'Case-response history limitations', 4, 600);
|
|
535
|
-
if ((!
|
|
536
|
-
|| (
|
|
538
|
+
if ((!hasActionBinding && history.length !== Math.min(actionTotal, MAX_RESPONSE_ACTION_HISTORY))
|
|
539
|
+
|| (hasActionBinding && history.length > Math.min(actionTotal, MAX_RESPONSE_ACTION_HISTORY))
|
|
537
540
|
|| escalationHistoryOmitted !== actionTotal - history.length
|
|
538
541
|
|| (escalationHistoryOmitted > 0) !== (escalationHistoryLimitations.length > 0))
|
|
539
542
|
fail('Case-response action-history bounds');
|
|
@@ -541,24 +544,25 @@ function validateCaseResponsePacketV7ToV9(value, version) {
|
|
|
541
544
|
const actionIdOrder = [];
|
|
542
545
|
const providerEvents = [];
|
|
543
546
|
for (const candidate of history) {
|
|
544
|
-
const action =
|
|
547
|
+
const action = exactOptional(candidate, [
|
|
545
548
|
'actionId', 'type', 'recipient', 'contactSource', 'state', 'reference',
|
|
546
549
|
'providerOutcome', 'outcomeDetail', 'originActionId', 'historyOmitted',
|
|
547
550
|
'historyLimitations', 'transitions', 'createdAt', 'updatedAt',
|
|
548
|
-
...(
|
|
549
|
-
...(
|
|
550
|
-
], 'Case-response escalation action');
|
|
551
|
+
...(hasActionBinding ? ['routeObservedAt'] : []),
|
|
552
|
+
...(hasPlatformRoutes ? ['routeReviewAfter'] : []),
|
|
553
|
+
], Number(root.schemaVersion) >= 11 ? ['amendment'] : [], 'Case-response escalation action');
|
|
554
|
+
readCasePacketAmendment(action.amendment);
|
|
551
555
|
const actionId = text(action.actionId, 'Case-response action id', 64);
|
|
552
556
|
if (actionIds.has(actionId))
|
|
553
557
|
fail('Case-response action identity');
|
|
554
558
|
actionIds.add(actionId);
|
|
555
559
|
actionIdOrder.push(actionId);
|
|
556
|
-
enumeration(action.type,
|
|
560
|
+
enumeration(action.type, hasPlatformRoutes ? CASE_ACTION_TYPES : PRE_PLATFORM_ACTION_TYPES, 'Case-response action type');
|
|
557
561
|
text(action.recipient, 'Case-response action recipient', 320);
|
|
558
562
|
text(action.contactSource, 'Case-response action source', 120);
|
|
559
|
-
if (
|
|
563
|
+
if (hasActionBinding)
|
|
560
564
|
iso(action.routeObservedAt, 'Case-response action route observedAt', true);
|
|
561
|
-
if (
|
|
565
|
+
if (hasPlatformRoutes)
|
|
562
566
|
iso(action.routeReviewAfter, 'Case-response action route reviewAfter', true);
|
|
563
567
|
enumeration(action.state, CASE_ACTION_STATES, 'Case-response action state');
|
|
564
568
|
optionalText(action.reference, 'Case-response action reference', 500);
|
|
@@ -569,6 +573,7 @@ function validateCaseResponsePacketV7ToV9(value, version) {
|
|
|
569
573
|
integer(action.historyOmitted, 'Case-response action omitted history', 0, 1_000_000);
|
|
570
574
|
strings(action.historyLimitations, 'Case-response action history limitations', 8, 240);
|
|
571
575
|
const transitions = array(action.transitions, 'Case-response action transitions', MAX_CASE_ACTION_EVENTS_PER_ACTION, 1);
|
|
576
|
+
const requestEvents = [];
|
|
572
577
|
const eventIds = new Set();
|
|
573
578
|
let projectedState = null;
|
|
574
579
|
let latestReference = null;
|
|
@@ -578,8 +583,11 @@ function validateCaseResponsePacketV7ToV9(value, version) {
|
|
|
578
583
|
let previousTime = Number.NEGATIVE_INFINITY;
|
|
579
584
|
let previousEventId = '';
|
|
580
585
|
for (const eventCandidate of transitions) {
|
|
581
|
-
const event =
|
|
586
|
+
const event = exactOptional(eventCandidate, ['id', 'previousState', 'nextState', 'occurredAt', 'sourceClass', 'provenance', 'reference', 'evidencePinId', 'limitations', 'providerOutcome', 'outcomeDetail', 'originActionId', 'applied'], Number(root.schemaVersion) >= 11 ? ['evidenceRequest'] : [], 'Case-response action transition');
|
|
587
|
+
const evidenceRequest = readCaseEvidenceRequest(event.evidenceRequest);
|
|
588
|
+
assertEvidenceRequestEvent(evidenceRequest, event);
|
|
582
589
|
const eventId = text(event.id, 'Case-response action event id', 64);
|
|
590
|
+
requestEvents.push({ id: eventId, ...(evidenceRequest ? { evidenceRequest } : {}) });
|
|
583
591
|
if (eventIds.has(eventId))
|
|
584
592
|
fail('Case-response action event identity');
|
|
585
593
|
eventIds.add(eventId);
|
|
@@ -648,6 +656,7 @@ function validateCaseResponsePacketV7ToV9(value, version) {
|
|
|
648
656
|
}
|
|
649
657
|
}
|
|
650
658
|
}
|
|
659
|
+
assertEvidenceRequestHistory(requestEvents, Number(action.historyOmitted) > 0);
|
|
651
660
|
if ((projectedState ?? 'drafting') !== action.state
|
|
652
661
|
|| latestReference !== action.reference
|
|
653
662
|
|| latestProviderOutcome !== action.providerOutcome
|
|
@@ -658,13 +667,18 @@ function validateCaseResponsePacketV7ToV9(value, version) {
|
|
|
658
667
|
iso(action.createdAt, 'Case-response action createdAt');
|
|
659
668
|
iso(action.updatedAt, 'Case-response action updatedAt');
|
|
660
669
|
}
|
|
661
|
-
if (
|
|
670
|
+
if (hasActionBinding) {
|
|
662
671
|
if (actionIds.size !== lineageActionIds.length
|
|
663
672
|
|| actionIdOrder.some((id, index) => lineageActionIds[index] !== id))
|
|
664
673
|
fail('Case-response action lineage');
|
|
665
674
|
if (recipientRoute && !actionIds.has(recipientRoute.actionId))
|
|
666
675
|
fail('Case-response recipient action');
|
|
667
676
|
}
|
|
677
|
+
if (version >= 11)
|
|
678
|
+
assertPacketAmendmentSelection(history.map(candidate => {
|
|
679
|
+
const action = candidate;
|
|
680
|
+
return { id: action.actionId, history: action.transitions, originActionId: action.originActionId, amendment: action.amendment };
|
|
681
|
+
}), selectedActionId, evidenceIds);
|
|
668
682
|
const lifecycle = exact(root.responseLifecycle, ['providerOutcomeState', 'latestProviderOutcome', 'observedChangeState', 'latestObservedEffect', 'latestObservedChangeAt', 'closure', 'limitations'], 'Case-response lifecycle');
|
|
669
683
|
const providerOutcomeState = enumeration(lifecycle.providerOutcomeState, ['available', 'missing', 'ambiguous'], 'Case-response lifecycle provider state');
|
|
670
684
|
let actualLatestProvider = null;
|
|
@@ -690,11 +704,11 @@ function validateCaseResponsePacketV7ToV9(value, version) {
|
|
|
690
704
|
const latestProviderEvents = latestProviderTime === null
|
|
691
705
|
? []
|
|
692
706
|
: providerEvents.filter((event) => event.occurredAt === latestProviderTime);
|
|
693
|
-
const expectedLatestProvider = (
|
|
707
|
+
const expectedLatestProvider = (hasActionBinding ? lineageComplete : escalationHistoryOmitted === 0)
|
|
694
708
|
&& latestProviderEvents.length === 1 && latestProviderEvents[0].applied
|
|
695
709
|
? latestProviderEvents[0]
|
|
696
710
|
: null;
|
|
697
|
-
const expectedProviderState = (
|
|
711
|
+
const expectedProviderState = (hasActionBinding ? !lineageComplete : escalationHistoryOmitted > 0)
|
|
698
712
|
? 'ambiguous'
|
|
699
713
|
: providerEvents.length === 0 ? 'missing' : expectedLatestProvider ? 'available' : 'ambiguous';
|
|
700
714
|
if (providerOutcomeState !== expectedProviderState
|
|
@@ -727,9 +741,11 @@ function validateCaseResponsePacketV7ToV9(value, version) {
|
|
|
727
741
|
strings(lifecycle.limitations, 'Case-response lifecycle limitations', 8, 600);
|
|
728
742
|
const reviewedInputs = {
|
|
729
743
|
contract: CASE_RESPONSE_REVIEW_INPUTS_SCHEMA,
|
|
730
|
-
version:
|
|
744
|
+
version: version === CASE_RESPONSE_PACKET_VERSION
|
|
731
745
|
? CASE_RESPONSE_REVIEW_INPUTS_VERSION
|
|
732
|
-
:
|
|
746
|
+
: version === LATEST_PUBLIC_CASE_RESPONSE_PACKET_VERSION ? LATEST_PUBLIC_CASE_RESPONSE_REVIEW_INPUTS_VERSION
|
|
747
|
+
: hasPlatformRoutes ? PUBLISHED_V2_3_CASE_RESPONSE_REVIEW_INPUTS_VERSION
|
|
748
|
+
: hasActionBinding ? PUBLISHED_V2_2_CASE_RESPONSE_REVIEW_INPUTS_VERSION : PUBLISHED_V2_CASE_RESPONSE_REVIEW_INPUTS_VERSION,
|
|
733
749
|
profile: {
|
|
734
750
|
id: profile.id,
|
|
735
751
|
label: profile.label,
|
|
@@ -743,7 +759,7 @@ function validateCaseResponsePacketV7ToV9(value, version) {
|
|
|
743
759
|
case: root.case,
|
|
744
760
|
incident: root.incident,
|
|
745
761
|
contacts: root.contacts,
|
|
746
|
-
...(
|
|
762
|
+
...(hasActionBinding ? { recipientRoute: root.recipientRoute, actionBinding: root.actionBinding } : {}),
|
|
747
763
|
selectedEvidence: root.selectedEvidence,
|
|
748
764
|
contradictions: root.contradictions,
|
|
749
765
|
readiness: root.readiness,
|
|
@@ -778,13 +794,18 @@ function validateCaseResponsePacketV7ToV9(value, version) {
|
|
|
778
794
|
fail('Case-response integrity');
|
|
779
795
|
}
|
|
780
796
|
export function validateCaseResponsePacket(value) {
|
|
797
|
+
if (value.schemaVersion === LATEST_PUBLIC_CASE_RESPONSE_PACKET_VERSION)
|
|
798
|
+
return validateVersionedCaseResponsePacket(value, LATEST_PUBLIC_CASE_RESPONSE_PACKET_VERSION);
|
|
781
799
|
if (value.schemaVersion === PUBLIC_CASE_RESPONSE_PACKET_VERSION)
|
|
782
800
|
return validateCaseResponsePacketV6(value);
|
|
783
801
|
if (value.schemaVersion === PUBLISHED_V2_CASE_RESPONSE_PACKET_VERSION) {
|
|
784
|
-
return
|
|
802
|
+
return validateVersionedCaseResponsePacket(value, PUBLISHED_V2_CASE_RESPONSE_PACKET_VERSION);
|
|
785
803
|
}
|
|
786
|
-
if (value.schemaVersion ===
|
|
787
|
-
return
|
|
804
|
+
if (value.schemaVersion === PUBLISHED_V2_2_CASE_RESPONSE_PACKET_VERSION) {
|
|
805
|
+
return validateVersionedCaseResponsePacket(value, PUBLISHED_V2_2_CASE_RESPONSE_PACKET_VERSION);
|
|
806
|
+
}
|
|
807
|
+
if (value.schemaVersion === PUBLISHED_V2_3_CASE_RESPONSE_PACKET_VERSION) {
|
|
808
|
+
return validateVersionedCaseResponsePacket(value, PUBLISHED_V2_3_CASE_RESPONSE_PACKET_VERSION);
|
|
788
809
|
}
|
|
789
810
|
if (Number.isSafeInteger(value.schemaVersion) && value.schemaVersion < CASE_RESPONSE_PACKET_VERSION) {
|
|
790
811
|
throw new TypeError(`Case-response packet version ${String(value.schemaVersion)} is not part of the public compatibility boundary; no data was changed.`);
|
|
@@ -792,5 +813,5 @@ export function validateCaseResponsePacket(value) {
|
|
|
792
813
|
if (Number.isSafeInteger(value.schemaVersion) && value.schemaVersion > CASE_RESPONSE_PACKET_VERSION) {
|
|
793
814
|
throw new TypeError(`Case-response packet version ${String(value.schemaVersion)} is newer than the supported version ${CASE_RESPONSE_PACKET_VERSION}; no data was changed.`);
|
|
794
815
|
}
|
|
795
|
-
|
|
816
|
+
validateVersionedCaseResponsePacket(value, CASE_RESPONSE_PACKET_VERSION);
|
|
796
817
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { investigationCapsuleContracts, MAX_LOOKUP_ASSET_NODES, MAX_LOOKUP_ASSET_EDGES, MAX_LOOKUP_ASSET_LIMITATIONS, MAX_LOOKUP_ASSET_INPUT_ROWS } from "../../packages/contracts/investigation-portability.mjs";
|
|
2
|
+
import { INVESTIGATION_CAPSULE_ANALYST_RECORDS_SCHEMA, INVESTIGATION_CAPSULE_ANALYST_RECORDS_VERSION, investigationCapsuleProjectionsMatch, } from "../../packages/investigation/investigation-capsule.mjs";
|
|
2
3
|
import { LOOKUP_ASSET_GRAPH_SCHEMA, LOOKUP_ASSET_GRAPH_VERSION, } from "../../packages/investigation/lookup-asset-graph.mjs";
|
|
3
|
-
import { PUBLIC_LOOKUP_INVESTIGATION_BRIEF_VERSION, LOOKUP_INVESTIGATION_BRIEF_SCHEMA,
|
|
4
|
+
import { PUBLIC_LOOKUP_INVESTIGATION_BRIEF_VERSION, LOOKUP_INVESTIGATION_BRIEF_SCHEMA, MAX_LOOKUP_INVESTIGATION_BRIEF_BYTES, } from "../../packages/investigation/lookup-investigation-brief.mjs";
|
|
4
5
|
import { DECISION_FACT_CONSISTENCY_STATES, DECISION_FACT_EVIDENCE_STATES, DECISION_FACT_FRESHNESS_STATES, DECISION_FACT_PROVENANCE_STATES, DECISION_FACT_PROJECTION_VERSION, DECISION_FACT_VERSION, MAX_DECISION_FACT_CONTRIBUTORS, MAX_DECISION_FACT_CONTRADICTIONS, MAX_DECISION_FACT_LIMITATIONS, MAX_DECISION_FACT_NEXT_ACTIONS, MAX_DECISION_FACT_PROJECTION_CONTRADICTIONS, MAX_DECISION_FACT_PROJECTION_BYTES, MAX_DECISION_FACT_PROJECTION_FACTS, MAX_DECISION_FACT_PROJECTION_LIMITATIONS, MAX_DECISION_FACT_PROJECTION_NEXT_ACTIONS, MAX_DECISION_FACT_PROJECTION_REFERENCES, MAX_DECISION_FACT_PROJECTION_SOURCES, MAX_DECISION_FACT_PROJECTION_SOURCE_LIMITATIONS, MAX_DECISION_FACT_PROJECTION_SOURCE_REFERENCES, MAX_DECISION_FACT_REFERENCES, MAX_DECISION_FACTS, decisionFactCompleteness, } from "../../packages/evidence/decision-fact.mjs";
|
|
5
6
|
import { MAX_CASE_ASSERTIONS, MAX_CASE_DECISIONS, MAX_DECISION_PIN_REFERENCES, } from "../../packages/cases/case-response-model.mjs";
|
|
6
7
|
import { array, boolean, digest, enumeration, exact, exactOptional, fail, integer, iso, optionalText, sameValues, strings, text, } from "./structure-primitives.mjs";
|
|
@@ -221,10 +222,10 @@ function validateDecisionFactProjection(value) {
|
|
|
221
222
|
fail('Investigation capsule Decision Fact projection byte limit');
|
|
222
223
|
}
|
|
223
224
|
}
|
|
224
|
-
function validateCurrentBrief(value) {
|
|
225
|
+
function validateCurrentBrief(value, expectedVersion) {
|
|
225
226
|
const brief = exact(value, ['schema', 'schemaVersion', 'generatedAt', 'target', 'targetType', 'task', 'taskLabel', 'question', 'summary', 'observation', 'decisionFacts', 'relationships', 'limitations'], 'Investigation capsule brief');
|
|
226
227
|
if (brief.schema !== LOOKUP_INVESTIGATION_BRIEF_SCHEMA
|
|
227
|
-
|| brief.schemaVersion !==
|
|
228
|
+
|| brief.schemaVersion !== expectedVersion)
|
|
228
229
|
fail('Investigation capsule brief');
|
|
229
230
|
iso(brief.generatedAt, 'Investigation capsule brief generatedAt');
|
|
230
231
|
text(brief.target, 'Investigation capsule brief target', 253);
|
|
@@ -250,21 +251,22 @@ function validateCurrentBrief(value) {
|
|
|
250
251
|
integer(thresholds[key], 'Investigation capsule freshness threshold', 0, 3650);
|
|
251
252
|
validateDecisionFactProjection(brief.decisionFacts);
|
|
252
253
|
const relationships = exact(brief.relationships, ['nodes', 'edges', 'truncated', 'kinds'], 'Investigation capsule relationship summary');
|
|
253
|
-
integer(relationships.nodes, 'Investigation capsule relationship node count', 0, 72);
|
|
254
|
-
integer(relationships.edges, 'Investigation capsule relationship edge count', 0, 120);
|
|
254
|
+
integer(relationships.nodes, 'Investigation capsule relationship node count', 0, expectedVersion === 2 ? 72 : MAX_LOOKUP_ASSET_NODES);
|
|
255
|
+
integer(relationships.edges, 'Investigation capsule relationship edge count', 0, expectedVersion === 2 ? 120 : MAX_LOOKUP_ASSET_EDGES);
|
|
255
256
|
boolean(relationships.truncated, 'Investigation capsule relationship truncation');
|
|
256
|
-
strings(relationships.kinds, 'Investigation capsule relationship kinds', 12, 320);
|
|
257
|
+
strings(relationships.kinds, 'Investigation capsule relationship kinds', expectedVersion === 2 ? 12 : 64, 320);
|
|
257
258
|
strings(brief.limitations, 'Investigation capsule brief limitations', 20, 320);
|
|
258
259
|
if (new TextEncoder().encode(JSON.stringify(brief)).byteLength > MAX_LOOKUP_INVESTIGATION_BRIEF_BYTES) {
|
|
259
260
|
fail('Investigation capsule brief byte limit');
|
|
260
261
|
}
|
|
261
262
|
}
|
|
262
|
-
function validateGraph(value) {
|
|
263
|
-
const
|
|
264
|
-
|
|
263
|
+
function validateGraph(value, expectedVersion) {
|
|
264
|
+
const current = expectedVersion === LOOKUP_ASSET_GRAPH_VERSION;
|
|
265
|
+
const graph = exact(value, ['version', 'targetId', 'nodes', 'edges', 'sources', 'truncated', 'limitations', ...(current ? ['coverage'] : [])], 'Investigation capsule graph');
|
|
266
|
+
if (graph.version !== expectedVersion)
|
|
265
267
|
fail('Investigation capsule graph');
|
|
266
268
|
text(graph.targetId, 'Investigation capsule graph target', 160);
|
|
267
|
-
const nodes = array(graph.nodes, 'Investigation capsule graph nodes', 72, 1);
|
|
269
|
+
const nodes = array(graph.nodes, 'Investigation capsule graph nodes', current ? MAX_LOOKUP_ASSET_NODES : 72, 1);
|
|
268
270
|
const nodeIds = new Set();
|
|
269
271
|
for (const candidate of nodes) {
|
|
270
272
|
const node = exact(candidate, ['id', 'label', 'kind', 'detail'], 'Investigation capsule graph node');
|
|
@@ -279,7 +281,7 @@ function validateGraph(value) {
|
|
|
279
281
|
if (!nodeIds.has(graph.targetId))
|
|
280
282
|
fail('Investigation capsule graph target');
|
|
281
283
|
const sourceIds = new Set();
|
|
282
|
-
for (const candidate of array(graph.sources, 'Investigation capsule graph sources', 32)) {
|
|
284
|
+
for (const candidate of array(graph.sources, 'Investigation capsule graph sources', current ? 128 : 32)) {
|
|
283
285
|
const source = exact(candidate, ['id', 'label', 'href', 'observedAt', 'completeness', 'limitations'], 'Investigation capsule graph source');
|
|
284
286
|
const id = text(source.id, 'Investigation capsule graph source id', 160);
|
|
285
287
|
if (sourceIds.has(id))
|
|
@@ -290,9 +292,9 @@ function validateGraph(value) {
|
|
|
290
292
|
fail('Investigation capsule graph source href');
|
|
291
293
|
iso(source.observedAt, 'Investigation capsule graph source observedAt', true);
|
|
292
294
|
enumeration(source.completeness, ['complete', 'partial', 'unknown'], 'Investigation capsule graph source completeness');
|
|
293
|
-
strings(source.limitations, 'Investigation capsule graph source limitations', 5, 320);
|
|
295
|
+
strings(source.limitations, 'Investigation capsule graph source limitations', current ? MAX_LOOKUP_ASSET_LIMITATIONS : 5, 320);
|
|
294
296
|
}
|
|
295
|
-
const edges = array(graph.edges, 'Investigation capsule graph edges', 120);
|
|
297
|
+
const edges = array(graph.edges, 'Investigation capsule graph edges', current ? MAX_LOOKUP_ASSET_EDGES : 120);
|
|
296
298
|
const edgeIds = new Set();
|
|
297
299
|
for (const candidate of edges) {
|
|
298
300
|
const edge = exactOptional(candidate, ['id', 'sourceId', 'source', 'target', 'kind', 'label', 'sourceLabel', 'observedAt', 'completeness', 'limitations', 'lenses', 'href'], ['boundary'], 'Investigation capsule graph edge');
|
|
@@ -310,13 +312,36 @@ function validateGraph(value) {
|
|
|
310
312
|
text(edge.sourceLabel, 'Investigation capsule graph edge source label', 320);
|
|
311
313
|
iso(edge.observedAt, 'Investigation capsule graph edge observedAt', true);
|
|
312
314
|
enumeration(edge.completeness, ['complete', 'partial', 'unknown'], 'Investigation capsule graph edge completeness');
|
|
313
|
-
strings(edge.limitations, 'Investigation capsule graph edge limitations', 5, 320);
|
|
315
|
+
strings(edge.limitations, 'Investigation capsule graph edge limitations', current ? MAX_LOOKUP_ASSET_LIMITATIONS : 5, 320);
|
|
314
316
|
strings(edge.lenses, 'Investigation capsule graph edge lenses', 4, 40).forEach((lens) => enumeration(lens, ['all', 'identity', 'delegation', 'certificate'], 'Investigation capsule graph edge lens'));
|
|
315
317
|
if (typeof edge.href !== 'string' || !/^#[^\u0000-\u001f\u007f]{1,160}$/u.test(edge.href))
|
|
316
318
|
fail('Investigation capsule graph edge href');
|
|
317
319
|
if (edge.boundary !== undefined)
|
|
318
320
|
enumeration(edge.boundary, ['external', 'reviewed_profile', 'same_origin', 'same_registrable_domain', 'unresolved'], 'Investigation capsule graph edge boundary');
|
|
319
321
|
}
|
|
322
|
+
if (current) {
|
|
323
|
+
const coverage = exact(graph.coverage, ['inputs'], 'Investigation capsule graph coverage');
|
|
324
|
+
const ids = new Set();
|
|
325
|
+
let incomplete = false;
|
|
326
|
+
for (const candidate of array(coverage.inputs, 'Investigation capsule graph input coverage', MAX_LOOKUP_ASSET_INPUT_ROWS)) {
|
|
327
|
+
const row = exact(candidate, ['id', 'supplied', 'inspected', 'admitted', 'invalid', 'duplicates', 'omitted'], 'Investigation capsule graph input coverage row');
|
|
328
|
+
const id = text(row.id, 'Investigation capsule graph input id', 160);
|
|
329
|
+
if (ids.has(id))
|
|
330
|
+
fail('Investigation capsule graph input ids');
|
|
331
|
+
ids.add(id);
|
|
332
|
+
const supplied = integer(row.supplied, 'Investigation capsule graph supplied input', 0, 0xffff_ffff);
|
|
333
|
+
const inspected = integer(row.inspected, 'Investigation capsule graph inspected input', 0, 800);
|
|
334
|
+
const admitted = integer(row.admitted, 'Investigation capsule graph admitted input', 0, inspected);
|
|
335
|
+
const invalid = integer(row.invalid, 'Investigation capsule graph invalid input', 0, inspected);
|
|
336
|
+
const duplicates = integer(row.duplicates, 'Investigation capsule graph duplicate input', 0, inspected);
|
|
337
|
+
const omitted = integer(row.omitted, 'Investigation capsule graph omitted input', 0, inspected);
|
|
338
|
+
if (inspected > supplied || admitted + invalid + duplicates + omitted !== inspected)
|
|
339
|
+
fail('Investigation capsule graph input accounting');
|
|
340
|
+
incomplete ||= supplied > inspected || invalid > 0 || omitted > 0;
|
|
341
|
+
}
|
|
342
|
+
if (graph.truncated !== incomplete)
|
|
343
|
+
fail('Investigation capsule graph coverage linkage');
|
|
344
|
+
}
|
|
320
345
|
boolean(graph.truncated, 'Investigation capsule graph truncation');
|
|
321
346
|
strings(graph.limitations, 'Investigation capsule graph limitations', 20, 320);
|
|
322
347
|
}
|
|
@@ -349,8 +374,8 @@ function validateAnalystRecords(value) {
|
|
|
349
374
|
}
|
|
350
375
|
export function validateInvestigationCapsuleStructure(value) {
|
|
351
376
|
const root = exact(value, ['schema', 'schemaVersion', 'generatedAt', 'application', 'target', 'sourceContracts', 'investigationBrief', 'graphSnapshot', 'analystRecords', 'integrity', 'limitations'], 'Investigation capsule');
|
|
352
|
-
|
|
353
|
-
|
|
377
|
+
const expected = investigationCapsuleContracts(root.schemaVersion);
|
|
378
|
+
if (!expected)
|
|
354
379
|
fail('Investigation capsule');
|
|
355
380
|
iso(root.generatedAt, 'Investigation capsule generatedAt');
|
|
356
381
|
const application = exact(root.application, ['name', 'version'], 'Investigation capsule application');
|
|
@@ -376,9 +401,7 @@ export function validateInvestigationCapsuleStructure(value) {
|
|
|
376
401
|
const expectedContractIds = root.analystRecords === null
|
|
377
402
|
? ['lookup-evidence', 'investigation-brief', 'asset-graph']
|
|
378
403
|
: ['lookup-evidence', 'investigation-brief', 'asset-graph', 'analyst-records'];
|
|
379
|
-
const expectedBriefVersion =
|
|
380
|
-
? LOOKUP_INVESTIGATION_BRIEF_VERSION
|
|
381
|
-
: PUBLIC_LOOKUP_INVESTIGATION_BRIEF_VERSION;
|
|
404
|
+
const expectedBriefVersion = expected.brief;
|
|
382
405
|
if (!sameValues(contracts.map((candidate) => candidate.id), expectedContractIds))
|
|
383
406
|
fail('Investigation capsule source contracts');
|
|
384
407
|
if (!byId.has('lookup-evidence') || !byId.has('investigation-brief') || !byId.has('asset-graph')
|
|
@@ -387,25 +410,23 @@ export function validateInvestigationCapsuleStructure(value) {
|
|
|
387
410
|
|| byId.get('investigation-brief')?.version !== expectedBriefVersion
|
|
388
411
|
|| byId.get('investigation-brief')?.embedded !== true
|
|
389
412
|
|| byId.get('asset-graph')?.schema !== LOOKUP_ASSET_GRAPH_SCHEMA
|
|
390
|
-
|| byId.get('asset-graph')?.version !==
|
|
413
|
+
|| byId.get('asset-graph')?.version !== expected.graph
|
|
391
414
|
|| byId.get('asset-graph')?.embedded !== true
|
|
392
415
|
|| (byId.has('analyst-records') && (byId.get('analyst-records')?.schema !== INVESTIGATION_CAPSULE_ANALYST_RECORDS_SCHEMA
|
|
393
416
|
|| byId.get('analyst-records')?.version !== INVESTIGATION_CAPSULE_ANALYST_RECORDS_VERSION
|
|
394
417
|
|| byId.get('analyst-records')?.embedded !== true)))
|
|
395
418
|
fail('Investigation capsule source contracts');
|
|
396
|
-
if (
|
|
397
|
-
validateCurrentBrief(root.investigationBrief);
|
|
398
|
-
else
|
|
419
|
+
if (expectedBriefVersion === 1)
|
|
399
420
|
validatePublicBrief(root.investigationBrief);
|
|
400
|
-
|
|
421
|
+
else
|
|
422
|
+
validateCurrentBrief(root.investigationBrief, expectedBriefVersion);
|
|
423
|
+
validateGraph(root.graphSnapshot, expected.graph);
|
|
401
424
|
validateAnalystRecords(root.analystRecords);
|
|
402
|
-
|
|
403
|
-
const
|
|
404
|
-
if (target
|
|
405
|
-
|| graph.targetId === undefined
|
|
406
|
-
|| brief.relationships.nodes !== graph.nodes.length
|
|
407
|
-
|| brief.relationships.edges !== graph.edges.length)
|
|
425
|
+
// The version-specific validators above independently establish these common fields.
|
|
426
|
+
const projections = root;
|
|
427
|
+
if (!investigationCapsuleProjectionsMatch(projections.target, projections.investigationBrief, projections.graphSnapshot)) {
|
|
408
428
|
fail('Investigation capsule projection linkage');
|
|
429
|
+
}
|
|
409
430
|
const integrity = exact(root.integrity, ['algorithm', 'canonicalization', 'scope', 'briefDigest', 'graphDigest', 'analystRecordsDigest', 'digestSha256'], 'Investigation capsule integrity');
|
|
410
431
|
if (integrity.algorithm !== 'SHA-256')
|
|
411
432
|
fail('Investigation capsule integrity');
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { isDeepStrictEqual } from 'node:util';
|
|
2
|
-
import { assertLookupEvidencePrivacySafeTree, assertLookupEvidencePortableTree, LOOKUP_EVIDENCE_SCHEMA, LOOKUP_EVIDENCE_SCHEMA_VERSION, PRIVACY_MINIMIZED_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";
|
|
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
|
+
import { validLookupObservationScope } from "../../packages/evidence/lookup-target.mjs";
|
|
3
4
|
import { registrarStandingObservedBy, resolveRegistrarIanaId, validRegistrarStanding, } from "../../lib/registrar-standing-contract.mjs";
|
|
4
5
|
import { compareRegistrySources } from "../../lib/registry-comparison.mjs";
|
|
5
6
|
import { buildRegistryInsights } from "../../lib/registry-insights.mjs";
|
|
6
|
-
import { WHOISLEUTH_SOURCE_REPOSITORY_URL } from "../../
|
|
7
|
+
import { WHOISLEUTH_SOURCE_REPOSITORY_URL } from "../../packages/analysis/project-metadata.mjs";
|
|
7
8
|
import { validHttpDeliveryMetadata, validPagePublicationMetadata, } from "../../lib/homepage-metadata-contract.mjs";
|
|
8
9
|
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";
|
|
9
10
|
const LOOKUP_RDAP_ATTEMPT_OUTCOMES = [
|
|
@@ -649,27 +650,32 @@ export function validateLookupEvidenceArtifactStructure(value) {
|
|
|
649
650
|
}
|
|
650
651
|
validateLookupEvidenceSecurityTxt(sources.securityTxt);
|
|
651
652
|
validateLookupEvidenceSslbl(sources.sslbl);
|
|
652
|
-
const analysis = exact(root.analysis, version >=
|
|
653
|
+
const analysis = exact(root.analysis, version >= REGISTRAR_STANDING_LOOKUP_EVIDENCE_SCHEMA_VERSION
|
|
653
654
|
? ['availability', 'idn', 'registryInsights', 'registryComparison', 'registrarPublicationComparison', 'registrarStanding']
|
|
654
655
|
: ['availability', 'idn', 'registryInsights', 'registryComparison', 'registrarPublicationComparison'], 'Lookup evidence analysis');
|
|
655
|
-
if (version >=
|
|
656
|
+
if (version >= REGISTRAR_STANDING_LOOKUP_EVIDENCE_SCHEMA_VERSION
|
|
656
657
|
&& analysis.registrarStanding !== null
|
|
657
658
|
&& !validRegistrarStanding(analysis.registrarStanding))
|
|
658
659
|
fail('Lookup evidence registrar standing');
|
|
659
|
-
if (version >=
|
|
660
|
+
if (version >= REGISTRAR_STANDING_LOOKUP_EVIDENCE_SCHEMA_VERSION
|
|
660
661
|
&& analysis.registrarStanding !== null
|
|
661
662
|
&& !registrarStandingObservedBy(analysis.registrarStanding, root.generatedAt)) {
|
|
662
663
|
fail('Lookup evidence registrar standing observation time');
|
|
663
664
|
}
|
|
664
665
|
if (analysis.availability !== null) {
|
|
665
666
|
const currentAvailability = version >= PRIVACY_MINIMIZED_LOOKUP_EVIDENCE_SCHEMA_VERSION;
|
|
666
|
-
const availability = exactOptional(analysis.availability, currentAvailability ? ['registryContactsExcluded'] : [], currentAvailability
|
|
667
|
+
const availability = exactOptional(analysis.availability, currentAvailability ? ['registryContactsExcluded'] : [], currentAvailability
|
|
668
|
+
? [...LOOKUP_AVAILABILITY_ANALYSIS_KEYS, ...(version >= HOSTNAME_SCOPED_LOOKUP_EVIDENCE_SCHEMA_VERSION ? ['observationHostname', 'webObservationMode'] : [])]
|
|
669
|
+
: PUBLIC_LOOKUP_AVAILABILITY_ANALYSIS_KEYS, 'Lookup evidence availability analysis');
|
|
667
670
|
if (currentAvailability && availability.registryContactsExcluded !== true) {
|
|
668
671
|
fail('Lookup evidence availability contact exclusion');
|
|
669
672
|
}
|
|
673
|
+
if (!validLookupObservationScope(availability, query)) {
|
|
674
|
+
fail('Lookup evidence observation hostname binding');
|
|
675
|
+
}
|
|
670
676
|
validateLookupEvidenceHomepageMetadata(availability, version);
|
|
671
677
|
if (!isDeepStrictEqual(analysis.availability, currentAvailability
|
|
672
|
-
? projectLookupEvidenceAvailability(analysis.availability)
|
|
678
|
+
? projectLookupEvidenceAvailability(analysis.availability, { legacyUris: version < HOSTNAME_SCOPED_LOOKUP_EVIDENCE_SCHEMA_VERSION })
|
|
673
679
|
: projectLookupEvidenceAvailabilityPublic(analysis.availability)))
|
|
674
680
|
fail('Lookup evidence availability analysis');
|
|
675
681
|
}
|
|
@@ -684,7 +690,7 @@ export function validateLookupEvidenceArtifactStructure(value) {
|
|
|
684
690
|
const whois = record(sources.whois, 'Lookup evidence WHOIS source');
|
|
685
691
|
const rdapParsed = ['success', 'partial'].includes(rdapDiagnosticStatus) ? rdap.parsed : null;
|
|
686
692
|
const whoisParsed = ['complete', 'partial'].includes(whoisDiagnosticStatus) ? whois.parsed : null;
|
|
687
|
-
if (version >=
|
|
693
|
+
if (version >= REGISTRAR_STANDING_LOOKUP_EVIDENCE_SCHEMA_VERSION
|
|
688
694
|
&& validRegistrarStanding(analysis.registrarStanding)
|
|
689
695
|
&& analysis.registrarStanding.ianaId !== resolveRegistrarIanaId(rdapParsed, whoisParsed)) {
|
|
690
696
|
fail('Lookup evidence registrar standing source binding');
|
|
@@ -709,7 +715,7 @@ export function validateLookupEvidenceArtifactStructure(value) {
|
|
|
709
715
|
if (!isDeepStrictEqual(registryInsights, expectedInsights))
|
|
710
716
|
fail('Lookup evidence registry insight derivation');
|
|
711
717
|
try {
|
|
712
|
-
assertLookupEvidencePrivacySafeTree(root);
|
|
718
|
+
assertLookupEvidencePrivacySafeTree(root, { legacyUris: version < HOSTNAME_SCOPED_LOOKUP_EVIDENCE_SCHEMA_VERSION });
|
|
713
719
|
}
|
|
714
720
|
catch {
|
|
715
721
|
fail('Lookup evidence privacy boundary');
|