@slicedearth/whoisleuth-cli 2.0.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/cli/arguments.mjs +21 -5
  2. package/cli/artifact-validation/case-response.mjs +771 -0
  3. package/cli/artifact-validation/investigation-capsule.mjs +426 -0
  4. package/cli/artifact-validation/lookup-evidence.mjs +718 -0
  5. package/cli/artifact-validation/signed-review.mjs +598 -0
  6. package/cli/artifact-validation/structure-primitives.mjs +105 -0
  7. package/cli/artifact-verify.mjs +1 -1
  8. package/cli/assurance-command-runner.mjs +324 -0
  9. package/cli/command-reference.mjs +1175 -843
  10. package/cli/completion.mjs +56 -9
  11. package/cli/discriminated-command-handlers.mjs +11 -0
  12. package/cli/doctor.mjs +2 -2
  13. package/cli/evidence-command-runner.mjs +41 -31
  14. package/cli/export-evidence.mjs +1 -1
  15. package/cli/fail-policy.mjs +33 -8
  16. package/cli/formatters/terminal-command-formats.mjs +437 -0
  17. package/cli/formatters/terminal-lookup.mjs +758 -0
  18. package/cli/formatters/terminal-metadata.mjs +58 -0
  19. package/cli/formatters/terminal-shared.mjs +83 -0
  20. package/cli/formatters/terminal.mjs +5 -1232
  21. package/cli/history-command-runner.mjs +132 -0
  22. package/cli/inline-command-families.mjs +45 -0
  23. package/cli/inline-command-runner.mjs +55 -0
  24. package/cli/investigation-plan.mjs +9 -2
  25. package/cli/investigation-run.mjs +114 -9
  26. package/cli/lookup-brief.mjs +141 -23
  27. package/cli/lookup-browser-view.mjs +289 -0
  28. package/cli/lookup-browser.mjs +3 -288
  29. package/cli/mail-review.mjs +2 -6
  30. package/cli/offline-artifact-validation.mjs +19 -0
  31. package/cli/review-command-runner.mjs +269 -0
  32. package/cli/runner.mjs +15 -972
  33. package/cli/saved-lookup.mjs +19 -2
  34. package/cli/support-command-runner.mjs +229 -0
  35. package/cli/workflow-command-runner.mjs +129 -0
  36. package/docs/cli-reference.md +16 -9
  37. package/docs/cli.md +16 -2
  38. package/lib/availability.mjs +12 -9
  39. package/lib/bounded-contract-normalizers.mjs +2 -9
  40. package/lib/browser-library-profile.mjs +1 -2
  41. package/lib/client-behavior-profile.mjs +1 -2
  42. package/lib/credential-surface-profile.mjs +1 -1
  43. package/lib/domain-posture-analysis.mjs +10 -1
  44. package/lib/domain-posture.mjs +51 -23
  45. package/lib/evidence-export.mjs +47 -5
  46. package/lib/evidence-report-markdown.mjs +4 -0
  47. package/lib/evidence-report.mjs +50 -0
  48. package/lib/generated/cisa-kev-catalog.mjs +34 -4
  49. package/lib/generated/registrar-standing-catalogue.mjs +367 -0
  50. package/lib/json-record.mjs +12 -0
  51. package/lib/lookup-child-profile-contract.mjs +858 -0
  52. package/lib/lookup-contract-primitives.mjs +98 -0
  53. package/lib/lookup-diagnostics.mjs +96 -0
  54. package/lib/lookup-network-evidence-bounds.mjs +1 -0
  55. package/lib/lookup-response.mjs +367 -0
  56. package/lib/lookup.mjs +28 -443
  57. package/lib/page-role-profile.mjs +1 -3
  58. package/lib/registrar-standing-catalogue-contract.mjs +70 -0
  59. package/lib/registrar-standing-contract.mjs +300 -0
  60. package/lib/registrar-standing.mjs +269 -0
  61. package/lib/registry-insights.mjs +2 -8
  62. package/lib/risk-scoring.mjs +26 -10
  63. package/lib/sslbl-certificates.generated.mjs +138 -136
  64. package/lib/structured-data-identity.mjs +1 -3
  65. package/lib/technology-evidence-role.mjs +51 -0
  66. package/lib/threat-intelligence-runtime.mjs +3 -3
  67. package/lib/threat-intelligence-types.mjs +13 -0
  68. package/lib/threatfox-intelligence.mjs +1 -3
  69. package/lib/tls-intelligence.mjs +1 -2
  70. package/lib/urlhaus-intelligence.mjs +1 -3
  71. package/lib/urlscan-intelligence.mjs +1 -3
  72. package/lib/website-security-posture.mjs +1 -2
  73. package/lib/website-technology.mjs +72 -23
  74. package/lib/whois-common-formats.mjs +276 -0
  75. package/lib/whois-dialects.mjs +534 -0
  76. package/lib/whois-parser-support.mjs +305 -0
  77. package/lib/whois-parser.mjs +135 -0
  78. package/lib/whois.mjs +6 -1187
  79. package/package.json +2 -2
  80. package/packages/cases/case-evidence-model.mjs +9 -0
  81. package/packages/cases/case-migration-model.mjs +17 -11
  82. package/packages/cases/case-record-operations.mjs +235 -0
  83. package/packages/cases/case-response-model.mjs +13 -9
  84. package/packages/cases/case-response-packet.mjs +297 -112
  85. package/packages/comparison/page-similarity.mjs +23 -0
  86. package/packages/contracts/case-portability.mjs +235 -61
  87. package/packages/contracts/cli-lookup.mjs +4 -3
  88. package/packages/contracts/domain-control-manifest.mjs +1 -1
  89. package/packages/contracts/investigation-portability.mjs +1 -1
  90. package/packages/contracts/risk-calibration.mjs +2 -2
  91. package/packages/contracts/workspace-portability.mjs +34 -6
  92. package/packages/interchange/external-findings-import.mjs +113 -45
  93. package/packages/investigation/evidence-coverage-ledger.mjs +21 -0
  94. package/packages/investigation/lookup-claim-readiness.mjs +2 -2
  95. package/packages/workspace/website-snapshot-model.mjs +79 -6
  96. package/third-party-notices.txt +1 -1
  97. package/cli/artifact-structure.mjs +0 -2507
@@ -0,0 +1,105 @@
1
+ const CONTROL_RE = /[\u0000-\u001f\u007f]/u;
2
+ const DIGEST_RE = /^sha256:[a-f0-9]{64}$/u;
3
+ const DOMAIN_RE = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/u;
4
+ export const HEX_DIGEST_RE = /^[a-f0-9]{64}$/u;
5
+ export const SEMANTIC_VERSION_RE = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/u;
6
+ export function fail(label) {
7
+ throw new TypeError(`${label} has an unsupported or malformed structure.`);
8
+ }
9
+ export function record(value, label) {
10
+ if (!value || typeof value !== 'object' || Array.isArray(value))
11
+ fail(label);
12
+ return value;
13
+ }
14
+ export function exact(value, keys, label) {
15
+ const source = record(value, label);
16
+ const actual = Object.keys(source);
17
+ if (actual.length !== keys.length || actual.some((key) => !keys.includes(key)))
18
+ fail(label);
19
+ return source;
20
+ }
21
+ export function exactOptional(value, required, optional, label) {
22
+ const source = record(value, label);
23
+ const actual = Object.keys(source);
24
+ if (required.some((key) => !actual.includes(key))
25
+ || actual.some((key) => !required.includes(key) && !optional.includes(key)))
26
+ fail(label);
27
+ return source;
28
+ }
29
+ export function text(value, label, maximum = 2_000, allowEmpty = false) {
30
+ if (typeof value !== 'string' || value.length > maximum || CONTROL_RE.test(value) || (!allowEmpty && !value))
31
+ fail(label);
32
+ return value;
33
+ }
34
+ export function optionalText(value, label, maximum = 2_000) {
35
+ if (value !== null)
36
+ text(value, label, maximum);
37
+ }
38
+ export function iso(value, label, nullable = false) {
39
+ if (nullable && value === null)
40
+ return;
41
+ const candidate = text(value, label, 64);
42
+ const parsed = Date.parse(candidate);
43
+ if (!Number.isFinite(parsed) || new Date(parsed).toISOString() !== candidate)
44
+ fail(label);
45
+ }
46
+ export function integer(value, label, minimum = 0, maximum = Number.MAX_SAFE_INTEGER) {
47
+ if (!Number.isSafeInteger(value) || Number(value) < minimum || Number(value) > maximum)
48
+ fail(label);
49
+ return Number(value);
50
+ }
51
+ export function boolean(value, label) {
52
+ if (typeof value !== 'boolean')
53
+ fail(label);
54
+ }
55
+ export function enumeration(value, allowed, label) {
56
+ if (typeof value !== 'string' || !allowed.includes(value))
57
+ fail(label);
58
+ return value;
59
+ }
60
+ export function array(value, label, maximum, minimum = 0) {
61
+ if (!Array.isArray(value) || value.length < minimum || value.length > maximum)
62
+ fail(label);
63
+ return value;
64
+ }
65
+ export function strings(value, label, maximum, textMaximum = 2_000) {
66
+ const values = array(value, label, maximum);
67
+ for (const item of values)
68
+ text(item, label, textMaximum);
69
+ return values;
70
+ }
71
+ export function digest(value, label) {
72
+ if (typeof value !== 'string' || !DIGEST_RE.test(value))
73
+ fail(label);
74
+ }
75
+ export function sameValues(left, right) {
76
+ return left.length === right.length && left.every((value, index) => value === right[index]);
77
+ }
78
+ export function domain(value, label) {
79
+ if (typeof value !== 'string' || !DOMAIN_RE.test(value))
80
+ fail(label);
81
+ }
82
+ export function absoluteUrl(value, label, protocols) {
83
+ const candidate = text(value, label, 2_048);
84
+ try {
85
+ const parsed = new URL(candidate);
86
+ if (!protocols.includes(parsed.protocol) || parsed.username || parsed.password || parsed.search || parsed.hash)
87
+ fail(label);
88
+ return parsed;
89
+ }
90
+ catch {
91
+ return fail(label);
92
+ }
93
+ }
94
+ export function nullableRecord(value, label) {
95
+ if (value !== null)
96
+ record(value, label);
97
+ }
98
+ export function validateIntegrity(value, label, version, currentVersion) {
99
+ if (version !== currentVersion)
100
+ fail(label);
101
+ const integrity = exact(value, ['algorithm', 'canonicalization', 'digestSha256'], label);
102
+ if (integrity.algorithm !== 'SHA-256' || integrity.canonicalization !== 'sorted-json-v2')
103
+ fail(label);
104
+ digest(integrity.digestSha256, label);
105
+ }
@@ -1,7 +1,7 @@
1
1
  import { Buffer } from 'node:buffer';
2
2
  import { createHash } from 'node:crypto';
3
3
  import { parseBoundedJsonObject } from "./bounded-json.mjs";
4
- import { validateInvestigationCapsuleStructure, validateLookupEvidenceArtifactStructure, validateOfflineArtifactStructure, validateSignedDigestArtifactStructure, } from "./artifact-structure.mjs";
4
+ import { validateInvestigationCapsuleStructure, validateLookupEvidenceArtifactStructure, validateOfflineArtifactStructure, validateSignedDigestArtifactStructure, } from "./offline-artifact-validation.mjs";
5
5
  import { verifyCaseResponsePacketIntegrity, } from "../packages/cases/case-response-packet.mjs";
6
6
  import { CASE_PORTABILITY_VERIFIER_DISPATCH, CASE_RESPONSE_PACKET_VERSION, CASE_RESPONSE_PACKET_SCHEMA, CLI_CASE_PACK_VERSION, CLI_CASE_PACK_SCHEMA, } from "../packages/contracts/case-portability.mjs";
7
7
  import { ACQUISITION_DECISION_PACKET_SCHEMA, BULK_DOMAIN_COMPARISON_SCHEMA, BULK_MAIL_EXPOSURE_SCHEMA, BULK_REVIEW_MANIFEST_SCHEMA, LOOKUP_CLAIM_PASSPORT_SCHEMA, LOOKUP_CLAIM_PASSPORT_VERSION, SUPPORTED_ACQUISITION_DECISION_PACKET_VERSIONS, SUPPORTED_BULK_DOMAIN_COMPARISON_EXPORT_VERSIONS, SUPPORTED_BULK_MAIL_EXPOSURE_EXPORT_VERSIONS, SUPPORTED_BULK_REVIEW_MANIFEST_VERSIONS, } from "../packages/contracts/investigation-portability.mjs";
@@ -0,0 +1,324 @@
1
+ import { Buffer } from 'node:buffer';
2
+ import { scanBoundedJson } from "../lib/bounded-json.mjs";
3
+ import { MAX_ASSURANCE_INPUT_BYTES, buildDomainAssurance, formatDomainAssurance, } from "../lib/domain-assurance.mjs";
4
+ import { MAX_DOMAIN_CHANGE_PACKET_INPUT_BYTES, buildDomainChangePacket, formatDomainChangePacket, } from "../lib/domain-change-packet.mjs";
5
+ import { DOMAIN_CONTROL_FLIGHT_RECORDER_INPUT_SCHEMA, buildDomainControlFlightRecorder, formatDomainControlFlightRecorder, serializeDomainControlFlightRecorder, } from "../lib/domain-control-flight-recorder.mjs";
6
+ import { DOMAIN_CONTROL_MANIFEST_INPUT_SCHEMA, DOMAIN_CONTROL_REVIEW_INPUT_SCHEMA, buildDomainControlManifest, formatDomainControlResult, reviewDomainControlManifest, } from "../lib/domain-control-manifest.mjs";
7
+ import { CLI_DOMAIN_CONTROL_REVIEW_INPUT_SCHEMA, MAX_DOMAIN_CONTROL_REVIEW_JSON_DEPTH, MAX_DOMAIN_CONTROL_REVIEW_JSON_KEYS, MAX_DOMAIN_CONTROL_REVIEW_JSON_VALUES, buildCliDomainControlReview, formatCliDomainControlReview, } from "./domain-control-observations.mjs";
8
+ import { serializeDomainControlManifest } from "../packages/evidence/domain-control-runtime.mjs";
9
+ import { MAX_DOMAIN_CONTROL_REVIEW_COMMAND_BYTES } from "../packages/contracts/domain-control-review.mjs";
10
+ import { MAX_CT_EVENT_INPUT_BYTES, buildCtEventFindings, formatCtEventFindings, } from "./ct-event-intake.mjs";
11
+ import { boundedCliErrorMessage, CliUsageError } from "./errors.mjs";
12
+ import EXIT_CODES from "./exit-codes.mjs";
13
+ import { MAX_EXTERNAL_OBSERVATION_MAPPING_BYTES, formatExternalObservationMapping, mapExternalObservations, } from "./external-observation-mapping.mjs";
14
+ import { formatJsonDocument } from "./formatters/json.mjs";
15
+ import { MAX_INVESTIGATION_MANIFEST_ARTIFACT_BYTES, MAX_INVESTIGATION_MANIFEST_TOTAL_BYTES, buildInvestigationManifest, formatInvestigationManifest, } from "./investigation-manifest.mjs";
16
+ import { buildOpenAssetModelBridge, formatOpenAssetModelBridge, } from "./open-asset-model-bridge.mjs";
17
+ import { ASSURANCE_INLINE_COMMANDS } from "./inline-command-families.mjs";
18
+ import { MAX_SHARING_REVIEW_BYTES, buildSharingReview, formatSharingReview, } from "./sharing-review.mjs";
19
+ import { runDiscriminatedCommandHandler } from "./discriminated-command-handlers.mjs";
20
+ async function runManifestCommand(args, dependencies, context) {
21
+ context.setFailureLabel('Investigation manifest');
22
+ const artifacts = [];
23
+ let totalBytes = 0;
24
+ try {
25
+ for (const source of args.sources) {
26
+ const content = dependencies.readDiffInput
27
+ ? await dependencies.readDiffInput(source)
28
+ : await context.readInput(source, MAX_INVESTIGATION_MANIFEST_ARTIFACT_BYTES, 'Manifest artefact input');
29
+ totalBytes += Buffer.byteLength(content, 'utf8');
30
+ if (totalBytes > MAX_INVESTIGATION_MANIFEST_TOTAL_BYTES) {
31
+ throw new CliUsageError(`Manifest artefacts exceed the ${MAX_INVESTIGATION_MANIFEST_TOTAL_BYTES}-byte combined limit.`);
32
+ }
33
+ artifacts.push({ content });
34
+ }
35
+ }
36
+ catch (error) {
37
+ if (error instanceof CliUsageError)
38
+ throw error;
39
+ throw new CliUsageError(`Could not read manifest artefact input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
40
+ }
41
+ let document;
42
+ try {
43
+ document = await buildInvestigationManifest({
44
+ workflow: args.workflow,
45
+ configurationDigestSha256: args.configurationDigestSha256,
46
+ artifacts,
47
+ }, context.now(), context.packageVersion);
48
+ }
49
+ catch (error) {
50
+ throw new CliUsageError(boundedCliErrorMessage(error, 'Investigation manifest input is invalid'));
51
+ }
52
+ if (!args.quiet) {
53
+ context.writeStdout(args.output === 'json'
54
+ ? formatJsonDocument(document)
55
+ : context.terminal(formatInvestigationManifest(document), args.color));
56
+ }
57
+ return EXIT_CODES.SUCCESS;
58
+ }
59
+ async function runObservationBridgeCommand(args, dependencies, context) {
60
+ const mapping = args.action === 'map-observations';
61
+ context.setFailureLabel(mapping ? 'External observation mapping' : 'Open Asset Model export');
62
+ let input;
63
+ try {
64
+ input = dependencies.readArtifactInput
65
+ ? await dependencies.readArtifactInput(args.source)
66
+ : await context.readInput(args.source, MAX_EXTERNAL_OBSERVATION_MAPPING_BYTES, mapping ? 'External observation mapping input' : 'Open Asset Model bridge input');
67
+ }
68
+ catch (error) {
69
+ if (error instanceof CliUsageError)
70
+ throw error;
71
+ throw new CliUsageError(`Could not read ${mapping ? 'observation mapping' : 'asset bridge'} input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
72
+ }
73
+ if (!input.trim())
74
+ throw new CliUsageError(`${args.action} requires one versioned JSON file or a document on stdin.`);
75
+ let parsed;
76
+ try {
77
+ scanBoundedJson(input);
78
+ parsed = JSON.parse(input);
79
+ }
80
+ catch {
81
+ throw new CliUsageError(`${mapping ? 'External observation mapping' : 'Open Asset Model bridge'} input is not valid bounded JSON without duplicate keys.`);
82
+ }
83
+ let document;
84
+ try {
85
+ document = mapping
86
+ ? mapExternalObservations(parsed)
87
+ : buildOpenAssetModelBridge(parsed, context.now());
88
+ }
89
+ catch (error) {
90
+ throw new CliUsageError(boundedCliErrorMessage(error, `${mapping ? 'External observation mapping' : 'Open Asset Model bridge'} input is invalid`));
91
+ }
92
+ if (!args.quiet) {
93
+ context.writeStdout(args.output === 'json'
94
+ ? formatJsonDocument(document)
95
+ : context.terminal(mapping
96
+ ? formatExternalObservationMapping(document)
97
+ : formatOpenAssetModelBridge(document), args.color));
98
+ }
99
+ return EXIT_CODES.SUCCESS;
100
+ }
101
+ async function runDomainControlCommand(args, dependencies, context) {
102
+ context.setFailureLabel('Domain control review');
103
+ let input;
104
+ try {
105
+ input = dependencies.readArtifactInput
106
+ ? await dependencies.readArtifactInput(args.source)
107
+ : await context.readInput(args.source, MAX_DOMAIN_CONTROL_REVIEW_COMMAND_BYTES, 'Domain control input');
108
+ }
109
+ catch (error) {
110
+ if (error instanceof CliUsageError)
111
+ throw error;
112
+ throw new CliUsageError(`Could not read domain control input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
113
+ }
114
+ if (!input.trim())
115
+ throw new CliUsageError('domain-control requires one JSON file or a document on stdin.');
116
+ const normalisedInput = input.replace(/^\uFEFF/u, '');
117
+ let parsed;
118
+ try {
119
+ scanBoundedJson(normalisedInput, {
120
+ maximumDepth: MAX_DOMAIN_CONTROL_REVIEW_JSON_DEPTH,
121
+ maximumKeys: MAX_DOMAIN_CONTROL_REVIEW_JSON_KEYS,
122
+ maximumValues: MAX_DOMAIN_CONTROL_REVIEW_JSON_VALUES,
123
+ });
124
+ parsed = JSON.parse(normalisedInput);
125
+ }
126
+ catch (cause) {
127
+ const detail = cause instanceof Error ? cause.message : '';
128
+ if (detail.startsWith('Artefact JSON ')) {
129
+ throw new CliUsageError(`Domain control input ${detail.slice('Artefact JSON '.length)}`);
130
+ }
131
+ throw new CliUsageError('Domain control input is not valid JSON.');
132
+ }
133
+ const schema = parsed && typeof parsed === 'object' && !Array.isArray(parsed)
134
+ ? parsed.schema
135
+ : null;
136
+ const document = schema === DOMAIN_CONTROL_MANIFEST_INPUT_SCHEMA
137
+ ? buildDomainControlManifest(parsed, context.now())
138
+ : schema === DOMAIN_CONTROL_REVIEW_INPUT_SCHEMA
139
+ ? reviewDomainControlManifest(parsed, context.now())
140
+ : schema === CLI_DOMAIN_CONTROL_REVIEW_INPUT_SCHEMA
141
+ ? buildCliDomainControlReview(input, context.now())
142
+ : schema === DOMAIN_CONTROL_FLIGHT_RECORDER_INPUT_SCHEMA
143
+ ? buildDomainControlFlightRecorder(parsed, context.now())
144
+ : null;
145
+ if (!document) {
146
+ throw new CliUsageError('Domain control input must use a supported manifest, review, saved-Lookup review, or flight-recorder schema.');
147
+ }
148
+ const terminalDocument = schema === CLI_DOMAIN_CONTROL_REVIEW_INPUT_SCHEMA
149
+ ? formatCliDomainControlReview(document)
150
+ : schema === DOMAIN_CONTROL_FLIGHT_RECORDER_INPUT_SCHEMA
151
+ ? formatDomainControlFlightRecorder(document)
152
+ : formatDomainControlResult(document);
153
+ if (!args.quiet) {
154
+ context.writeStdout(args.output === 'json'
155
+ ? schema === DOMAIN_CONTROL_MANIFEST_INPUT_SCHEMA
156
+ ? serializeDomainControlManifest(document)
157
+ : schema === DOMAIN_CONTROL_FLIGHT_RECORDER_INPUT_SCHEMA
158
+ ? serializeDomainControlFlightRecorder(document)
159
+ : formatJsonDocument(document)
160
+ : context.terminal(terminalDocument, args.color));
161
+ }
162
+ return EXIT_CODES.SUCCESS;
163
+ }
164
+ async function runAssuranceReviewCommand(args, dependencies, context) {
165
+ context.setFailureLabel('Domain assurance review');
166
+ let input;
167
+ try {
168
+ input = dependencies.readArtifactInput
169
+ ? await dependencies.readArtifactInput(args.source)
170
+ : await context.readInput(args.source, MAX_ASSURANCE_INPUT_BYTES, 'Domain assurance input');
171
+ }
172
+ catch (error) {
173
+ if (error instanceof CliUsageError)
174
+ throw error;
175
+ throw new CliUsageError(`Could not read domain assurance input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
176
+ }
177
+ if (!input.trim())
178
+ throw new CliUsageError('assurance requires one versioned JSON file or a document on stdin.');
179
+ let parsed;
180
+ try {
181
+ scanBoundedJson(input);
182
+ parsed = JSON.parse(input);
183
+ }
184
+ catch {
185
+ throw new CliUsageError('Domain assurance input is not valid bounded JSON without duplicate keys.');
186
+ }
187
+ let document;
188
+ try {
189
+ document = buildDomainAssurance(parsed, context.now());
190
+ }
191
+ catch (error) {
192
+ throw new CliUsageError(boundedCliErrorMessage(error, 'Domain assurance input is invalid'));
193
+ }
194
+ if (!args.quiet) {
195
+ context.writeStdout(args.output === 'json'
196
+ ? formatJsonDocument(document)
197
+ : context.terminal(formatDomainAssurance(document), args.color));
198
+ }
199
+ return EXIT_CODES.SUCCESS;
200
+ }
201
+ async function runChangePacketCommand(args, dependencies, context) {
202
+ context.setFailureLabel('Domain change packet');
203
+ let input;
204
+ try {
205
+ input = dependencies.readArtifactInput
206
+ ? await dependencies.readArtifactInput(args.source)
207
+ : await context.readInput(args.source, MAX_DOMAIN_CHANGE_PACKET_INPUT_BYTES, 'Domain change packet input');
208
+ }
209
+ catch (error) {
210
+ if (error instanceof CliUsageError)
211
+ throw error;
212
+ throw new CliUsageError(`Could not read domain change packet input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
213
+ }
214
+ if (!input.trim())
215
+ throw new CliUsageError('change-packet requires one versioned JSON file or a document on stdin.');
216
+ let parsed;
217
+ try {
218
+ scanBoundedJson(input);
219
+ parsed = JSON.parse(input);
220
+ }
221
+ catch {
222
+ throw new CliUsageError('Domain change packet input is not valid bounded JSON without duplicate keys.');
223
+ }
224
+ let document;
225
+ try {
226
+ document = await buildDomainChangePacket(parsed, context.now());
227
+ }
228
+ catch (error) {
229
+ throw new CliUsageError(boundedCliErrorMessage(error, 'Domain change packet input is invalid'));
230
+ }
231
+ if (!args.quiet) {
232
+ context.writeStdout(args.output === 'json'
233
+ ? formatJsonDocument(document)
234
+ : context.terminal(formatDomainChangePacket(document), args.color));
235
+ }
236
+ return document.gate.pass ? EXIT_CODES.SUCCESS : EXIT_CODES.PARTIAL_FAILURE;
237
+ }
238
+ async function runSharingReviewCommand(args, dependencies, context) {
239
+ context.setFailureLabel('Evidence sharing review');
240
+ let input;
241
+ try {
242
+ input = dependencies.readArtifactInput
243
+ ? await dependencies.readArtifactInput(args.source)
244
+ : await context.readInput(args.source, MAX_SHARING_REVIEW_BYTES, 'Sharing review input');
245
+ }
246
+ catch (error) {
247
+ if (error instanceof CliUsageError)
248
+ throw error;
249
+ throw new CliUsageError(`Could not read sharing review input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
250
+ }
251
+ if (!input.trim())
252
+ throw new CliUsageError('sharing-review requires one artefact JSON file or a document on stdin.');
253
+ let document;
254
+ try {
255
+ document = await buildSharingReview(input, {
256
+ marking: args.marking,
257
+ recipientScope: args.recipientScope,
258
+ purpose: args.purpose,
259
+ humanReviewed: args.humanReviewed,
260
+ personalDataReviewed: args.personalDataReviewed,
261
+ redactionsConfirmed: args.redactionsConfirmed,
262
+ }, context.now());
263
+ }
264
+ catch (error) {
265
+ throw new CliUsageError(boundedCliErrorMessage(error, 'Sharing review input is invalid'));
266
+ }
267
+ if (!args.quiet) {
268
+ context.writeStdout(args.output === 'json'
269
+ ? formatJsonDocument(document)
270
+ : context.terminal(formatSharingReview(document), args.color));
271
+ }
272
+ return document.summary.status === 'blocked' ? EXIT_CODES.PARTIAL_FAILURE : EXIT_CODES.SUCCESS;
273
+ }
274
+ async function runCertificateIntakeCommand(args, dependencies, context) {
275
+ context.setFailureLabel('Certificate event intake');
276
+ let input;
277
+ try {
278
+ input = dependencies.readArtifactInput
279
+ ? await dependencies.readArtifactInput(args.source)
280
+ : await context.readInput(args.source, MAX_CT_EVENT_INPUT_BYTES, 'Certificate event input');
281
+ }
282
+ catch (error) {
283
+ if (error instanceof CliUsageError)
284
+ throw error;
285
+ throw new CliUsageError(`Could not read certificate event input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
286
+ }
287
+ if (!input.trim())
288
+ throw new CliUsageError('ct-intake requires one versioned JSON file or a document on stdin.');
289
+ let parsed;
290
+ try {
291
+ scanBoundedJson(input);
292
+ parsed = JSON.parse(input);
293
+ }
294
+ catch {
295
+ throw new CliUsageError('Certificate event input is not valid bounded JSON without duplicate keys.');
296
+ }
297
+ let document;
298
+ try {
299
+ document = buildCtEventFindings(parsed);
300
+ }
301
+ catch (error) {
302
+ throw new CliUsageError(boundedCliErrorMessage(error, 'Certificate event input is invalid'));
303
+ }
304
+ if (!args.quiet) {
305
+ context.writeStdout(args.output === 'json'
306
+ ? formatJsonDocument(document)
307
+ : context.terminal(formatCtEventFindings(document), args.color));
308
+ }
309
+ return EXIT_CODES.SUCCESS;
310
+ }
311
+ const ASSURANCE_COMMAND_HANDLERS = Object.freeze({
312
+ 'manifest': runManifestCommand,
313
+ 'map-observations': runObservationBridgeCommand,
314
+ 'oam-export': runObservationBridgeCommand,
315
+ 'domain-control': runDomainControlCommand,
316
+ 'assurance': runAssuranceReviewCommand,
317
+ 'change-packet': runChangePacketCommand,
318
+ 'sharing-review': runSharingReviewCommand,
319
+ 'ct-intake': runCertificateIntakeCommand,
320
+ });
321
+ function runAssuranceCommand(args, dependencies, context) {
322
+ return runDiscriminatedCommandHandler(ASSURANCE_COMMAND_HANDLERS, args, dependencies, context);
323
+ }
324
+ export { ASSURANCE_COMMAND_HANDLERS, runAssuranceCommand };