@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.
- package/README.md +74 -177
- package/SECURITY.md +6 -3
- package/bin/whoisleuth.mjs +2 -1
- package/cli/archive-inspect.mjs +28 -10
- package/cli/arguments.mjs +204 -174
- package/cli/artifact-structure.mjs +837 -163
- package/cli/artifact-verify.mjs +90 -61
- package/cli/bounded-json.mjs +1 -1
- package/cli/bulk-checkpoint.mjs +33 -20
- package/cli/bulk.mjs +9 -2
- package/cli/case-pack.mjs +180 -576
- package/cli/ci-report.mjs +6 -1
- package/cli/collection-preflight.mjs +2 -2
- package/cli/command-catalogue.mjs +12 -3
- package/cli/command-reference.mjs +910 -92
- package/cli/completion.mjs +504 -226
- package/cli/config-profile.mjs +13 -3
- package/cli/discover.mjs +7 -1
- package/cli/discovery-observation-snapshot.mjs +24 -28
- package/cli/discovery-scan-command-runner.mjs +9 -9
- package/cli/discovery-scan.mjs +17 -3
- package/cli/discovery-snapshot.mjs +15 -7
- package/cli/domain-control-monitor.mjs +203 -30
- package/cli/domain-control-observations.mjs +15 -21
- package/cli/errors.mjs +8 -3
- package/cli/evidence-signing.mjs +10 -20
- package/cli/external-observation-mapping.mjs +3 -3
- package/cli/formatters/json.mjs +20 -32
- package/cli/formatters/terminal.mjs +583 -22
- package/cli/input.mjs +7 -2
- package/cli/interactive-launcher.mjs +226 -0
- package/cli/interchange-report.mjs +19 -19
- package/cli/investigation-manifest.mjs +1 -1
- package/cli/investigation-plan.mjs +167 -16
- package/cli/investigation-run.mjs +9 -1
- package/cli/lookalike-calibration.mjs +3 -1
- package/cli/lookup-brief.mjs +13 -8
- package/cli/lookup-browser.mjs +844 -0
- package/cli/lookup-command-runner.mjs +94 -31
- package/cli/lookup-diff.mjs +2 -2
- package/cli/lookup-plan.mjs +1 -0
- package/cli/mail-review.mjs +206 -94
- package/cli/manual.mjs +5 -5
- package/cli/offline-evidence-review.mjs +18 -7
- package/cli/open-asset-model-bridge.mjs +1 -1
- package/cli/output-file.mjs +70 -14
- package/cli/page-compare.mjs +11 -6
- package/cli/progress-events.mjs +2 -1
- package/cli/progress.mjs +3 -3
- package/cli/registry-cohort.mjs +9 -51
- package/cli/registry-doctor.mjs +67 -28
- package/cli/retained-artifact-diff.mjs +7 -8
- package/cli/risk-calibration.mjs +85 -38
- package/cli/runner.mjs +189 -75
- package/cli/saved-lookup.mjs +424 -57
- package/cli/sharing-review.mjs +6 -1
- package/cli/source-reliability.mjs +75 -36
- package/cli/terminal-presentation.mjs +43 -18
- package/docs/cli-reference.md +125 -1708
- package/docs/cli.md +86 -254
- package/frontend/src/lib/analysis/domain-control-manifest-core.js +3 -145
- package/frontend/src/lib/analysis/domain-control-records.js +3 -88
- package/lib/availability.mjs +58 -40
- package/lib/bounded-contract-normalizers.mjs +2 -4
- package/lib/bounded-file.mjs +78 -4
- package/lib/bounded-json.mjs +51 -13
- package/lib/browser-library-profile.mjs +1 -1
- package/lib/classify.mjs +56 -5
- package/lib/client-behavior-profile.mjs +1 -1
- package/lib/credential-surface-profile.mjs +1 -1
- package/lib/cryptographic-assurance.mjs +35 -17
- package/lib/ct-query.mjs +3 -3
- package/lib/ct-search.mjs +37 -21
- package/lib/distributed-operation-budget.mjs +4 -2
- package/lib/dns-delegation-health.mjs +9 -6
- package/lib/dns-intelligence.mjs +1 -1
- package/lib/dnssec-chain-validation.mjs +2 -3
- package/lib/dnssec-evidence-validation.mjs +13 -7
- package/lib/domain-assurance.mjs +12 -12
- package/lib/domain-change-packet.mjs +1 -1
- package/lib/domain-control-flight-recorder.mjs +442 -78
- package/lib/domain-control-manifest.mjs +314 -59
- package/lib/domain-posture-analysis.mjs +5 -2
- package/lib/encrypted-dns-contract.mjs +2 -3
- package/lib/evidence-export.mjs +518 -36
- package/lib/external-intelligence-risk.mjs +26 -10
- package/lib/feature-policy.mjs +1 -1
- package/lib/homepage-metadata-contract.mjs +290 -0
- package/lib/hostname.mjs +1 -1
- package/lib/html-signals.mjs +89 -5
- package/lib/http-intelligence.mjs +331 -2
- package/lib/interchange-fidelity-registry.mjs +28 -26
- package/lib/lookup-cache.mjs +6 -4
- package/lib/lookup.mjs +25 -2
- package/lib/observed-network-context.mjs +1 -1
- package/lib/page-fingerprints.mjs +2 -2
- package/lib/page-role-profile.mjs +1 -1
- package/lib/portable-generator.mjs +8 -0
- package/lib/project-metadata.mjs +1 -1
- package/lib/provider-policy-admission.mjs +3 -1
- package/lib/rdap-bootstrap.mjs +76 -16
- package/lib/rdap-client.mjs +20 -12
- package/lib/rdap-normalization.mjs +306 -75
- package/lib/rdap-transport.mjs +2 -2
- package/lib/rdap-validation.mjs +65 -14
- package/lib/rdap.mjs +2 -2
- package/lib/registrable-domain.mjs +36 -0
- package/lib/registry-insights.mjs +7 -0
- package/lib/response-policy.mjs +27 -4
- package/lib/risk-calibration-summary.mjs +35 -33
- package/lib/risk-scoring.mjs +4 -3
- package/lib/safe-fetch.mjs +62 -40
- package/lib/security-txt.mjs +1 -1
- package/lib/smtp-transport-review.mjs +8 -5
- package/lib/sslbl-intelligence.mjs +6 -8
- package/lib/static-html-analysis.mjs +301 -4
- package/lib/structured-data-identity.mjs +51 -19
- package/lib/threat-intelligence-fixtures.mjs +2 -4
- package/lib/threat-intelligence-manifests.mjs +2 -7
- package/lib/threat-intelligence-runtime.mjs +3 -5
- package/lib/threat-intelligence-types.mjs +3 -0
- package/lib/threatfox-intelligence.mjs +7 -3
- package/lib/tls-intelligence.mjs +4 -4
- package/lib/urlhaus-intelligence.mjs +4 -2
- package/lib/urlscan-intelligence.mjs +6 -5
- package/lib/website-security-posture.mjs +1 -1
- package/lib/website-technology.mjs +1 -1
- package/lib/whois-authority.mjs +43 -8
- package/lib/whois-chain.mjs +25 -3
- package/lib/whois-normalization.mjs +3 -1
- package/lib/whois-transport.mjs +12 -3
- package/lib/zone-intent-review.mjs +1 -1
- package/package.json +13 -5
- package/{frontend/src/lib/analysis/case-brand-profile-references.js → packages/cases/case-brand-profile-references.mjs} +2 -2
- package/{frontend/src/lib/analysis/case-evidence-model.js → packages/cases/case-evidence-model.mjs} +35 -14
- package/{frontend/src/lib/analysis/case-investigation-branch-model.js → packages/cases/case-investigation-branch-model.mjs} +10 -12
- package/packages/cases/case-migration-model.mjs +476 -0
- package/{frontend/src/lib/analysis/case-model.js → packages/cases/case-model.mjs} +3 -3
- package/{frontend/src/lib/analysis/case-record-contracts.js → packages/cases/case-record-contracts.mjs} +3 -21
- package/{frontend/src/lib/analysis/case-record-core.js → packages/cases/case-record-core.mjs} +58 -31
- package/packages/cases/case-record-model.mjs +9 -0
- package/{frontend/src/lib/analysis/case-record-operations.js → packages/cases/case-record-operations.mjs} +72 -29
- package/{frontend/src/lib/analysis/case-report.js → packages/cases/case-report.mjs} +108 -14
- package/packages/cases/case-response-model.mjs +1604 -0
- package/packages/cases/case-response-packet.mjs +1312 -0
- package/{frontend/src/lib/analysis/case-storage-model.js → packages/cases/case-storage-model.mjs} +2 -2
- package/{frontend/src/lib/analysis/http-summary.js → packages/cases/http-summary.mjs} +1 -1
- package/{frontend/src/lib/analysis/comparison-ledger-bulk.js → packages/comparison/comparison-ledger-bulk.mjs} +2 -2
- package/{frontend/src/lib/analysis/comparison-ledger-contract.js → packages/comparison/comparison-ledger-contract.mjs} +4 -4
- package/{frontend/src/lib/analysis/comparison-ledger-json.js → packages/comparison/comparison-ledger-json.mjs} +1 -0
- package/{frontend/src/lib/analysis/comparison-ledger-serialization.js → packages/comparison/comparison-ledger-serialization.mjs} +1 -0
- package/packages/comparison/favicon-similarity.mjs +78 -0
- package/{frontend/src/lib/analysis/page-similarity.js → packages/comparison/page-similarity.mjs} +4 -3
- package/{frontend/src/lib/analysis/relationship-evidence.js → packages/comparison/relationship-evidence.mjs} +6 -7
- package/packages/contracts/analyst-review-state-contract.mjs +61 -0
- package/packages/contracts/analyst-review-state.mjs +50 -0
- package/packages/contracts/case-portability.mjs +1310 -0
- package/packages/contracts/cli-command-catalogue.mjs +2 -0
- package/packages/contracts/cli-lookup.mjs +394 -0
- package/packages/contracts/domain-control-flight-recorder.mjs +461 -0
- package/packages/contracts/domain-control-manifest.mjs +1027 -0
- package/packages/contracts/domain-control-monitor.mjs +310 -0
- package/packages/contracts/domain-control-review.mjs +828 -0
- package/packages/contracts/domain-name.mjs +4 -0
- package/packages/contracts/external-observation-interchange.mjs +224 -0
- package/packages/contracts/extracted-domain-lifecycle.mjs +218 -0
- package/packages/contracts/investigation-portability.mjs +331 -0
- package/packages/contracts/offline-comparison.mjs +160 -0
- package/packages/contracts/risk-calibration.mjs +1288 -0
- package/packages/contracts/schema-compatibility.mjs +71 -0
- package/packages/contracts/schema-lifecycle.mjs +1702 -0
- package/packages/contracts/sslbl-snapshot.mjs +17 -0
- package/packages/contracts/workspace-portability.mjs +1347 -0
- package/{frontend/src/lib/analysis/artifact-integrity.js → packages/evidence/artifact-integrity.mjs} +13 -11
- package/packages/evidence/decision-fact.mjs +735 -0
- package/packages/evidence/domain-control-runtime.mjs +547 -0
- package/packages/evidence/domain-name.mjs +41 -0
- package/packages/evidence/observation.mjs +384 -0
- package/{frontend/src/lib/analysis/external-findings-import.js → packages/interchange/external-findings-import.mjs} +24 -22
- package/packages/interchange/retained-text.mjs +8 -0
- package/{frontend/src/lib/analysis/acquisition-decision-packet.js → packages/investigation/acquisition-decision-packet.mjs} +9 -6
- package/{frontend/src/lib/analysis/bulk-domain-comparison.js → packages/investigation/bulk-domain-comparison.mjs} +5 -6
- package/{frontend/src/lib/analysis/bulk-mail-exposure.js → packages/investigation/bulk-mail-exposure.mjs} +4 -6
- package/{frontend/src/lib/analysis/bulk-review-export.js → packages/investigation/bulk-review-export.mjs} +5 -5
- package/packages/investigation/bulk-review-policy.mjs +1 -0
- package/{frontend/src/lib/analysis/investigation-capsule.js → packages/investigation/investigation-capsule.mjs} +30 -32
- package/packages/investigation/lookup-artefact-inputs.mjs +1 -0
- package/{frontend/src/lib/analysis/lookup-asset-graph.js → packages/investigation/lookup-asset-graph.mjs} +4 -2
- package/{frontend/src/lib/analysis/lookup-claim-passport.js → packages/investigation/lookup-claim-passport.mjs} +6 -10
- package/{frontend/src/lib/analysis/lookup-claim-readiness.js → packages/investigation/lookup-claim-readiness.mjs} +9 -2
- package/packages/investigation/lookup-investigation-brief.mjs +172 -0
- package/packages/monitoring/analyst-review-state.mjs +680 -0
- package/{frontend/src/lib/analysis/brand-profile-model.js → packages/workspace/brand-profile-model.mjs} +71 -48
- package/{frontend/src/lib/analysis/bulk-review-model.js → packages/workspace/bulk-review-model.mjs} +13 -14
- package/{frontend/src/lib/analysis/bulk-session-model.js → packages/workspace/bulk-session-model.mjs} +33 -59
- package/{frontend/src/lib/analysis/bulk-sort.js → packages/workspace/bulk-sort.mjs} +8 -5
- package/{frontend/src/lib/analysis/campaign-model.js → packages/workspace/campaign-model.mjs} +20 -22
- package/{frontend/src/lib/analysis/detection-rule-model.js → packages/workspace/detection-rule-model.mjs} +15 -17
- package/packages/workspace/hostile-input.mjs +172 -0
- package/{frontend/src/lib/analysis/investigation-guide.js → packages/workspace/investigation-guide.mjs} +8 -45
- package/{frontend/src/lib/analysis/investigation-template-model.js → packages/workspace/investigation-template-model.mjs} +18 -21
- package/{frontend/src/lib/analysis/page-baseline.js → packages/workspace/page-baseline.mjs} +6 -13
- package/{frontend/src/lib/analysis/relationship-observation-model.js → packages/workspace/relationship-observation-model.mjs} +13 -17
- package/{frontend/src/lib/analysis/shortlist-model.js → packages/workspace/shortlist-model.mjs} +20 -18
- package/{frontend/src/lib/analysis/watchlist-history.js → packages/workspace/watchlist-history.mjs} +9 -16
- package/{frontend/src/lib/analysis/watchlist-store.js → packages/workspace/watchlist-store.mjs} +24 -17
- package/{frontend/src/lib/analysis/website-snapshot-model.js → packages/workspace/website-snapshot-model.mjs} +13 -13
- package/{frontend/src/lib/analysis/workspace-archive-crypto.js → packages/workspace/workspace-archive-crypto.mjs} +49 -19
- package/{frontend/src/lib/analysis/workspace-archive.js → packages/workspace/workspace-archive.mjs} +110 -89
- package/third-party-notices.txt +14 -14
- package/frontend/src/lib/analysis/bulk-retry-plan.js +0 -79
- package/frontend/src/lib/analysis/bulk-source-coverage.js +0 -76
- package/frontend/src/lib/analysis/case-migration-model.js +0 -305
- package/frontend/src/lib/analysis/case-record-model.js +0 -9
- package/frontend/src/lib/analysis/case-response-model.js +0 -728
- package/frontend/src/lib/analysis/case-response-packet.js +0 -583
- package/frontend/src/lib/analysis/lookup-decision-support.js +0 -542
- package/frontend/src/lib/analysis/lookup-investigation-brief.js +0 -153
- package/frontend/src/lib/analysis/lookup-presentation.js +0 -120
- package/frontend/src/lib/analysis/lookup-response.js +0 -2
- package/frontend/src/lib/analysis/lookup-source-refresh.js +0 -277
- package/frontend/src/lib/analysis/lookup-summary-model.js +0 -398
- package/frontend/src/lib/analysis/scoring.js +0 -55
- package/frontend/src/lib/analysis/utils.js +0 -255
- package/frontend/src/lib/analysis/visualization-bounds.js +0 -26
- package/frontend/src/lib/analysis/visualization-force-graph.js +0 -352
- package/frontend/src/lib/analysis/visualization-matrix.js +0 -131
- package/frontend/src/lib/analysis/visualization-models.js +0 -338
- package/frontend/src/lib/analysis/visualization-time-series.js +0 -185
- package/lib/bounded-json-response.mjs +0 -170
- package/lib/lookup-response-contract.mjs +0 -1034
- package/lib/observation.mjs +0 -115
- /package/{frontend/src/lib/analysis/opaque-reference-id.js → packages/cases/opaque-reference-id.mjs} +0 -0
- /package/{frontend/src/lib/analysis/acquisition-due-diligence.js → packages/investigation/acquisition-due-diligence.mjs} +0 -0
- /package/{frontend/src/lib/analysis/evidence-coverage-ledger.js → packages/investigation/evidence-coverage-ledger.mjs} +0 -0
- /package/{frontend/src/lib/analysis/dns-record-normalization.js → packages/workspace/dns-record-normalization.mjs} +0 -0
|
@@ -0,0 +1,1288 @@
|
|
|
1
|
+
import { defineSchemaCompatibility } from "./schema-compatibility.mjs";
|
|
2
|
+
import { defineSchemaLifecycleFamily } from "./schema-lifecycle.mjs";
|
|
3
|
+
export const RISK_CALIBRATION_DATASET_SCHEMA = 'whoisleuth.risk-calibration-dataset';
|
|
4
|
+
export const RISK_CALIBRATION_DATASET_VERSION = 2;
|
|
5
|
+
export const SUPPORTED_RISK_CALIBRATION_DATASET_VERSIONS = Object.freeze([RISK_CALIBRATION_DATASET_VERSION]);
|
|
6
|
+
export const RISK_CALIBRATION_REPORT_SCHEMA = 'whoisleuth.cli.risk-calibration';
|
|
7
|
+
export const RISK_CALIBRATION_REPORT_VERSION = 3;
|
|
8
|
+
export const SUPPORTED_RISK_CALIBRATION_REPORT_VERSIONS = Object.freeze([RISK_CALIBRATION_REPORT_VERSION]);
|
|
9
|
+
export const MAX_RISK_CALIBRATION_INPUT_BYTES = 2 * 1024 * 1024;
|
|
10
|
+
export const MAX_RISK_CALIBRATION_RECORDS = 500;
|
|
11
|
+
export const MAX_RISK_CALIBRATION_STRING_LENGTH = 256;
|
|
12
|
+
export const MAX_RISK_CALIBRATION_SUMMARY_BYTES = 512 * 1024;
|
|
13
|
+
export const MAX_RISK_CALIBRATION_DETAILED_REPORT_BYTES = 32 * 1024 * 1024;
|
|
14
|
+
export const MAX_RISK_CALIBRATION_JSON_DEPTH = 48;
|
|
15
|
+
export const MAX_RISK_CALIBRATION_JSON_KEYS = 50_000;
|
|
16
|
+
export const MAX_RISK_CALIBRATION_JSON_VALUES = 100_000;
|
|
17
|
+
export const MAX_RISK_CALIBRATION_JSON_CONTAINER_ITEMS = 10_000;
|
|
18
|
+
export const MAX_RISK_CALIBRATION_MUTATIONS = 30;
|
|
19
|
+
export const MAX_RISK_CALIBRATION_PROVIDERS = 10;
|
|
20
|
+
export const MAX_RISK_CALIBRATION_FINDINGS_PER_PROVIDER = 100;
|
|
21
|
+
export const MAX_RISK_CALIBRATION_TIMESTAMP_LENGTH = 64;
|
|
22
|
+
export const MAX_RISK_CALIBRATION_RECORD_ID_LENGTH = 128;
|
|
23
|
+
export const MAX_RISK_CALIBRATION_DOMAIN_LENGTH = 253;
|
|
24
|
+
export const MAX_RISK_CALIBRATION_DISPOSITION_LENGTH = 32;
|
|
25
|
+
export const MAX_RISK_CALIBRATION_REVIEW_REASON_LENGTH = 64;
|
|
26
|
+
export const MAX_RISK_CALIBRATION_PROVIDER_ID_LENGTH = 64;
|
|
27
|
+
export const MAX_RISK_CALIBRATION_PROVIDER_STATE_LENGTH = 32;
|
|
28
|
+
export const MAX_RISK_CALIBRATION_FINDING_CATEGORY_LENGTH = 64;
|
|
29
|
+
export const MAX_RISK_CALIBRATION_AVAILABILITY_LENGTH = 32;
|
|
30
|
+
export const MAX_RISK_CALIBRATION_ACTIVITY_LENGTH = 32;
|
|
31
|
+
export const MAX_RISK_CALIBRATION_SCAN_DEPTH_LENGTH = 16;
|
|
32
|
+
export const MAX_RISK_CALIBRATION_DOMAIN_AGE_DAYS = 100_000;
|
|
33
|
+
export const MAX_RISK_CALIBRATION_SUMMARY_STRATA = 32;
|
|
34
|
+
export const MAX_RISK_CALIBRATION_MODEL_VERSION = 100;
|
|
35
|
+
export const RISK_CALIBRATION_REVIEW_REASON_VALUES = Object.freeze([
|
|
36
|
+
'authorized_or_owned',
|
|
37
|
+
'shared_infrastructure',
|
|
38
|
+
'generic_platform_or_template',
|
|
39
|
+
'parked_or_reseller',
|
|
40
|
+
'insufficient_evidence',
|
|
41
|
+
'legitimate_third_party',
|
|
42
|
+
'confirmed_credential_abuse',
|
|
43
|
+
'confirmed_malware',
|
|
44
|
+
'other_reviewed',
|
|
45
|
+
]);
|
|
46
|
+
export const RISK_CALIBRATION_MUTATION_TYPES = Object.freeze([
|
|
47
|
+
'unicode_homoglyph',
|
|
48
|
+
'unicode_homoglyph_depth_2',
|
|
49
|
+
'dictionary',
|
|
50
|
+
'dictionary_token_replacement',
|
|
51
|
+
'ascii_homoglyph',
|
|
52
|
+
'bitsquatting',
|
|
53
|
+
'tld_embedding',
|
|
54
|
+
'tld_typo',
|
|
55
|
+
'tld_substitution',
|
|
56
|
+
'character_addition',
|
|
57
|
+
'character_omission',
|
|
58
|
+
'character_duplication',
|
|
59
|
+
'character_transposition',
|
|
60
|
+
'pluralization',
|
|
61
|
+
'www_prefix',
|
|
62
|
+
'hyphenation',
|
|
63
|
+
'separator_omission',
|
|
64
|
+
'word_reordering',
|
|
65
|
+
'keyboard_substitution',
|
|
66
|
+
'keyboard_insertion',
|
|
67
|
+
'vowel_swap',
|
|
68
|
+
]);
|
|
69
|
+
export const RISK_CALIBRATION_DATASET_ROOT_KEYS = Object.freeze([
|
|
70
|
+
'schema', 'version', 'records',
|
|
71
|
+
]);
|
|
72
|
+
export const RISK_CALIBRATION_DATASET_OPTIONAL_ROOT_KEYS = Object.freeze([
|
|
73
|
+
'export', 'limitations',
|
|
74
|
+
]);
|
|
75
|
+
export const RISK_CALIBRATION_DATASET_RECORD_KEYS = Object.freeze([
|
|
76
|
+
'id', 'domain', 'analystDisposition', 'evidence',
|
|
77
|
+
]);
|
|
78
|
+
export const RISK_CALIBRATION_DATASET_OPTIONAL_RECORD_KEYS = Object.freeze([
|
|
79
|
+
'reviewReasonCode',
|
|
80
|
+
]);
|
|
81
|
+
export const RISK_CALIBRATION_EVIDENCE_OPTIONAL_KEYS = Object.freeze([
|
|
82
|
+
'activityStatus', 'mutationTypes', 'domainAgeDays', 'faviconMatch',
|
|
83
|
+
'faviconNearMatch', 'reusesOfficialAssets', 'hasPasswordField', 'hasMx',
|
|
84
|
+
'hasSpf', 'hasDmarc', 'privacyProtected', 'phishingLanguageMatch',
|
|
85
|
+
'hasExternalFormAction', 'idnReferenceMatch', 'pageBaselineMatch',
|
|
86
|
+
'hasActiveBrandProfile', 'scanDepth', 'observedAt', 'threatIntelligence',
|
|
87
|
+
]);
|
|
88
|
+
export const RISK_CALIBRATION_THREAT_INTELLIGENCE_KEYS = Object.freeze([
|
|
89
|
+
'providers',
|
|
90
|
+
]);
|
|
91
|
+
export const RISK_CALIBRATION_PROVIDER_KEYS = Object.freeze([
|
|
92
|
+
'provider', 'state', 'findings',
|
|
93
|
+
]);
|
|
94
|
+
export const RISK_CALIBRATION_PROVIDER_OPTIONAL_KEYS = Object.freeze([
|
|
95
|
+
'observation',
|
|
96
|
+
]);
|
|
97
|
+
export const RISK_CALIBRATION_PROVIDER_IDENTITY_KEYS = Object.freeze(['id']);
|
|
98
|
+
export const RISK_CALIBRATION_OBSERVATION_OPTIONAL_KEYS = Object.freeze(['observedAt']);
|
|
99
|
+
export const RISK_CALIBRATION_FINDING_KEYS = Object.freeze(['category']);
|
|
100
|
+
export const RISK_CALIBRATION_FINDING_OPTIONAL_KEYS = Object.freeze([
|
|
101
|
+
'firstObservedAt', 'lastObservedAt',
|
|
102
|
+
]);
|
|
103
|
+
export const RISK_CALIBRATION_EXPORT_KEYS = Object.freeze([
|
|
104
|
+
'selected', 'included', 'excluded', 'exclusions',
|
|
105
|
+
]);
|
|
106
|
+
export const RISK_CALIBRATION_EXPORT_EXCLUSION_KEYS = Object.freeze([
|
|
107
|
+
'caseId', 'domain', 'reason',
|
|
108
|
+
]);
|
|
109
|
+
export const RISK_CALIBRATION_EXPORT_LIMITATIONS = Object.freeze([
|
|
110
|
+
'This file contains analyst-selected case domains, reviewed dispositions, and a bounded subset of normalised scoring inputs.',
|
|
111
|
+
'It excludes notes, tags, assertions, actions, contacts, raw source data, provider payloads, and stored Risk scores.',
|
|
112
|
+
'The CLI revalidates every record and does not tune the Risk model automatically. Analyst dispositions do not prove maliciousness or safety.',
|
|
113
|
+
]);
|
|
114
|
+
export const RISK_CALIBRATION_REPORT_V3_DETAILED_ROOT_KEYS = Object.freeze([
|
|
115
|
+
'schema', 'version', 'mode', 'generatedAt', 'dataset', 'riskModelVersion',
|
|
116
|
+
'currentReviewThreshold', 'summary', 'thresholds', 'strata',
|
|
117
|
+
'modelComparison', 'records', 'interpretation',
|
|
118
|
+
]);
|
|
119
|
+
export const RISK_CALIBRATION_REPORT_V3_SUMMARY_ROOT_KEYS = Object.freeze([
|
|
120
|
+
'schema', 'version', 'mode', 'generatedAt', 'dataset', 'riskModelVersion',
|
|
121
|
+
'currentReviewThreshold', 'summary', 'thresholds', 'strata',
|
|
122
|
+
'modelComparison', 'privacy', 'interpretation',
|
|
123
|
+
]);
|
|
124
|
+
export const RISK_CALIBRATION_REPORT_DATASET_KEYS = Object.freeze([
|
|
125
|
+
'schema', 'version', 'recordCount',
|
|
126
|
+
]);
|
|
127
|
+
export const RISK_CALIBRATION_REPORT_SUMMARY_KEYS = Object.freeze([
|
|
128
|
+
'total', 'positive', 'negative', 'excluded', 'scoreBands',
|
|
129
|
+
]);
|
|
130
|
+
export const RISK_CALIBRATION_REPORT_SCORE_BAND_KEYS = Object.freeze([
|
|
131
|
+
'not_scored', '0_39', '40_69', '70_100',
|
|
132
|
+
]);
|
|
133
|
+
export const RISK_CALIBRATION_REPORT_METRIC_KEYS = Object.freeze([
|
|
134
|
+
'threshold', 'truePositive', 'falsePositive', 'trueNegative', 'falseNegative',
|
|
135
|
+
'precision', 'recall', 'specificity', 'falsePositiveRate',
|
|
136
|
+
'f1', 'balancedAccuracy', 'confidence95',
|
|
137
|
+
]);
|
|
138
|
+
export const RISK_CALIBRATION_REPORT_CONFIDENCE_KEYS = Object.freeze([
|
|
139
|
+
'precision', 'recall', 'specificity',
|
|
140
|
+
]);
|
|
141
|
+
export const RISK_CALIBRATION_REPORT_INTERVAL_KEYS = Object.freeze(['lower', 'upper']);
|
|
142
|
+
export const RISK_CALIBRATION_REPORT_STRATUM_KEYS = Object.freeze([
|
|
143
|
+
'dimension', 'value', 'sampleCount', 'insufficientSample', 'metrics',
|
|
144
|
+
]);
|
|
145
|
+
export const RISK_CALIBRATION_REPORT_MODEL_COMPARISON_KEYS = Object.freeze([
|
|
146
|
+
'available', 'previousModelVersion', 'currentModelVersion', 'scoresChanged',
|
|
147
|
+
'bandsChanged', 'thresholdClassificationsChanged',
|
|
148
|
+
]);
|
|
149
|
+
export const RISK_CALIBRATION_REPORT_RECORD_KEYS = Object.freeze([
|
|
150
|
+
'id', 'domain', 'analystDisposition', 'reviewReasonCode',
|
|
151
|
+
'interoperabilityTags', 'metricClass', 'includedInMetrics',
|
|
152
|
+
'exclusionReason', 'modelVersion', 'score', 'band', 'factors',
|
|
153
|
+
]);
|
|
154
|
+
export const RISK_CALIBRATION_REPORT_FACTOR_KEYS = Object.freeze(['family', 'label', 'delta']);
|
|
155
|
+
export const RISK_CALIBRATION_REPORT_PRIVACY_KEYS = Object.freeze([
|
|
156
|
+
'targetsRetained', 'identifiersRetained', 'rawEvidenceRetained',
|
|
157
|
+
]);
|
|
158
|
+
export const RISK_CALIBRATION_REPORT_INTERPRETATION_KEYS = Object.freeze([
|
|
159
|
+
'authority', 'statement', 'automaticTuning', 'networkRequests', 'persisted',
|
|
160
|
+
]);
|
|
161
|
+
export const RISK_CALIBRATION_DATASET_COMPATIBILITY = defineSchemaCompatibility({
|
|
162
|
+
id: 'cli.risk-calibration-dataset',
|
|
163
|
+
kind: 'cli_document',
|
|
164
|
+
schema: RISK_CALIBRATION_DATASET_SCHEMA,
|
|
165
|
+
currentVersion: RISK_CALIBRATION_DATASET_VERSION,
|
|
166
|
+
supportedVersions: SUPPORTED_RISK_CALIBRATION_DATASET_VERSIONS,
|
|
167
|
+
acceptsUnversionedLegacy: false,
|
|
168
|
+
futureVersionBehavior: 'reject',
|
|
169
|
+
migration: 'read_only',
|
|
170
|
+
writeSemantics: 'read_only',
|
|
171
|
+
byteBudget: MAX_RISK_CALIBRATION_INPUT_BYTES,
|
|
172
|
+
owner: 'packages/contracts/risk-calibration.mts',
|
|
173
|
+
note: 'Offline labelled fixture input; version 2 adds bounded review context without live collection.',
|
|
174
|
+
});
|
|
175
|
+
export const RISK_CALIBRATION_REPORT_COMPATIBILITY = defineSchemaCompatibility({
|
|
176
|
+
id: 'cli.risk-calibration-report',
|
|
177
|
+
kind: 'cli_document',
|
|
178
|
+
schema: RISK_CALIBRATION_REPORT_SCHEMA,
|
|
179
|
+
currentVersion: RISK_CALIBRATION_REPORT_VERSION,
|
|
180
|
+
supportedVersions: SUPPORTED_RISK_CALIBRATION_REPORT_VERSIONS,
|
|
181
|
+
acceptsUnversionedLegacy: false,
|
|
182
|
+
futureVersionBehavior: 'reject',
|
|
183
|
+
migration: 'read_only',
|
|
184
|
+
writeSemantics: 'read_only',
|
|
185
|
+
byteBudget: null,
|
|
186
|
+
owner: 'packages/contracts/risk-calibration.mts',
|
|
187
|
+
note: 'Offline calibration output; version 3 distinguishes the bounded detailed report from a target-free aggregate summary for tab-local review.',
|
|
188
|
+
});
|
|
189
|
+
function exactReportObject(path, requiredKeys) {
|
|
190
|
+
return {
|
|
191
|
+
path,
|
|
192
|
+
requiredKeys,
|
|
193
|
+
alternativeRequiredKeys: [],
|
|
194
|
+
optionalKeys: [],
|
|
195
|
+
unknownKeys: 'reject',
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function metricObjects(path) {
|
|
199
|
+
return [
|
|
200
|
+
exactReportObject(path, RISK_CALIBRATION_REPORT_METRIC_KEYS),
|
|
201
|
+
exactReportObject(`${path}.confidence95`, RISK_CALIBRATION_REPORT_CONFIDENCE_KEYS),
|
|
202
|
+
...RISK_CALIBRATION_REPORT_CONFIDENCE_KEYS.map((field) => (exactReportObject(`${path}.confidence95.${field}`, RISK_CALIBRATION_REPORT_INTERVAL_KEYS))),
|
|
203
|
+
];
|
|
204
|
+
}
|
|
205
|
+
const DATASET_SHAPE_OBJECTS = [
|
|
206
|
+
{
|
|
207
|
+
path: '$',
|
|
208
|
+
requiredKeys: RISK_CALIBRATION_DATASET_ROOT_KEYS,
|
|
209
|
+
alternativeRequiredKeys: [],
|
|
210
|
+
optionalKeys: [],
|
|
211
|
+
unknownKeys: 'discard_bounded',
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
path: '$.records[]',
|
|
215
|
+
requiredKeys: RISK_CALIBRATION_DATASET_RECORD_KEYS,
|
|
216
|
+
alternativeRequiredKeys: [],
|
|
217
|
+
optionalKeys: RISK_CALIBRATION_DATASET_OPTIONAL_RECORD_KEYS,
|
|
218
|
+
unknownKeys: 'discard_bounded',
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
path: '$.records[].evidence',
|
|
222
|
+
requiredKeys: [],
|
|
223
|
+
alternativeRequiredKeys: [{ keys: ['availability', 'state'], resolution: 'first_present' }],
|
|
224
|
+
optionalKeys: RISK_CALIBRATION_EVIDENCE_OPTIONAL_KEYS,
|
|
225
|
+
unknownKeys: 'discard_bounded',
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
path: '$.records[].evidence.threatIntelligence',
|
|
229
|
+
requiredKeys: RISK_CALIBRATION_THREAT_INTELLIGENCE_KEYS,
|
|
230
|
+
alternativeRequiredKeys: [],
|
|
231
|
+
optionalKeys: [],
|
|
232
|
+
unknownKeys: 'discard_bounded',
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
path: '$.records[].evidence.threatIntelligence.providers[]',
|
|
236
|
+
requiredKeys: RISK_CALIBRATION_PROVIDER_KEYS,
|
|
237
|
+
alternativeRequiredKeys: [],
|
|
238
|
+
optionalKeys: RISK_CALIBRATION_PROVIDER_OPTIONAL_KEYS,
|
|
239
|
+
unknownKeys: 'discard_bounded',
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
path: '$.records[].evidence.threatIntelligence.providers[].provider',
|
|
243
|
+
requiredKeys: RISK_CALIBRATION_PROVIDER_IDENTITY_KEYS,
|
|
244
|
+
alternativeRequiredKeys: [],
|
|
245
|
+
optionalKeys: [],
|
|
246
|
+
unknownKeys: 'discard_bounded',
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
path: '$.records[].evidence.threatIntelligence.providers[].observation',
|
|
250
|
+
requiredKeys: [],
|
|
251
|
+
alternativeRequiredKeys: [],
|
|
252
|
+
optionalKeys: RISK_CALIBRATION_OBSERVATION_OPTIONAL_KEYS,
|
|
253
|
+
unknownKeys: 'discard_bounded',
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
path: '$.records[].evidence.threatIntelligence.providers[].findings[]',
|
|
257
|
+
requiredKeys: RISK_CALIBRATION_FINDING_KEYS,
|
|
258
|
+
alternativeRequiredKeys: [],
|
|
259
|
+
optionalKeys: RISK_CALIBRATION_FINDING_OPTIONAL_KEYS,
|
|
260
|
+
unknownKeys: 'discard_bounded',
|
|
261
|
+
},
|
|
262
|
+
];
|
|
263
|
+
const REPORT_COMMON_OBJECTS = [
|
|
264
|
+
exactReportObject('$.dataset', RISK_CALIBRATION_REPORT_DATASET_KEYS),
|
|
265
|
+
exactReportObject('$.summary', RISK_CALIBRATION_REPORT_SUMMARY_KEYS),
|
|
266
|
+
exactReportObject('$.summary.scoreBands', RISK_CALIBRATION_REPORT_SCORE_BAND_KEYS),
|
|
267
|
+
...metricObjects('$.thresholds[]'),
|
|
268
|
+
exactReportObject('$.strata[]', RISK_CALIBRATION_REPORT_STRATUM_KEYS),
|
|
269
|
+
...metricObjects('$.strata[].metrics'),
|
|
270
|
+
exactReportObject('$.modelComparison', RISK_CALIBRATION_REPORT_MODEL_COMPARISON_KEYS),
|
|
271
|
+
exactReportObject('$.interpretation', RISK_CALIBRATION_REPORT_INTERPRETATION_KEYS),
|
|
272
|
+
];
|
|
273
|
+
const REPORT_RECORD_OBJECTS = [
|
|
274
|
+
exactReportObject('$.records[]', RISK_CALIBRATION_REPORT_RECORD_KEYS),
|
|
275
|
+
exactReportObject('$.records[].factors[]', RISK_CALIBRATION_REPORT_FACTOR_KEYS),
|
|
276
|
+
];
|
|
277
|
+
export const RISK_CALIBRATION_SCHEMA_LIFECYCLE = defineSchemaLifecycleFamily({
|
|
278
|
+
id: 'risk-calibration',
|
|
279
|
+
owner: 'packages/contracts/risk-calibration.mts',
|
|
280
|
+
privacy: 'analyst_authored_sensitive',
|
|
281
|
+
compatibility: [
|
|
282
|
+
RISK_CALIBRATION_DATASET_COMPATIBILITY,
|
|
283
|
+
RISK_CALIBRATION_REPORT_COMPATIBILITY,
|
|
284
|
+
],
|
|
285
|
+
contracts: [
|
|
286
|
+
{
|
|
287
|
+
compatibilityId: RISK_CALIBRATION_DATASET_COMPATIBILITY.id,
|
|
288
|
+
schema: RISK_CALIBRATION_DATASET_SCHEMA,
|
|
289
|
+
version: RISK_CALIBRATION_DATASET_VERSION,
|
|
290
|
+
role: 'document',
|
|
291
|
+
lifecycle: 'current',
|
|
292
|
+
readable: true,
|
|
293
|
+
emitted: true,
|
|
294
|
+
exactKeys: false,
|
|
295
|
+
extensionPolicy: 'discard_bounded',
|
|
296
|
+
futureVersionBehaviour: 'reject',
|
|
297
|
+
migrationTarget: null,
|
|
298
|
+
canonicalisation: null,
|
|
299
|
+
byteBudget: MAX_RISK_CALIBRATION_INPUT_BYTES,
|
|
300
|
+
fixtureIds: ['risk-calibration-dataset-v2'],
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
compatibilityId: RISK_CALIBRATION_REPORT_COMPATIBILITY.id,
|
|
304
|
+
schema: RISK_CALIBRATION_REPORT_SCHEMA,
|
|
305
|
+
version: RISK_CALIBRATION_REPORT_VERSION,
|
|
306
|
+
role: 'document',
|
|
307
|
+
lifecycle: 'current',
|
|
308
|
+
readable: true,
|
|
309
|
+
emitted: true,
|
|
310
|
+
exactKeys: true,
|
|
311
|
+
extensionPolicy: 'reject',
|
|
312
|
+
futureVersionBehaviour: 'reject',
|
|
313
|
+
migrationTarget: null,
|
|
314
|
+
canonicalisation: null,
|
|
315
|
+
byteBudget: null,
|
|
316
|
+
fixtureIds: [
|
|
317
|
+
'risk-calibration-report-v3-detailed',
|
|
318
|
+
'risk-calibration-report-v3-summary',
|
|
319
|
+
],
|
|
320
|
+
},
|
|
321
|
+
],
|
|
322
|
+
fixtures: [
|
|
323
|
+
{
|
|
324
|
+
id: 'risk-calibration-dataset-v2',
|
|
325
|
+
path: 'test/fixtures/risk-calibration-dataset-v2.json',
|
|
326
|
+
bytes: 1_154,
|
|
327
|
+
sha256: 'bd650fe84923c61658d451c25d928bd5ff8e54ff585f4f059e6798c681f9a401',
|
|
328
|
+
contentDigestSha256: null,
|
|
329
|
+
schema: RISK_CALIBRATION_DATASET_SCHEMA,
|
|
330
|
+
version: RISK_CALIBRATION_DATASET_VERSION,
|
|
331
|
+
role: 'current',
|
|
332
|
+
expectation: 'accepted_exact',
|
|
333
|
+
expectedOutputFixtureId: null,
|
|
334
|
+
scope: 'repository',
|
|
335
|
+
shapeId: 'risk-calibration.dataset.v2',
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
id: 'risk-calibration-report-v3-detailed',
|
|
339
|
+
path: 'test/fixtures/risk-calibration-report-v3-detailed.json',
|
|
340
|
+
bytes: 7_026,
|
|
341
|
+
sha256: '0c905036b05da2feb586199781444d5b575982bb5f6ef585d8f7c2e431363e8d',
|
|
342
|
+
contentDigestSha256: null,
|
|
343
|
+
schema: RISK_CALIBRATION_REPORT_SCHEMA,
|
|
344
|
+
version: RISK_CALIBRATION_REPORT_VERSION,
|
|
345
|
+
role: 'current',
|
|
346
|
+
expectation: 'accepted_exact',
|
|
347
|
+
expectedOutputFixtureId: null,
|
|
348
|
+
scope: 'repository',
|
|
349
|
+
shapeId: 'risk-calibration.report.v3-detailed',
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
id: 'risk-calibration-report-v3-summary',
|
|
353
|
+
path: 'test/fixtures/risk-calibration-report-v3-summary.json',
|
|
354
|
+
bytes: 5_486,
|
|
355
|
+
sha256: '637e739eac58ec06f3f2c9688fb2c03d5d139536e9629e1e1a811d3c8c7563d0',
|
|
356
|
+
contentDigestSha256: null,
|
|
357
|
+
schema: RISK_CALIBRATION_REPORT_SCHEMA,
|
|
358
|
+
version: RISK_CALIBRATION_REPORT_VERSION,
|
|
359
|
+
role: 'current',
|
|
360
|
+
expectation: 'accepted_exact',
|
|
361
|
+
expectedOutputFixtureId: null,
|
|
362
|
+
scope: 'repository',
|
|
363
|
+
shapeId: 'risk-calibration.report.v3-summary',
|
|
364
|
+
},
|
|
365
|
+
],
|
|
366
|
+
metadata: {
|
|
367
|
+
metadataVersion: 4,
|
|
368
|
+
enforcement: 'declarative_only',
|
|
369
|
+
shapes: [
|
|
370
|
+
...[RISK_CALIBRATION_DATASET_VERSION].map((version) => ({
|
|
371
|
+
id: `risk-calibration.dataset.v${version}`,
|
|
372
|
+
schema: RISK_CALIBRATION_DATASET_SCHEMA,
|
|
373
|
+
versions: [version],
|
|
374
|
+
objects: DATASET_SHAPE_OBJECTS,
|
|
375
|
+
fixedArrays: [],
|
|
376
|
+
normalisation: 'project_known_fields',
|
|
377
|
+
target: null,
|
|
378
|
+
discriminator: null,
|
|
379
|
+
})),
|
|
380
|
+
{
|
|
381
|
+
id: 'risk-calibration.report.v3-detailed',
|
|
382
|
+
schema: RISK_CALIBRATION_REPORT_SCHEMA,
|
|
383
|
+
versions: [RISK_CALIBRATION_REPORT_VERSION],
|
|
384
|
+
objects: [
|
|
385
|
+
exactReportObject('$', RISK_CALIBRATION_REPORT_V3_DETAILED_ROOT_KEYS),
|
|
386
|
+
...REPORT_COMMON_OBJECTS,
|
|
387
|
+
...REPORT_RECORD_OBJECTS,
|
|
388
|
+
],
|
|
389
|
+
fixedArrays: [],
|
|
390
|
+
normalisation: 'preserve_document',
|
|
391
|
+
target: null,
|
|
392
|
+
discriminator: { path: '$.mode', value: 'detailed' },
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
id: 'risk-calibration.report.v3-summary',
|
|
396
|
+
schema: RISK_CALIBRATION_REPORT_SCHEMA,
|
|
397
|
+
versions: [RISK_CALIBRATION_REPORT_VERSION],
|
|
398
|
+
objects: [
|
|
399
|
+
exactReportObject('$', RISK_CALIBRATION_REPORT_V3_SUMMARY_ROOT_KEYS),
|
|
400
|
+
...REPORT_COMMON_OBJECTS,
|
|
401
|
+
exactReportObject('$.privacy', RISK_CALIBRATION_REPORT_PRIVACY_KEYS),
|
|
402
|
+
],
|
|
403
|
+
fixedArrays: [],
|
|
404
|
+
normalisation: 'preserve_document',
|
|
405
|
+
target: null,
|
|
406
|
+
discriminator: { path: '$.mode', value: 'summary' },
|
|
407
|
+
},
|
|
408
|
+
],
|
|
409
|
+
boundProfiles: [
|
|
410
|
+
{
|
|
411
|
+
id: 'risk-calibration.dataset-bounds.v2',
|
|
412
|
+
bounds: [
|
|
413
|
+
{ id: 'raw-bytes', path: '$', phase: 'raw_intake', unit: 'bytes', minimum: 1, maximum: MAX_RISK_CALIBRATION_INPUT_BYTES, handling: 'reject' },
|
|
414
|
+
{ id: 'serialised-bytes', path: '$', phase: 'serialised', unit: 'bytes', minimum: 1, maximum: MAX_RISK_CALIBRATION_INPUT_BYTES, handling: 'reject' },
|
|
415
|
+
{ id: 'json-depth', path: '$', phase: 'pre_accumulation', unit: 'depth', minimum: 0, maximum: MAX_RISK_CALIBRATION_JSON_DEPTH, handling: 'reject' },
|
|
416
|
+
{ id: 'json-keys', path: '$', phase: 'pre_accumulation', unit: 'keys', minimum: 1, maximum: MAX_RISK_CALIBRATION_JSON_KEYS, handling: 'reject' },
|
|
417
|
+
{ id: 'json-values', path: '$', phase: 'pre_accumulation', unit: 'values', minimum: 1, maximum: MAX_RISK_CALIBRATION_JSON_VALUES, handling: 'reject' },
|
|
418
|
+
{ id: 'json-container-items', path: '$', phase: 'pre_accumulation', unit: 'items', minimum: 1, maximum: MAX_RISK_CALIBRATION_JSON_CONTAINER_ITEMS, handling: 'reject' },
|
|
419
|
+
{ id: 'records', path: '$.records', phase: 'pre_accumulation', unit: 'entries', minimum: 1, maximum: MAX_RISK_CALIBRATION_RECORDS, handling: 'reject' },
|
|
420
|
+
{ id: 'record-id', path: '$.records[].id', phase: 'normalised', unit: 'characters', minimum: 1, maximum: MAX_RISK_CALIBRATION_RECORD_ID_LENGTH, handling: 'reject' },
|
|
421
|
+
{ id: 'domain', path: '$.records[].domain', phase: 'normalised', unit: 'characters', minimum: 1, maximum: MAX_RISK_CALIBRATION_DOMAIN_LENGTH, handling: 'reject' },
|
|
422
|
+
{ id: 'disposition', path: '$.records[].analystDisposition', phase: 'normalised', unit: 'characters', minimum: 1, maximum: MAX_RISK_CALIBRATION_DISPOSITION_LENGTH, handling: 'reject' },
|
|
423
|
+
{ id: 'review-reason', path: '$.records[].reviewReasonCode', phase: 'normalised', unit: 'characters', minimum: 1, maximum: MAX_RISK_CALIBRATION_REVIEW_REASON_LENGTH, handling: 'reject' },
|
|
424
|
+
{ id: 'availability', path: '$.records[].evidence.availability', phase: 'normalised', unit: 'characters', minimum: 1, maximum: MAX_RISK_CALIBRATION_AVAILABILITY_LENGTH, handling: 'reject' },
|
|
425
|
+
{ id: 'activity', path: '$.records[].evidence.activityStatus', phase: 'normalised', unit: 'characters', minimum: 1, maximum: MAX_RISK_CALIBRATION_ACTIVITY_LENGTH, handling: 'reject' },
|
|
426
|
+
{ id: 'scan-depth', path: '$.records[].evidence.scanDepth', phase: 'normalised', unit: 'characters', minimum: 1, maximum: MAX_RISK_CALIBRATION_SCAN_DEPTH_LENGTH, handling: 'reject' },
|
|
427
|
+
{ id: 'general-text', path: '$.records[].evidence.phishingLanguageMatch', phase: 'normalised', unit: 'characters', minimum: 1, maximum: MAX_RISK_CALIBRATION_STRING_LENGTH, handling: 'reject' },
|
|
428
|
+
{ id: 'timestamp', path: '$.records[].evidence.observedAt', phase: 'normalised', unit: 'characters', minimum: 1, maximum: MAX_RISK_CALIBRATION_TIMESTAMP_LENGTH, handling: 'reject' },
|
|
429
|
+
{ id: 'domain-age', path: '$.records[].evidence.domainAgeDays', phase: 'normalised', unit: 'finite_number', minimum: 0, maximum: MAX_RISK_CALIBRATION_DOMAIN_AGE_DAYS, handling: 'reject' },
|
|
430
|
+
{ id: 'mutation-types', path: '$.records[].evidence.mutationTypes', phase: 'pre_accumulation', unit: 'items', minimum: 0, maximum: MAX_RISK_CALIBRATION_MUTATIONS, handling: 'reject' },
|
|
431
|
+
{ id: 'providers', path: '$.records[].evidence.threatIntelligence.providers', phase: 'pre_accumulation', unit: 'items', minimum: 0, maximum: MAX_RISK_CALIBRATION_PROVIDERS, handling: 'reject' },
|
|
432
|
+
{ id: 'provider-id', path: '$.records[].evidence.threatIntelligence.providers[].provider.id', phase: 'normalised', unit: 'characters', minimum: 1, maximum: MAX_RISK_CALIBRATION_PROVIDER_ID_LENGTH, handling: 'reject' },
|
|
433
|
+
{ id: 'provider-state', path: '$.records[].evidence.threatIntelligence.providers[].state', phase: 'normalised', unit: 'characters', minimum: 1, maximum: MAX_RISK_CALIBRATION_PROVIDER_STATE_LENGTH, handling: 'reject' },
|
|
434
|
+
{ id: 'provider-findings', path: '$.records[].evidence.threatIntelligence.providers[].findings', phase: 'pre_accumulation', unit: 'items', minimum: 0, maximum: MAX_RISK_CALIBRATION_FINDINGS_PER_PROVIDER, handling: 'reject' },
|
|
435
|
+
{ id: 'finding-category', path: '$.records[].evidence.threatIntelligence.providers[].findings[].category', phase: 'normalised', unit: 'characters', minimum: 1, maximum: MAX_RISK_CALIBRATION_FINDING_CATEGORY_LENGTH, handling: 'reject' },
|
|
436
|
+
],
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
id: 'risk-calibration.report-detailed.v3',
|
|
440
|
+
bounds: [
|
|
441
|
+
{ id: 'records', path: '$.records', phase: 'normalised', unit: 'entries', minimum: 1, maximum: MAX_RISK_CALIBRATION_RECORDS, handling: 'reject' },
|
|
442
|
+
],
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
id: 'risk-calibration.report-detailed-json-output.v3',
|
|
446
|
+
bounds: [
|
|
447
|
+
{ id: 'serialised-bytes', path: '$', phase: 'serialised', unit: 'bytes', minimum: 1, maximum: MAX_RISK_CALIBRATION_DETAILED_REPORT_BYTES, handling: 'reject' },
|
|
448
|
+
],
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
id: 'risk-calibration.report-summary-output.v3',
|
|
452
|
+
bounds: [
|
|
453
|
+
{ id: 'serialised-bytes', path: '$', phase: 'serialised', unit: 'bytes', minimum: 1, maximum: MAX_RISK_CALIBRATION_SUMMARY_BYTES, handling: 'reject' },
|
|
454
|
+
{ id: 'dataset-records', path: '$.dataset.recordCount', phase: 'normalised', unit: 'integer', minimum: 1, maximum: MAX_RISK_CALIBRATION_RECORDS, handling: 'reject' },
|
|
455
|
+
{ id: 'strata', path: '$.strata', phase: 'normalised', unit: 'entries', minimum: 0, maximum: MAX_RISK_CALIBRATION_SUMMARY_STRATA, handling: 'reject' },
|
|
456
|
+
{ id: 'model-version', path: '$.riskModelVersion', phase: 'normalised', unit: 'integer', minimum: 1, maximum: MAX_RISK_CALIBRATION_MODEL_VERSION, handling: 'reject' },
|
|
457
|
+
],
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
id: 'risk-calibration.report-summary-input.v3',
|
|
461
|
+
bounds: [
|
|
462
|
+
{ id: 'raw-bytes', path: '$', phase: 'raw_intake', unit: 'bytes', minimum: 1, maximum: MAX_RISK_CALIBRATION_SUMMARY_BYTES, handling: 'reject' },
|
|
463
|
+
{ id: 'serialised-bytes', path: '$', phase: 'serialised', unit: 'bytes', minimum: 1, maximum: MAX_RISK_CALIBRATION_SUMMARY_BYTES, handling: 'reject' },
|
|
464
|
+
{ id: 'json-depth', path: '$', phase: 'pre_accumulation', unit: 'depth', minimum: 0, maximum: MAX_RISK_CALIBRATION_JSON_DEPTH, handling: 'reject' },
|
|
465
|
+
{ id: 'json-keys', path: '$', phase: 'pre_accumulation', unit: 'keys', minimum: 1, maximum: MAX_RISK_CALIBRATION_JSON_KEYS, handling: 'reject' },
|
|
466
|
+
{ id: 'json-values', path: '$', phase: 'pre_accumulation', unit: 'values', minimum: 1, maximum: MAX_RISK_CALIBRATION_JSON_VALUES, handling: 'reject' },
|
|
467
|
+
{ id: 'json-container-items', path: '$', phase: 'pre_accumulation', unit: 'items', minimum: 1, maximum: MAX_RISK_CALIBRATION_JSON_CONTAINER_ITEMS, handling: 'reject' },
|
|
468
|
+
],
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
id: 'risk-calibration.cli-private-output.v1',
|
|
472
|
+
bounds: [
|
|
473
|
+
{ id: 'output-file-bytes', path: '$.output', phase: 'serialised', unit: 'bytes', minimum: 1, maximum: MAX_RISK_CALIBRATION_DETAILED_REPORT_BYTES, handling: 'reject' },
|
|
474
|
+
],
|
|
475
|
+
},
|
|
476
|
+
],
|
|
477
|
+
hooks: [
|
|
478
|
+
{ id: 'risk-calibration.browser.build-dataset', role: 'builder', runtime: 'browser', module: 'packages/investigation/risk-calibration-export.mts', exportName: 'buildRiskCalibrationDatasetExport' },
|
|
479
|
+
{ id: 'risk-calibration.browser.serialise-dataset', role: 'serialiser', runtime: 'browser', module: 'packages/investigation/risk-calibration-export.mts', exportName: 'serializeRiskCalibrationDatasetExport' },
|
|
480
|
+
{ id: 'risk-calibration.cli.parse-dataset', role: 'normaliser', runtime: 'cli', module: 'cli/risk-calibration.mts', exportName: 'parseRiskCalibrationDataset' },
|
|
481
|
+
{ id: 'risk-calibration.cli.build-detailed', role: 'builder', runtime: 'cli', module: 'cli/risk-calibration.mts', exportName: 'buildRiskCalibrationReport' },
|
|
482
|
+
{ id: 'risk-calibration.shared.build-summary', role: 'builder', runtime: 'shared', module: 'lib/risk-calibration-summary.mts', exportName: 'buildRiskCalibrationSummaryReport' },
|
|
483
|
+
{ id: 'risk-calibration.shared.parse-summary', role: 'normaliser', runtime: 'shared', module: 'lib/risk-calibration-summary.mts', exportName: 'parseRiskCalibrationSummaryReport' },
|
|
484
|
+
{ id: 'risk-calibration.cli.serialise-report', role: 'serialiser', runtime: 'cli', module: 'cli/risk-calibration.mts', exportName: 'serializeRiskCalibrationReport' },
|
|
485
|
+
{ id: 'risk-calibration.cli.format-terminal', role: 'serialiser', runtime: 'cli', module: 'cli/formatters/terminal.mts', exportName: 'formatTerminalRiskCalibration' },
|
|
486
|
+
{ id: 'risk-calibration.cli.write-private-file', role: 'private_file_writer', runtime: 'cli', module: 'cli/output-file.mts', exportName: 'writePrivateFile' },
|
|
487
|
+
{ id: 'risk-calibration.browser.read-summary', role: 'reviewer', runtime: 'browser', module: 'packages/investigation/risk-calibration-dashboard.mts', exportName: 'parseRiskCalibrationDashboard' },
|
|
488
|
+
],
|
|
489
|
+
serialisationProfiles: [
|
|
490
|
+
{
|
|
491
|
+
id: 'risk-calibration.dataset-json.v2',
|
|
492
|
+
schema: RISK_CALIBRATION_DATASET_SCHEMA,
|
|
493
|
+
versions: [RISK_CALIBRATION_DATASET_VERSION],
|
|
494
|
+
mediaType: 'application/json',
|
|
495
|
+
encoding: 'utf-8',
|
|
496
|
+
bom: false,
|
|
497
|
+
indentSpaces: 2,
|
|
498
|
+
terminalLf: true,
|
|
499
|
+
propertyOrder: 'normalised_fixed',
|
|
500
|
+
canonicalisation: null,
|
|
501
|
+
integrity: 'none',
|
|
502
|
+
serializerHookId: 'risk-calibration.browser.serialise-dataset',
|
|
503
|
+
verifierHookIds: [],
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
id: 'risk-calibration.report-json.v3',
|
|
507
|
+
schema: RISK_CALIBRATION_REPORT_SCHEMA,
|
|
508
|
+
versions: [RISK_CALIBRATION_REPORT_VERSION],
|
|
509
|
+
mediaType: 'application/json',
|
|
510
|
+
encoding: 'utf-8',
|
|
511
|
+
bom: false,
|
|
512
|
+
indentSpaces: 2,
|
|
513
|
+
terminalLf: true,
|
|
514
|
+
propertyOrder: 'normalised_fixed',
|
|
515
|
+
canonicalisation: null,
|
|
516
|
+
integrity: 'none',
|
|
517
|
+
serializerHookId: 'risk-calibration.cli.serialise-report',
|
|
518
|
+
verifierHookIds: [],
|
|
519
|
+
},
|
|
520
|
+
],
|
|
521
|
+
privacyProfiles: [
|
|
522
|
+
{
|
|
523
|
+
id: 'risk-calibration.dataset-export.v2',
|
|
524
|
+
classification: 'analyst_authored_sensitive',
|
|
525
|
+
projection: 'browser_export',
|
|
526
|
+
includedCategories: ['selected-case-identifiers', 'target-identifiers', 'reviewed-dispositions', 'review-reasons', 'bounded-scoring-evidence'],
|
|
527
|
+
excludedCategories: ['analyst-notes', 'tags', 'assertions', 'actions', 'contacts', 'raw-source-payloads', 'provider-payloads', 'stored-risk-scores'],
|
|
528
|
+
notePolicy: 'discarded',
|
|
529
|
+
retention: 'deliberate_local_file',
|
|
530
|
+
network: 'none',
|
|
531
|
+
sharingReview: 'required',
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
id: 'risk-calibration.dataset-input.v2',
|
|
535
|
+
classification: 'analyst_authored_sensitive',
|
|
536
|
+
projection: 'full_manifest',
|
|
537
|
+
includedCategories: ['target-identifiers', 'reviewed-dispositions', 'review-reasons', 'bounded-scoring-evidence', 'bounded-provider-findings'],
|
|
538
|
+
excludedCategories: ['analyst-notes', 'raw-source-payloads', 'provider-payloads', 'credentials', 'cookies', 'sessions'],
|
|
539
|
+
notePolicy: 'discarded',
|
|
540
|
+
retention: 'deliberate_local_file',
|
|
541
|
+
network: 'none',
|
|
542
|
+
sharingReview: 'required',
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
id: 'risk-calibration.detailed-output.v3',
|
|
546
|
+
classification: 'analyst_authored_sensitive',
|
|
547
|
+
projection: 'review_output',
|
|
548
|
+
includedCategories: ['target-identifiers', 'reviewed-dispositions', 'review-reasons', 'interoperability-tags', 'scores', 'bands', 'factors', 'aggregate-metrics'],
|
|
549
|
+
excludedCategories: ['analyst-notes', 'raw-source-payloads', 'provider-payloads', 'contacts', 'credentials', 'cookies', 'sessions'],
|
|
550
|
+
notePolicy: 'discarded',
|
|
551
|
+
retention: 'transient_report',
|
|
552
|
+
network: 'none',
|
|
553
|
+
sharingReview: 'required',
|
|
554
|
+
},
|
|
555
|
+
{
|
|
556
|
+
id: 'risk-calibration.summary-output.v3',
|
|
557
|
+
classification: 'metadata_only',
|
|
558
|
+
projection: 'metadata_only',
|
|
559
|
+
includedCategories: ['dataset-and-model-versions', 'aggregate-counts', 'fixed-threshold-metrics', 'confidence-intervals', 'bounded-strata', 'model-comparison-counts', 'zero-retention-declaration'],
|
|
560
|
+
excludedCategories: ['target-identifiers', 'record-identifiers', 'reviewed-dispositions', 'review-reasons', 'records', 'evidence', 'factors'],
|
|
561
|
+
notePolicy: 'not_applicable',
|
|
562
|
+
retention: 'transient_report',
|
|
563
|
+
network: 'none',
|
|
564
|
+
sharingReview: 'not_applicable',
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
id: 'risk-calibration.detailed-private-file.v3',
|
|
568
|
+
classification: 'analyst_authored_sensitive',
|
|
569
|
+
projection: 'review_output',
|
|
570
|
+
includedCategories: ['target-identifiers', 'reviewed-dispositions', 'review-reasons', 'interoperability-tags', 'scores', 'bands', 'factors', 'aggregate-metrics'],
|
|
571
|
+
excludedCategories: ['analyst-notes', 'raw-source-payloads', 'provider-payloads', 'contacts', 'credentials', 'cookies', 'sessions'],
|
|
572
|
+
notePolicy: 'discarded',
|
|
573
|
+
retention: 'operator_controlled_output',
|
|
574
|
+
network: 'none',
|
|
575
|
+
sharingReview: 'required',
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
id: 'risk-calibration.summary-private-file.v3',
|
|
579
|
+
classification: 'metadata_only',
|
|
580
|
+
projection: 'metadata_only',
|
|
581
|
+
includedCategories: ['dataset-and-model-versions', 'aggregate-counts', 'fixed-threshold-metrics', 'confidence-intervals', 'bounded-strata', 'model-comparison-counts', 'zero-retention-declaration'],
|
|
582
|
+
excludedCategories: ['target-identifiers', 'record-identifiers', 'reviewed-dispositions', 'review-reasons', 'records', 'evidence', 'factors'],
|
|
583
|
+
notePolicy: 'not_applicable',
|
|
584
|
+
retention: 'operator_controlled_output',
|
|
585
|
+
network: 'none',
|
|
586
|
+
sharingReview: 'not_applicable',
|
|
587
|
+
},
|
|
588
|
+
],
|
|
589
|
+
expiryProfiles: [{
|
|
590
|
+
id: 'risk-calibration.expiry.not-applicable.v1',
|
|
591
|
+
field: null,
|
|
592
|
+
anchor: null,
|
|
593
|
+
handling: 'not_applicable',
|
|
594
|
+
phase: 'not_applicable',
|
|
595
|
+
maximumLifetimeDays: null,
|
|
596
|
+
}],
|
|
597
|
+
consumerEdges: [
|
|
598
|
+
{
|
|
599
|
+
id: 'risk-calibration.browser-dataset-export',
|
|
600
|
+
plane: 'browser',
|
|
601
|
+
operation: 'build-dataset-download',
|
|
602
|
+
acceptedContracts: [],
|
|
603
|
+
emittedContract: { schema: RISK_CALIBRATION_DATASET_SCHEMA, version: RISK_CALIBRATION_DATASET_VERSION, discriminator: null },
|
|
604
|
+
shapeIds: ['risk-calibration.dataset.v2'],
|
|
605
|
+
boundProfileIds: ['risk-calibration.dataset-bounds.v2'],
|
|
606
|
+
hookIds: ['risk-calibration.browser.build-dataset', 'risk-calibration.browser.serialise-dataset'],
|
|
607
|
+
serialisationProfileId: 'risk-calibration.dataset-json.v2',
|
|
608
|
+
privacyProfileId: 'risk-calibration.dataset-export.v2',
|
|
609
|
+
expiryPolicyId: 'risk-calibration.expiry.not-applicable.v1',
|
|
610
|
+
requestMode: 'none',
|
|
611
|
+
retentionEffect: 'deliberate_local_file',
|
|
612
|
+
bindingState: 'declared_unenforced',
|
|
613
|
+
policyState: 'current',
|
|
614
|
+
},
|
|
615
|
+
{
|
|
616
|
+
id: 'risk-calibration.cli-detailed-json-stdout',
|
|
617
|
+
plane: 'cli',
|
|
618
|
+
operation: 'build-detailed-json-stdout',
|
|
619
|
+
acceptedContracts: [{ schema: RISK_CALIBRATION_DATASET_SCHEMA, versions: [RISK_CALIBRATION_DATASET_VERSION], mode: 'direct', discriminator: null }],
|
|
620
|
+
emittedContract: { schema: RISK_CALIBRATION_REPORT_SCHEMA, version: RISK_CALIBRATION_REPORT_VERSION, discriminator: { path: '$.mode', value: 'detailed' } },
|
|
621
|
+
shapeIds: ['risk-calibration.dataset.v2', 'risk-calibration.report.v3-detailed'],
|
|
622
|
+
boundProfileIds: ['risk-calibration.dataset-bounds.v2', 'risk-calibration.report-detailed.v3', 'risk-calibration.report-detailed-json-output.v3'],
|
|
623
|
+
hookIds: ['risk-calibration.cli.parse-dataset', 'risk-calibration.cli.build-detailed', 'risk-calibration.cli.serialise-report'],
|
|
624
|
+
serialisationProfileId: 'risk-calibration.report-json.v3',
|
|
625
|
+
privacyProfileId: 'risk-calibration.detailed-output.v3',
|
|
626
|
+
expiryPolicyId: 'risk-calibration.expiry.not-applicable.v1',
|
|
627
|
+
requestMode: 'none',
|
|
628
|
+
retentionEffect: 'transient_report',
|
|
629
|
+
bindingState: 'declared_unenforced',
|
|
630
|
+
policyState: 'current',
|
|
631
|
+
},
|
|
632
|
+
{
|
|
633
|
+
id: 'risk-calibration.cli-dataset-read',
|
|
634
|
+
plane: 'cli',
|
|
635
|
+
operation: 'read-dataset',
|
|
636
|
+
acceptedContracts: [{ schema: RISK_CALIBRATION_DATASET_SCHEMA, versions: [RISK_CALIBRATION_DATASET_VERSION], mode: 'direct', discriminator: null }],
|
|
637
|
+
emittedContract: null,
|
|
638
|
+
shapeIds: ['risk-calibration.dataset.v2'],
|
|
639
|
+
boundProfileIds: ['risk-calibration.dataset-bounds.v2'],
|
|
640
|
+
hookIds: ['risk-calibration.cli.parse-dataset'],
|
|
641
|
+
serialisationProfileId: null,
|
|
642
|
+
privacyProfileId: 'risk-calibration.dataset-input.v2',
|
|
643
|
+
expiryPolicyId: 'risk-calibration.expiry.not-applicable.v1',
|
|
644
|
+
requestMode: 'none',
|
|
645
|
+
retentionEffect: 'deliberate_local_file',
|
|
646
|
+
bindingState: 'declared_unenforced',
|
|
647
|
+
policyState: 'current',
|
|
648
|
+
},
|
|
649
|
+
{
|
|
650
|
+
id: 'risk-calibration.cli-detailed-terminal-stdout',
|
|
651
|
+
plane: 'cli',
|
|
652
|
+
operation: 'build-detailed-terminal-stdout',
|
|
653
|
+
acceptedContracts: [{ schema: RISK_CALIBRATION_DATASET_SCHEMA, versions: [RISK_CALIBRATION_DATASET_VERSION], mode: 'direct', discriminator: null }],
|
|
654
|
+
emittedContract: { schema: RISK_CALIBRATION_REPORT_SCHEMA, version: RISK_CALIBRATION_REPORT_VERSION, discriminator: { path: '$.mode', value: 'detailed' } },
|
|
655
|
+
shapeIds: ['risk-calibration.dataset.v2', 'risk-calibration.report.v3-detailed'],
|
|
656
|
+
boundProfileIds: ['risk-calibration.dataset-bounds.v2', 'risk-calibration.report-detailed.v3'],
|
|
657
|
+
hookIds: ['risk-calibration.cli.parse-dataset', 'risk-calibration.cli.build-detailed', 'risk-calibration.cli.format-terminal'],
|
|
658
|
+
serialisationProfileId: null,
|
|
659
|
+
privacyProfileId: 'risk-calibration.detailed-output.v3',
|
|
660
|
+
expiryPolicyId: 'risk-calibration.expiry.not-applicable.v1',
|
|
661
|
+
requestMode: 'none',
|
|
662
|
+
retentionEffect: 'transient_report',
|
|
663
|
+
bindingState: 'declared_unenforced',
|
|
664
|
+
policyState: 'current',
|
|
665
|
+
},
|
|
666
|
+
{
|
|
667
|
+
id: 'risk-calibration.cli-summary-json-stdout',
|
|
668
|
+
plane: 'cli',
|
|
669
|
+
operation: 'build-summary-json-stdout',
|
|
670
|
+
acceptedContracts: [{ schema: RISK_CALIBRATION_DATASET_SCHEMA, versions: [RISK_CALIBRATION_DATASET_VERSION], mode: 'direct', discriminator: null }],
|
|
671
|
+
emittedContract: { schema: RISK_CALIBRATION_REPORT_SCHEMA, version: RISK_CALIBRATION_REPORT_VERSION, discriminator: { path: '$.mode', value: 'summary' } },
|
|
672
|
+
shapeIds: ['risk-calibration.dataset.v2', 'risk-calibration.report.v3-summary'],
|
|
673
|
+
boundProfileIds: ['risk-calibration.dataset-bounds.v2', 'risk-calibration.report-summary-output.v3'],
|
|
674
|
+
hookIds: ['risk-calibration.cli.parse-dataset', 'risk-calibration.cli.build-detailed', 'risk-calibration.shared.build-summary', 'risk-calibration.cli.serialise-report'],
|
|
675
|
+
serialisationProfileId: 'risk-calibration.report-json.v3',
|
|
676
|
+
privacyProfileId: 'risk-calibration.summary-output.v3',
|
|
677
|
+
expiryPolicyId: 'risk-calibration.expiry.not-applicable.v1',
|
|
678
|
+
requestMode: 'none',
|
|
679
|
+
retentionEffect: 'transient_report',
|
|
680
|
+
bindingState: 'declared_unenforced',
|
|
681
|
+
policyState: 'current',
|
|
682
|
+
},
|
|
683
|
+
{
|
|
684
|
+
id: 'risk-calibration.cli-detailed-json-file',
|
|
685
|
+
plane: 'cli',
|
|
686
|
+
operation: 'build-detailed-json-file',
|
|
687
|
+
acceptedContracts: [{ schema: RISK_CALIBRATION_DATASET_SCHEMA, versions: [RISK_CALIBRATION_DATASET_VERSION], mode: 'direct', discriminator: null }],
|
|
688
|
+
emittedContract: { schema: RISK_CALIBRATION_REPORT_SCHEMA, version: RISK_CALIBRATION_REPORT_VERSION, discriminator: { path: '$.mode', value: 'detailed' } },
|
|
689
|
+
shapeIds: ['risk-calibration.dataset.v2', 'risk-calibration.report.v3-detailed'],
|
|
690
|
+
boundProfileIds: ['risk-calibration.dataset-bounds.v2', 'risk-calibration.report-detailed.v3', 'risk-calibration.report-detailed-json-output.v3', 'risk-calibration.cli-private-output.v1'],
|
|
691
|
+
hookIds: ['risk-calibration.cli.parse-dataset', 'risk-calibration.cli.build-detailed', 'risk-calibration.cli.serialise-report', 'risk-calibration.cli.write-private-file'],
|
|
692
|
+
serialisationProfileId: 'risk-calibration.report-json.v3',
|
|
693
|
+
privacyProfileId: 'risk-calibration.detailed-private-file.v3',
|
|
694
|
+
expiryPolicyId: 'risk-calibration.expiry.not-applicable.v1',
|
|
695
|
+
requestMode: 'none',
|
|
696
|
+
retentionEffect: 'operator_controlled_output',
|
|
697
|
+
bindingState: 'declared_unenforced',
|
|
698
|
+
policyState: 'current',
|
|
699
|
+
},
|
|
700
|
+
{
|
|
701
|
+
id: 'risk-calibration.cli-detailed-terminal-file',
|
|
702
|
+
plane: 'cli',
|
|
703
|
+
operation: 'build-detailed-terminal-file',
|
|
704
|
+
acceptedContracts: [{ schema: RISK_CALIBRATION_DATASET_SCHEMA, versions: [RISK_CALIBRATION_DATASET_VERSION], mode: 'direct', discriminator: null }],
|
|
705
|
+
emittedContract: { schema: RISK_CALIBRATION_REPORT_SCHEMA, version: RISK_CALIBRATION_REPORT_VERSION, discriminator: { path: '$.mode', value: 'detailed' } },
|
|
706
|
+
shapeIds: ['risk-calibration.dataset.v2', 'risk-calibration.report.v3-detailed'],
|
|
707
|
+
boundProfileIds: ['risk-calibration.dataset-bounds.v2', 'risk-calibration.report-detailed.v3', 'risk-calibration.cli-private-output.v1'],
|
|
708
|
+
hookIds: ['risk-calibration.cli.parse-dataset', 'risk-calibration.cli.build-detailed', 'risk-calibration.cli.format-terminal', 'risk-calibration.cli.write-private-file'],
|
|
709
|
+
serialisationProfileId: null,
|
|
710
|
+
privacyProfileId: 'risk-calibration.detailed-private-file.v3',
|
|
711
|
+
expiryPolicyId: 'risk-calibration.expiry.not-applicable.v1',
|
|
712
|
+
requestMode: 'none',
|
|
713
|
+
retentionEffect: 'operator_controlled_output',
|
|
714
|
+
bindingState: 'declared_unenforced',
|
|
715
|
+
policyState: 'current',
|
|
716
|
+
},
|
|
717
|
+
{
|
|
718
|
+
id: 'risk-calibration.cli-summary-json-file',
|
|
719
|
+
plane: 'cli',
|
|
720
|
+
operation: 'build-summary-json-file',
|
|
721
|
+
acceptedContracts: [{ schema: RISK_CALIBRATION_DATASET_SCHEMA, versions: [RISK_CALIBRATION_DATASET_VERSION], mode: 'direct', discriminator: null }],
|
|
722
|
+
emittedContract: { schema: RISK_CALIBRATION_REPORT_SCHEMA, version: RISK_CALIBRATION_REPORT_VERSION, discriminator: { path: '$.mode', value: 'summary' } },
|
|
723
|
+
shapeIds: ['risk-calibration.dataset.v2', 'risk-calibration.report.v3-summary'],
|
|
724
|
+
boundProfileIds: ['risk-calibration.dataset-bounds.v2', 'risk-calibration.report-summary-output.v3', 'risk-calibration.cli-private-output.v1'],
|
|
725
|
+
hookIds: ['risk-calibration.cli.parse-dataset', 'risk-calibration.cli.build-detailed', 'risk-calibration.shared.build-summary', 'risk-calibration.cli.serialise-report', 'risk-calibration.cli.write-private-file'],
|
|
726
|
+
serialisationProfileId: 'risk-calibration.report-json.v3',
|
|
727
|
+
privacyProfileId: 'risk-calibration.summary-private-file.v3',
|
|
728
|
+
expiryPolicyId: 'risk-calibration.expiry.not-applicable.v1',
|
|
729
|
+
requestMode: 'none',
|
|
730
|
+
retentionEffect: 'operator_controlled_output',
|
|
731
|
+
bindingState: 'declared_unenforced',
|
|
732
|
+
policyState: 'current',
|
|
733
|
+
},
|
|
734
|
+
{
|
|
735
|
+
id: 'risk-calibration.browser-summary-review',
|
|
736
|
+
plane: 'browser',
|
|
737
|
+
operation: 'review-summary',
|
|
738
|
+
acceptedContracts: [{
|
|
739
|
+
schema: RISK_CALIBRATION_REPORT_SCHEMA,
|
|
740
|
+
versions: [RISK_CALIBRATION_REPORT_VERSION],
|
|
741
|
+
mode: 'direct',
|
|
742
|
+
discriminator: { path: '$.mode', values: ['summary'] },
|
|
743
|
+
}],
|
|
744
|
+
emittedContract: null,
|
|
745
|
+
shapeIds: ['risk-calibration.report.v3-summary'],
|
|
746
|
+
boundProfileIds: ['risk-calibration.report-summary-input.v3'],
|
|
747
|
+
hookIds: ['risk-calibration.shared.parse-summary', 'risk-calibration.browser.read-summary'],
|
|
748
|
+
serialisationProfileId: null,
|
|
749
|
+
privacyProfileId: 'risk-calibration.summary-output.v3',
|
|
750
|
+
expiryPolicyId: 'risk-calibration.expiry.not-applicable.v1',
|
|
751
|
+
requestMode: 'none',
|
|
752
|
+
retentionEffect: 'transient_report',
|
|
753
|
+
bindingState: 'declared_unenforced',
|
|
754
|
+
policyState: 'current',
|
|
755
|
+
},
|
|
756
|
+
],
|
|
757
|
+
consumerRelationships: [],
|
|
758
|
+
},
|
|
759
|
+
});
|
|
760
|
+
class RiskCalibrationJsonBudgetError extends RangeError {
|
|
761
|
+
}
|
|
762
|
+
class RiskCalibrationJsonShapeError extends TypeError {
|
|
763
|
+
}
|
|
764
|
+
const RISK_CALIBRATION_CONTROL_RE = /[\u0000-\u001f\u007f]/u;
|
|
765
|
+
function literal(value) {
|
|
766
|
+
return { kind: 'literal', value };
|
|
767
|
+
}
|
|
768
|
+
function boundedText(maximum, values = null, minimum = 1, validate = null) {
|
|
769
|
+
return { kind: 'string', minimum, maximum, values, controlFree: true, nonBlank: true, validate };
|
|
770
|
+
}
|
|
771
|
+
const RISK_CALIBRATION_ASCII_LABEL_RE = /^[a-z0-9-]+$/iu;
|
|
772
|
+
function validRiskCalibrationDomain(value) {
|
|
773
|
+
const normalised = value.trim().toLowerCase().replace(/\.$/u, '');
|
|
774
|
+
if (!normalised || normalised.length > MAX_RISK_CALIBRATION_DOMAIN_LENGTH)
|
|
775
|
+
return false;
|
|
776
|
+
const labels = normalised.split('.');
|
|
777
|
+
if (labels.length < 2 || labels.some((label) => (!label
|
|
778
|
+
|| label.length > 63
|
|
779
|
+
|| label.startsWith('-')
|
|
780
|
+
|| label.endsWith('-')
|
|
781
|
+
|| !RISK_CALIBRATION_ASCII_LABEL_RE.test(label))))
|
|
782
|
+
return false;
|
|
783
|
+
const ipv4Parts = normalised.split('.');
|
|
784
|
+
return ipv4Parts.length !== 4 || !ipv4Parts.every((part) => (/^(?:0|[1-9]\d{0,2})$/u.test(part) && Number(part) <= 255));
|
|
785
|
+
}
|
|
786
|
+
function canonicalRiskCalibrationIdentifier(value) {
|
|
787
|
+
return value === value.trim();
|
|
788
|
+
}
|
|
789
|
+
function boundedNumber(minimum, maximum, integer = false, values = null) {
|
|
790
|
+
return { kind: 'number', minimum, maximum, integer, values };
|
|
791
|
+
}
|
|
792
|
+
function nullable(value) {
|
|
793
|
+
return { kind: 'nullable', value };
|
|
794
|
+
}
|
|
795
|
+
function arrayOf(item, minimum, maximum) {
|
|
796
|
+
return { kind: 'array', item, minimum, maximum };
|
|
797
|
+
}
|
|
798
|
+
function tupleOf(items) {
|
|
799
|
+
return { kind: 'tuple', items };
|
|
800
|
+
}
|
|
801
|
+
function objectOf(fields) {
|
|
802
|
+
return { kind: 'object', fields };
|
|
803
|
+
}
|
|
804
|
+
function consumeRiskCalibrationJsonBytes(budget, addition) {
|
|
805
|
+
if (!Number.isSafeInteger(addition)
|
|
806
|
+
|| addition < 0
|
|
807
|
+
|| budget.bytes > budget.maximum - addition) {
|
|
808
|
+
throw new RiskCalibrationJsonBudgetError();
|
|
809
|
+
}
|
|
810
|
+
budget.bytes += addition;
|
|
811
|
+
}
|
|
812
|
+
function consumeRiskCalibrationJsonString(budget, value) {
|
|
813
|
+
consumeRiskCalibrationJsonBytes(budget, 2);
|
|
814
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
815
|
+
const code = value.charCodeAt(index);
|
|
816
|
+
let addition = 0;
|
|
817
|
+
if (code === 0x22 || code === 0x5c)
|
|
818
|
+
addition = 2;
|
|
819
|
+
else if (code <= 0x1f) {
|
|
820
|
+
addition = code === 0x08
|
|
821
|
+
|| code === 0x09
|
|
822
|
+
|| code === 0x0a
|
|
823
|
+
|| code === 0x0c
|
|
824
|
+
|| code === 0x0d
|
|
825
|
+
? 2
|
|
826
|
+
: 6;
|
|
827
|
+
}
|
|
828
|
+
else if (code >= 0xd800 && code <= 0xdbff) {
|
|
829
|
+
const next = value.charCodeAt(index + 1);
|
|
830
|
+
if (next >= 0xdc00 && next <= 0xdfff) {
|
|
831
|
+
addition = 4;
|
|
832
|
+
index += 1;
|
|
833
|
+
}
|
|
834
|
+
else
|
|
835
|
+
addition = 6;
|
|
836
|
+
}
|
|
837
|
+
else if (code >= 0xdc00 && code <= 0xdfff)
|
|
838
|
+
addition = 6;
|
|
839
|
+
else if (code <= 0x7f)
|
|
840
|
+
addition = 1;
|
|
841
|
+
else if (code <= 0x7ff)
|
|
842
|
+
addition = 2;
|
|
843
|
+
else
|
|
844
|
+
addition = 3;
|
|
845
|
+
consumeRiskCalibrationJsonBytes(budget, addition);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
function reflected(label, operation) {
|
|
849
|
+
try {
|
|
850
|
+
return operation();
|
|
851
|
+
}
|
|
852
|
+
catch {
|
|
853
|
+
throw new RiskCalibrationJsonShapeError(`${label} must use stable ordinary data fields.`);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
function sameOwnKeys(left, right) {
|
|
857
|
+
return left.length === right.length && left.every((key, index) => Object.is(key, right[index]));
|
|
858
|
+
}
|
|
859
|
+
function sameDataDescriptor(left, right) {
|
|
860
|
+
return Boolean(right
|
|
861
|
+
&& 'value' in left
|
|
862
|
+
&& 'value' in right
|
|
863
|
+
&& left.enumerable === right.enumerable
|
|
864
|
+
&& left.configurable === right.configurable
|
|
865
|
+
&& left.writable === right.writable
|
|
866
|
+
&& Object.is(left.value, right.value));
|
|
867
|
+
}
|
|
868
|
+
function copyRiskCalibrationDataProperty(source, key, spec, label, budget, depth) {
|
|
869
|
+
const descriptor = reflected(label, () => Object.getOwnPropertyDescriptor(source, key));
|
|
870
|
+
if (!descriptor || !descriptor.enumerable || !('value' in descriptor)) {
|
|
871
|
+
throw new RiskCalibrationJsonShapeError(`${label} must be an ordinary enumerable data field.`);
|
|
872
|
+
}
|
|
873
|
+
const result = copyRiskCalibrationJsonValue(descriptor.value, spec, label, budget, depth);
|
|
874
|
+
const repeated = reflected(label, () => Object.getOwnPropertyDescriptor(source, key));
|
|
875
|
+
if (!sameDataDescriptor(descriptor, repeated)) {
|
|
876
|
+
throw new RiskCalibrationJsonShapeError(`${label} changed while it was being snapshotted.`);
|
|
877
|
+
}
|
|
878
|
+
return result;
|
|
879
|
+
}
|
|
880
|
+
function copyRiskCalibrationJsonObject(value, spec, label, budget, depth) {
|
|
881
|
+
if (!value || typeof value !== 'object' || reflected(label, () => Array.isArray(value))) {
|
|
882
|
+
throw new RiskCalibrationJsonShapeError(`${label} must be an ordinary object.`);
|
|
883
|
+
}
|
|
884
|
+
const source = value;
|
|
885
|
+
const prototype = reflected(label, () => Object.getPrototypeOf(source));
|
|
886
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
887
|
+
throw new RiskCalibrationJsonShapeError(`${label} must be an ordinary object.`);
|
|
888
|
+
}
|
|
889
|
+
const ownKeys = reflected(label, () => Reflect.ownKeys(source));
|
|
890
|
+
const allowed = new Map(spec.fields.map((field) => [field.key, field]));
|
|
891
|
+
if (ownKeys.some((key) => typeof key !== 'string' || !allowed.has(key))) {
|
|
892
|
+
throw new RiskCalibrationJsonShapeError(`${label} contains unsupported fields.`);
|
|
893
|
+
}
|
|
894
|
+
const present = new Set(ownKeys);
|
|
895
|
+
if (spec.fields.some((field) => !field.optional && !present.has(field.key))) {
|
|
896
|
+
throw new RiskCalibrationJsonShapeError(`${label} is missing required fields.`);
|
|
897
|
+
}
|
|
898
|
+
const selected = spec.fields.filter((field) => present.has(field.key));
|
|
899
|
+
consumeRiskCalibrationJsonBytes(budget, 1);
|
|
900
|
+
const result = {};
|
|
901
|
+
for (let index = 0; index < selected.length; index += 1) {
|
|
902
|
+
const field = selected[index];
|
|
903
|
+
if (index > 0)
|
|
904
|
+
consumeRiskCalibrationJsonBytes(budget, 1);
|
|
905
|
+
consumeRiskCalibrationJsonBytes(budget, 1 + ((depth + 1) * 2));
|
|
906
|
+
consumeRiskCalibrationJsonString(budget, field.key);
|
|
907
|
+
consumeRiskCalibrationJsonBytes(budget, 2);
|
|
908
|
+
result[field.key] = copyRiskCalibrationDataProperty(source, field.key, field.value, `${label}.${field.key}`, budget, depth + 1);
|
|
909
|
+
}
|
|
910
|
+
if (selected.length)
|
|
911
|
+
consumeRiskCalibrationJsonBytes(budget, 1 + (depth * 2) + 1);
|
|
912
|
+
else
|
|
913
|
+
consumeRiskCalibrationJsonBytes(budget, 1);
|
|
914
|
+
const repeatedPrototype = reflected(label, () => Object.getPrototypeOf(source));
|
|
915
|
+
const repeatedKeys = reflected(label, () => Reflect.ownKeys(source));
|
|
916
|
+
if (repeatedPrototype !== prototype || !sameOwnKeys(ownKeys, repeatedKeys)) {
|
|
917
|
+
throw new RiskCalibrationJsonShapeError(`${label} changed while it was being snapshotted.`);
|
|
918
|
+
}
|
|
919
|
+
return Object.freeze(result);
|
|
920
|
+
}
|
|
921
|
+
function copyRiskCalibrationJsonArray(value, label, budget, depth, minimum, maximum, specAt) {
|
|
922
|
+
if (!reflected(label, () => Array.isArray(value))) {
|
|
923
|
+
throw new RiskCalibrationJsonShapeError(`${label} must be a bounded ordinary array.`);
|
|
924
|
+
}
|
|
925
|
+
const source = value;
|
|
926
|
+
const prototype = reflected(label, () => Object.getPrototypeOf(source));
|
|
927
|
+
if (prototype !== Array.prototype) {
|
|
928
|
+
throw new RiskCalibrationJsonShapeError(`${label} must be a bounded ordinary array.`);
|
|
929
|
+
}
|
|
930
|
+
const lengthDescriptor = reflected(label, () => Object.getOwnPropertyDescriptor(source, 'length'));
|
|
931
|
+
const length = lengthDescriptor && 'value' in lengthDescriptor ? lengthDescriptor.value : null;
|
|
932
|
+
if (!lengthDescriptor
|
|
933
|
+
|| lengthDescriptor.enumerable
|
|
934
|
+
|| !Number.isSafeInteger(length)
|
|
935
|
+
|| Number(length) < minimum
|
|
936
|
+
|| Number(length) > maximum) {
|
|
937
|
+
throw new RiskCalibrationJsonShapeError(`${label} must contain between ${minimum} and ${maximum} entries.`);
|
|
938
|
+
}
|
|
939
|
+
const boundedLength = Number(length);
|
|
940
|
+
const ownKeys = reflected(label, () => Reflect.ownKeys(source));
|
|
941
|
+
if (ownKeys.length !== boundedLength + 1
|
|
942
|
+
|| ownKeys.at(-1) !== 'length'
|
|
943
|
+
|| ownKeys.slice(0, -1).some((key, index) => key !== String(index))) {
|
|
944
|
+
throw new RiskCalibrationJsonShapeError(`${label} must be a dense ordinary array without extra fields.`);
|
|
945
|
+
}
|
|
946
|
+
consumeRiskCalibrationJsonBytes(budget, 1);
|
|
947
|
+
const result = [];
|
|
948
|
+
for (let index = 0; index < boundedLength; index += 1) {
|
|
949
|
+
if (index > 0)
|
|
950
|
+
consumeRiskCalibrationJsonBytes(budget, 1);
|
|
951
|
+
consumeRiskCalibrationJsonBytes(budget, 1 + ((depth + 1) * 2));
|
|
952
|
+
result.push(copyRiskCalibrationDataProperty(source, String(index), specAt(index), `${label}[${index}]`, budget, depth + 1));
|
|
953
|
+
}
|
|
954
|
+
if (boundedLength)
|
|
955
|
+
consumeRiskCalibrationJsonBytes(budget, 1 + (depth * 2) + 1);
|
|
956
|
+
else
|
|
957
|
+
consumeRiskCalibrationJsonBytes(budget, 1);
|
|
958
|
+
const repeatedPrototype = reflected(label, () => Object.getPrototypeOf(source));
|
|
959
|
+
const repeatedLength = reflected(label, () => Object.getOwnPropertyDescriptor(source, 'length'));
|
|
960
|
+
const repeatedKeys = reflected(label, () => Reflect.ownKeys(source));
|
|
961
|
+
if (repeatedPrototype !== prototype
|
|
962
|
+
|| !sameDataDescriptor(lengthDescriptor, repeatedLength)
|
|
963
|
+
|| !sameOwnKeys(ownKeys, repeatedKeys)) {
|
|
964
|
+
throw new RiskCalibrationJsonShapeError(`${label} changed while it was being snapshotted.`);
|
|
965
|
+
}
|
|
966
|
+
return Object.freeze(result);
|
|
967
|
+
}
|
|
968
|
+
function copyRiskCalibrationJsonValue(value, spec, label, budget, depth) {
|
|
969
|
+
if (spec.kind === 'literal') {
|
|
970
|
+
if (!Object.is(value, spec.value)) {
|
|
971
|
+
throw new RiskCalibrationJsonShapeError(`${label} has an unsupported value.`);
|
|
972
|
+
}
|
|
973
|
+
if (typeof value === 'string')
|
|
974
|
+
consumeRiskCalibrationJsonString(budget, value);
|
|
975
|
+
else if (value === null)
|
|
976
|
+
consumeRiskCalibrationJsonBytes(budget, 4);
|
|
977
|
+
else if (typeof value === 'boolean')
|
|
978
|
+
consumeRiskCalibrationJsonBytes(budget, value ? 4 : 5);
|
|
979
|
+
else
|
|
980
|
+
consumeRiskCalibrationJsonBytes(budget, String(value).length);
|
|
981
|
+
return value;
|
|
982
|
+
}
|
|
983
|
+
if (spec.kind === 'string') {
|
|
984
|
+
if (typeof value !== 'string'
|
|
985
|
+
|| value.length < spec.minimum
|
|
986
|
+
|| value.length > spec.maximum
|
|
987
|
+
|| (spec.nonBlank && !value.trim())
|
|
988
|
+
|| (spec.controlFree && RISK_CALIBRATION_CONTROL_RE.test(value))
|
|
989
|
+
|| (spec.values && !spec.values.includes(value))
|
|
990
|
+
|| (spec.validate && !spec.validate(value))) {
|
|
991
|
+
throw new RiskCalibrationJsonShapeError(`${label} must be bounded supported text.`);
|
|
992
|
+
}
|
|
993
|
+
consumeRiskCalibrationJsonString(budget, value);
|
|
994
|
+
return value;
|
|
995
|
+
}
|
|
996
|
+
if (spec.kind === 'number') {
|
|
997
|
+
if (typeof value !== 'number'
|
|
998
|
+
|| !Number.isFinite(value)
|
|
999
|
+
|| value < spec.minimum
|
|
1000
|
+
|| value > spec.maximum
|
|
1001
|
+
|| (spec.integer && !Number.isSafeInteger(value))
|
|
1002
|
+
|| (spec.values && !spec.values.includes(value))) {
|
|
1003
|
+
throw new RiskCalibrationJsonShapeError(`${label} must be a bounded finite number.`);
|
|
1004
|
+
}
|
|
1005
|
+
const normalised = Object.is(value, -0) ? 0 : value;
|
|
1006
|
+
consumeRiskCalibrationJsonBytes(budget, JSON.stringify(normalised).length);
|
|
1007
|
+
return normalised;
|
|
1008
|
+
}
|
|
1009
|
+
if (spec.kind === 'boolean') {
|
|
1010
|
+
if (typeof value !== 'boolean') {
|
|
1011
|
+
throw new RiskCalibrationJsonShapeError(`${label} must be true or false.`);
|
|
1012
|
+
}
|
|
1013
|
+
consumeRiskCalibrationJsonBytes(budget, value ? 4 : 5);
|
|
1014
|
+
return value;
|
|
1015
|
+
}
|
|
1016
|
+
if (spec.kind === 'nullable') {
|
|
1017
|
+
if (value === null) {
|
|
1018
|
+
consumeRiskCalibrationJsonBytes(budget, 4);
|
|
1019
|
+
return null;
|
|
1020
|
+
}
|
|
1021
|
+
return copyRiskCalibrationJsonValue(value, spec.value, label, budget, depth);
|
|
1022
|
+
}
|
|
1023
|
+
if (spec.kind === 'array') {
|
|
1024
|
+
return copyRiskCalibrationJsonArray(value, label, budget, depth, spec.minimum, spec.maximum, () => spec.item);
|
|
1025
|
+
}
|
|
1026
|
+
if (spec.kind === 'tuple') {
|
|
1027
|
+
return copyRiskCalibrationJsonArray(value, label, budget, depth, spec.items.length, spec.items.length, (index) => spec.items[index]);
|
|
1028
|
+
}
|
|
1029
|
+
return copyRiskCalibrationJsonObject(value, spec, label, budget, depth);
|
|
1030
|
+
}
|
|
1031
|
+
const BOOLEAN_SPEC = { kind: 'boolean' };
|
|
1032
|
+
const COUNT_SPEC = boundedNumber(0, MAX_RISK_CALIBRATION_RECORDS, true);
|
|
1033
|
+
const RATIO_SPEC = nullable(boundedNumber(0, 1));
|
|
1034
|
+
const INTERVAL_SPEC = nullable(objectOf([
|
|
1035
|
+
{ key: 'lower', value: boundedNumber(0, 1) },
|
|
1036
|
+
{ key: 'upper', value: boundedNumber(0, 1) },
|
|
1037
|
+
]));
|
|
1038
|
+
const CONFIDENCE_SPEC = objectOf([
|
|
1039
|
+
{ key: 'precision', value: INTERVAL_SPEC },
|
|
1040
|
+
{ key: 'recall', value: INTERVAL_SPEC },
|
|
1041
|
+
{ key: 'specificity', value: INTERVAL_SPEC },
|
|
1042
|
+
]);
|
|
1043
|
+
const METRIC_SPEC = objectOf([
|
|
1044
|
+
{ key: 'threshold', value: boundedNumber(0, 100, true, [40, 50, 60, 70, 80, 90]) },
|
|
1045
|
+
{ key: 'truePositive', value: COUNT_SPEC },
|
|
1046
|
+
{ key: 'falsePositive', value: COUNT_SPEC },
|
|
1047
|
+
{ key: 'trueNegative', value: COUNT_SPEC },
|
|
1048
|
+
{ key: 'falseNegative', value: COUNT_SPEC },
|
|
1049
|
+
{ key: 'precision', value: RATIO_SPEC },
|
|
1050
|
+
{ key: 'recall', value: RATIO_SPEC },
|
|
1051
|
+
{ key: 'specificity', value: RATIO_SPEC },
|
|
1052
|
+
{ key: 'falsePositiveRate', value: RATIO_SPEC },
|
|
1053
|
+
{ key: 'f1', value: RATIO_SPEC },
|
|
1054
|
+
{ key: 'balancedAccuracy', value: RATIO_SPEC },
|
|
1055
|
+
{ key: 'confidence95', value: CONFIDENCE_SPEC },
|
|
1056
|
+
]);
|
|
1057
|
+
const SCORE_BANDS_SPEC = objectOf([
|
|
1058
|
+
{ key: 'not_scored', value: COUNT_SPEC },
|
|
1059
|
+
{ key: '0_39', value: COUNT_SPEC },
|
|
1060
|
+
{ key: '40_69', value: COUNT_SPEC },
|
|
1061
|
+
{ key: '70_100', value: COUNT_SPEC },
|
|
1062
|
+
]);
|
|
1063
|
+
const REPORT_SUMMARY_SPEC = objectOf([
|
|
1064
|
+
{ key: 'total', value: COUNT_SPEC },
|
|
1065
|
+
{ key: 'positive', value: COUNT_SPEC },
|
|
1066
|
+
{ key: 'negative', value: COUNT_SPEC },
|
|
1067
|
+
{ key: 'excluded', value: COUNT_SPEC },
|
|
1068
|
+
{ key: 'scoreBands', value: SCORE_BANDS_SPEC },
|
|
1069
|
+
]);
|
|
1070
|
+
const REPORT_DATASET_SPEC = objectOf([
|
|
1071
|
+
{ key: 'schema', value: literal(RISK_CALIBRATION_DATASET_SCHEMA) },
|
|
1072
|
+
{ key: 'version', value: boundedNumber(1, RISK_CALIBRATION_DATASET_VERSION, true, SUPPORTED_RISK_CALIBRATION_DATASET_VERSIONS) },
|
|
1073
|
+
{ key: 'recordCount', value: boundedNumber(1, MAX_RISK_CALIBRATION_RECORDS, true) },
|
|
1074
|
+
]);
|
|
1075
|
+
const STRATUM_SPEC = objectOf([
|
|
1076
|
+
{ key: 'dimension', value: boundedText(MAX_RISK_CALIBRATION_REVIEW_REASON_LENGTH, ['review_reason', 'scan_depth']) },
|
|
1077
|
+
{ key: 'value', value: boundedText(MAX_RISK_CALIBRATION_REVIEW_REASON_LENGTH) },
|
|
1078
|
+
{ key: 'sampleCount', value: COUNT_SPEC },
|
|
1079
|
+
{ key: 'insufficientSample', value: BOOLEAN_SPEC },
|
|
1080
|
+
{ key: 'metrics', value: METRIC_SPEC },
|
|
1081
|
+
]);
|
|
1082
|
+
const MODEL_COMPARISON_SPEC = objectOf([
|
|
1083
|
+
{ key: 'available', value: BOOLEAN_SPEC },
|
|
1084
|
+
{ key: 'previousModelVersion', value: nullable(boundedNumber(1, MAX_RISK_CALIBRATION_MODEL_VERSION, true)) },
|
|
1085
|
+
{ key: 'currentModelVersion', value: boundedNumber(1, MAX_RISK_CALIBRATION_MODEL_VERSION, true) },
|
|
1086
|
+
{ key: 'scoresChanged', value: COUNT_SPEC },
|
|
1087
|
+
{ key: 'bandsChanged', value: COUNT_SPEC },
|
|
1088
|
+
{ key: 'thresholdClassificationsChanged', value: COUNT_SPEC },
|
|
1089
|
+
]);
|
|
1090
|
+
const INTERPRETATION_SPEC = objectOf([
|
|
1091
|
+
{ key: 'authority', value: literal('analyst_context_only') },
|
|
1092
|
+
{ key: 'statement', value: boundedText(1_024) },
|
|
1093
|
+
{ key: 'automaticTuning', value: literal(false) },
|
|
1094
|
+
{ key: 'networkRequests', value: literal(false) },
|
|
1095
|
+
{ key: 'persisted', value: literal(false) },
|
|
1096
|
+
]);
|
|
1097
|
+
const FACTOR_SPEC = objectOf([
|
|
1098
|
+
{ key: 'family', value: boundedText(MAX_RISK_CALIBRATION_REVIEW_REASON_LENGTH, [
|
|
1099
|
+
'brand-presentation',
|
|
1100
|
+
'corroboration',
|
|
1101
|
+
'credential-lure',
|
|
1102
|
+
'domain-resemblance',
|
|
1103
|
+
'external-intelligence',
|
|
1104
|
+
'operational-support',
|
|
1105
|
+
'registration',
|
|
1106
|
+
]) },
|
|
1107
|
+
{ key: 'label', value: boundedText(MAX_RISK_CALIBRATION_STRING_LENGTH) },
|
|
1108
|
+
{ key: 'delta', value: boundedNumber(-100, 100) },
|
|
1109
|
+
]);
|
|
1110
|
+
const REPORT_RECORD_SPEC = objectOf([
|
|
1111
|
+
{ key: 'id', value: boundedText(MAX_RISK_CALIBRATION_RECORD_ID_LENGTH) },
|
|
1112
|
+
{ key: 'domain', value: boundedText(MAX_RISK_CALIBRATION_DOMAIN_LENGTH) },
|
|
1113
|
+
{ key: 'analystDisposition', value: boundedText(MAX_RISK_CALIBRATION_DISPOSITION_LENGTH, [
|
|
1114
|
+
'unreviewed', 'suspicious', 'confirmed_abuse', 'false_positive', 'expected', 'closed_no_action',
|
|
1115
|
+
]) },
|
|
1116
|
+
{ key: 'reviewReasonCode', value: nullable(boundedText(MAX_RISK_CALIBRATION_REVIEW_REASON_LENGTH)) },
|
|
1117
|
+
{ key: 'interoperabilityTags', value: arrayOf(boundedText(MAX_RISK_CALIBRATION_STRING_LENGTH), 0, 32) },
|
|
1118
|
+
{ key: 'metricClass', value: boundedText(16, ['positive', 'negative', 'excluded']) },
|
|
1119
|
+
{ key: 'includedInMetrics', value: BOOLEAN_SPEC },
|
|
1120
|
+
{ key: 'exclusionReason', value: nullable(boundedText(32, ['not_scored', 'contextual_disposition'])) },
|
|
1121
|
+
{ key: 'modelVersion', value: boundedNumber(1, MAX_RISK_CALIBRATION_MODEL_VERSION, true) },
|
|
1122
|
+
{ key: 'score', value: nullable(boundedNumber(0, 100, true)) },
|
|
1123
|
+
{ key: 'band', value: boundedText(16, ['not_scored', '0_39', '40_69', '70_100']) },
|
|
1124
|
+
{ key: 'factors', value: arrayOf(FACTOR_SPEC, 0, 32) },
|
|
1125
|
+
]);
|
|
1126
|
+
const REPORT_COMMON_FIELDS = [
|
|
1127
|
+
{ key: 'generatedAt', value: boundedText(MAX_RISK_CALIBRATION_TIMESTAMP_LENGTH) },
|
|
1128
|
+
{ key: 'dataset', value: REPORT_DATASET_SPEC },
|
|
1129
|
+
{ key: 'riskModelVersion', value: boundedNumber(1, MAX_RISK_CALIBRATION_MODEL_VERSION, true) },
|
|
1130
|
+
{ key: 'currentReviewThreshold', value: boundedNumber(0, 100, true, [40, 50, 60, 70, 80, 90]) },
|
|
1131
|
+
{ key: 'summary', value: REPORT_SUMMARY_SPEC },
|
|
1132
|
+
{ key: 'thresholds', value: arrayOf(METRIC_SPEC, 6, 6) },
|
|
1133
|
+
{ key: 'strata', value: arrayOf(STRATUM_SPEC, 0, MAX_RISK_CALIBRATION_SUMMARY_STRATA) },
|
|
1134
|
+
{ key: 'modelComparison', value: MODEL_COMPARISON_SPEC },
|
|
1135
|
+
];
|
|
1136
|
+
const DETAILED_REPORT_SPEC = objectOf([
|
|
1137
|
+
{ key: 'schema', value: literal(RISK_CALIBRATION_REPORT_SCHEMA) },
|
|
1138
|
+
{ key: 'version', value: literal(RISK_CALIBRATION_REPORT_VERSION) },
|
|
1139
|
+
{ key: 'mode', value: literal('detailed') },
|
|
1140
|
+
...REPORT_COMMON_FIELDS,
|
|
1141
|
+
{ key: 'records', value: arrayOf(REPORT_RECORD_SPEC, 1, MAX_RISK_CALIBRATION_RECORDS) },
|
|
1142
|
+
{ key: 'interpretation', value: INTERPRETATION_SPEC },
|
|
1143
|
+
]);
|
|
1144
|
+
const SUMMARY_REPORT_SPEC = objectOf([
|
|
1145
|
+
{ key: 'schema', value: literal(RISK_CALIBRATION_REPORT_SCHEMA) },
|
|
1146
|
+
{ key: 'version', value: literal(RISK_CALIBRATION_REPORT_VERSION) },
|
|
1147
|
+
{ key: 'mode', value: literal('summary') },
|
|
1148
|
+
...REPORT_COMMON_FIELDS,
|
|
1149
|
+
{ key: 'privacy', value: objectOf([
|
|
1150
|
+
{ key: 'targetsRetained', value: literal(0) },
|
|
1151
|
+
{ key: 'identifiersRetained', value: literal(0) },
|
|
1152
|
+
{ key: 'rawEvidenceRetained', value: literal(0) },
|
|
1153
|
+
]) },
|
|
1154
|
+
{ key: 'interpretation', value: INTERPRETATION_SPEC },
|
|
1155
|
+
]);
|
|
1156
|
+
const DATASET_EVIDENCE_SPEC = objectOf([
|
|
1157
|
+
{ key: 'availability', value: boundedText(MAX_RISK_CALIBRATION_AVAILABILITY_LENGTH, [
|
|
1158
|
+
'registered', 'for_sale', 'expiring', 'available', 'unknown', 'error',
|
|
1159
|
+
]) },
|
|
1160
|
+
{ key: 'activityStatus', value: boundedText(MAX_RISK_CALIBRATION_ACTIVITY_LENGTH, ['active', 'parked', 'unreachable', 'no_site']), optional: true },
|
|
1161
|
+
{ key: 'mutationTypes', value: arrayOf(boundedText(MAX_RISK_CALIBRATION_REVIEW_REASON_LENGTH, RISK_CALIBRATION_MUTATION_TYPES), 0, MAX_RISK_CALIBRATION_MUTATIONS), optional: true },
|
|
1162
|
+
{ key: 'domainAgeDays', value: boundedNumber(0, MAX_RISK_CALIBRATION_DOMAIN_AGE_DAYS), optional: true },
|
|
1163
|
+
{ key: 'faviconMatch', value: BOOLEAN_SPEC, optional: true },
|
|
1164
|
+
{ key: 'faviconNearMatch', value: BOOLEAN_SPEC, optional: true },
|
|
1165
|
+
{ key: 'reusesOfficialAssets', value: BOOLEAN_SPEC, optional: true },
|
|
1166
|
+
{ key: 'hasPasswordField', value: BOOLEAN_SPEC, optional: true },
|
|
1167
|
+
{ key: 'hasMx', value: BOOLEAN_SPEC, optional: true },
|
|
1168
|
+
{ key: 'hasSpf', value: BOOLEAN_SPEC, optional: true },
|
|
1169
|
+
{ key: 'hasDmarc', value: BOOLEAN_SPEC, optional: true },
|
|
1170
|
+
{ key: 'privacyProtected', value: BOOLEAN_SPEC, optional: true },
|
|
1171
|
+
{ key: 'phishingLanguageMatch', value: boundedText(MAX_RISK_CALIBRATION_STRING_LENGTH), optional: true },
|
|
1172
|
+
{ key: 'hasExternalFormAction', value: BOOLEAN_SPEC, optional: true },
|
|
1173
|
+
{ key: 'idnReferenceMatch', value: BOOLEAN_SPEC, optional: true },
|
|
1174
|
+
{ key: 'pageBaselineMatch', value: BOOLEAN_SPEC, optional: true },
|
|
1175
|
+
{ key: 'hasActiveBrandProfile', value: BOOLEAN_SPEC, optional: true },
|
|
1176
|
+
{ key: 'scanDepth', value: boundedText(MAX_RISK_CALIBRATION_SCAN_DEPTH_LENGTH, ['fast', 'deep']), optional: true },
|
|
1177
|
+
{ key: 'observedAt', value: boundedText(MAX_RISK_CALIBRATION_TIMESTAMP_LENGTH), optional: true },
|
|
1178
|
+
]);
|
|
1179
|
+
const DATASET_RECORD_SPEC = objectOf([
|
|
1180
|
+
{ key: 'id', value: boundedText(MAX_RISK_CALIBRATION_RECORD_ID_LENGTH, null, 1, canonicalRiskCalibrationIdentifier) },
|
|
1181
|
+
{ key: 'domain', value: boundedText(MAX_RISK_CALIBRATION_DOMAIN_LENGTH, null, 1, validRiskCalibrationDomain) },
|
|
1182
|
+
{ key: 'analystDisposition', value: boundedText(MAX_RISK_CALIBRATION_DISPOSITION_LENGTH, [
|
|
1183
|
+
'unreviewed', 'suspicious', 'confirmed_abuse', 'false_positive', 'expected', 'closed_no_action',
|
|
1184
|
+
]) },
|
|
1185
|
+
{ key: 'reviewReasonCode', value: boundedText(MAX_RISK_CALIBRATION_REVIEW_REASON_LENGTH, RISK_CALIBRATION_REVIEW_REASON_VALUES), optional: true },
|
|
1186
|
+
{ key: 'evidence', value: DATASET_EVIDENCE_SPEC },
|
|
1187
|
+
]);
|
|
1188
|
+
const DATASET_EXCLUSION_SPEC = objectOf([
|
|
1189
|
+
{ key: 'caseId', value: boundedText(MAX_RISK_CALIBRATION_RECORD_ID_LENGTH, null, 1, canonicalRiskCalibrationIdentifier) },
|
|
1190
|
+
{ key: 'domain', value: boundedText(MAX_RISK_CALIBRATION_DOMAIN_LENGTH, null, 1, validRiskCalibrationDomain) },
|
|
1191
|
+
{ key: 'reason', value: boundedText(32, [
|
|
1192
|
+
'duplicate_selection', 'record_limit', 'unreviewed', 'missing_evidence', 'unsupported_availability',
|
|
1193
|
+
]) },
|
|
1194
|
+
]);
|
|
1195
|
+
const DATASET_EXPORT_SPEC = objectOf([
|
|
1196
|
+
{ key: 'selected', value: COUNT_SPEC },
|
|
1197
|
+
{ key: 'included', value: COUNT_SPEC },
|
|
1198
|
+
{ key: 'excluded', value: COUNT_SPEC },
|
|
1199
|
+
{ key: 'exclusions', value: arrayOf(DATASET_EXCLUSION_SPEC, 0, MAX_RISK_CALIBRATION_RECORDS) },
|
|
1200
|
+
]);
|
|
1201
|
+
const DATASET_EXPORT_DOCUMENT_SPEC = objectOf([
|
|
1202
|
+
{ key: 'schema', value: literal(RISK_CALIBRATION_DATASET_SCHEMA) },
|
|
1203
|
+
{ key: 'version', value: literal(RISK_CALIBRATION_DATASET_VERSION) },
|
|
1204
|
+
{ key: 'records', value: arrayOf(DATASET_RECORD_SPEC, 1, MAX_RISK_CALIBRATION_RECORDS) },
|
|
1205
|
+
{ key: 'export', value: DATASET_EXPORT_SPEC },
|
|
1206
|
+
{ key: 'limitations', value: tupleOf(RISK_CALIBRATION_EXPORT_LIMITATIONS.map((value) => literal(value))) },
|
|
1207
|
+
]);
|
|
1208
|
+
function serialisationBudget(maximum, label) {
|
|
1209
|
+
if (!Number.isSafeInteger(maximum) || maximum < 1) {
|
|
1210
|
+
throw new RiskCalibrationJsonShapeError(`${label} byte limit must be a positive safe integer.`);
|
|
1211
|
+
}
|
|
1212
|
+
return { bytes: 0, maximum };
|
|
1213
|
+
}
|
|
1214
|
+
function copySerialisationRoot(value, spec, label, maximumBytes) {
|
|
1215
|
+
const budget = serialisationBudget(maximumBytes, label);
|
|
1216
|
+
const document = copyRiskCalibrationJsonValue(value, spec, label, budget, 0);
|
|
1217
|
+
consumeRiskCalibrationJsonBytes(budget, 1);
|
|
1218
|
+
return Object.freeze({
|
|
1219
|
+
document: document,
|
|
1220
|
+
bytes: budget.bytes,
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
export function snapshotRiskCalibrationDatasetExportForSerialization(value, maximumBytes = MAX_RISK_CALIBRATION_INPUT_BYTES) {
|
|
1224
|
+
try {
|
|
1225
|
+
const snapshot = copySerialisationRoot(value, DATASET_EXPORT_DOCUMENT_SPEC, 'Risk calibration export', maximumBytes);
|
|
1226
|
+
const document = snapshot.document;
|
|
1227
|
+
const records = document.records;
|
|
1228
|
+
const exportSummary = document.export;
|
|
1229
|
+
const exclusions = exportSummary.exclusions;
|
|
1230
|
+
if (exportSummary.included !== records.length
|
|
1231
|
+
|| exportSummary.excluded !== exclusions.length
|
|
1232
|
+
|| Number(exportSummary.selected) < records.length + exclusions.length) {
|
|
1233
|
+
throw new RiskCalibrationJsonShapeError('Risk calibration export counts must match the snapshotted records and exclusions.');
|
|
1234
|
+
}
|
|
1235
|
+
if (new Set(records.map((record) => record.id)).size !== records.length) {
|
|
1236
|
+
throw new RiskCalibrationJsonShapeError('Risk calibration export record identifiers must be unique.');
|
|
1237
|
+
}
|
|
1238
|
+
return snapshot;
|
|
1239
|
+
}
|
|
1240
|
+
catch (cause) {
|
|
1241
|
+
if (cause instanceof RiskCalibrationJsonBudgetError) {
|
|
1242
|
+
throw new RangeError(`Risk calibration export is limited to ${maximumBytes} bytes.`);
|
|
1243
|
+
}
|
|
1244
|
+
if (cause instanceof RiskCalibrationJsonShapeError)
|
|
1245
|
+
throw cause;
|
|
1246
|
+
throw new RiskCalibrationJsonShapeError('Risk calibration export must be a stable exact current dataset document.');
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
function riskCalibrationReportMode(value) {
|
|
1250
|
+
if (!value || typeof value !== 'object' || reflected('Risk calibration report', () => Array.isArray(value))) {
|
|
1251
|
+
throw new RiskCalibrationJsonShapeError('Risk calibration report must be an ordinary object.');
|
|
1252
|
+
}
|
|
1253
|
+
const descriptor = reflected('Risk calibration report mode', () => Object.getOwnPropertyDescriptor(value, 'mode'));
|
|
1254
|
+
if (!descriptor || !descriptor.enumerable || !('value' in descriptor)
|
|
1255
|
+
|| (descriptor.value !== 'detailed' && descriptor.value !== 'summary')) {
|
|
1256
|
+
throw new RiskCalibrationJsonShapeError('Risk calibration report must use the current detailed or summary discriminator.');
|
|
1257
|
+
}
|
|
1258
|
+
return descriptor.value;
|
|
1259
|
+
}
|
|
1260
|
+
export function snapshotRiskCalibrationReportForSerialization(value, limits = {}) {
|
|
1261
|
+
const mode = riskCalibrationReportMode(value);
|
|
1262
|
+
const maximumBytes = mode === 'summary'
|
|
1263
|
+
? limits.summaryMaximumBytes ?? MAX_RISK_CALIBRATION_SUMMARY_BYTES
|
|
1264
|
+
: limits.detailedMaximumBytes ?? MAX_RISK_CALIBRATION_DETAILED_REPORT_BYTES;
|
|
1265
|
+
try {
|
|
1266
|
+
const snapshot = copySerialisationRoot(value, mode === 'summary' ? SUMMARY_REPORT_SPEC : DETAILED_REPORT_SPEC, `Risk calibration ${mode} report`, maximumBytes);
|
|
1267
|
+
if (mode === 'detailed') {
|
|
1268
|
+
const dataset = snapshot.document.dataset;
|
|
1269
|
+
const records = snapshot.document.records;
|
|
1270
|
+
if (dataset.recordCount !== records.length
|
|
1271
|
+
|| new Set(records.map((record) => record.id)).size !== records.length) {
|
|
1272
|
+
throw new RiskCalibrationJsonShapeError('Risk calibration detailed records must match the dataset metadata and use unique identifiers.');
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
return Object.freeze({ ...snapshot, mode });
|
|
1276
|
+
}
|
|
1277
|
+
catch (cause) {
|
|
1278
|
+
if (cause instanceof RiskCalibrationJsonBudgetError) {
|
|
1279
|
+
throw new RangeError(`Risk calibration ${mode} output is limited to ${maximumBytes} bytes.`);
|
|
1280
|
+
}
|
|
1281
|
+
if (cause instanceof RiskCalibrationJsonShapeError)
|
|
1282
|
+
throw cause;
|
|
1283
|
+
throw new RiskCalibrationJsonShapeError(`Risk calibration ${mode} output must be a stable exact current report document.`);
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
export function serializeRiskCalibrationSnapshot(snapshot) {
|
|
1287
|
+
return `${JSON.stringify(snapshot.document, null, 2)}\n`;
|
|
1288
|
+
}
|