@slicedearth/whoisleuth-cli 2.5.1 → 2.7.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 (170) hide show
  1. package/DISCLOSURE +9 -6
  2. package/NOTICE +3 -2
  3. package/README.md +60 -67
  4. package/cli/artifact-validation/case-response.mjs +6 -814
  5. package/cli/artifact-validation/lookup-evidence.mjs +30 -3
  6. package/cli/artifact-validation/signed-review.mjs +2 -51
  7. package/cli/artifact-verify.mjs +41 -3
  8. package/cli/bulk-command-runner.mjs +4 -6
  9. package/cli/case-command.mjs +92 -16
  10. package/cli/case-pack.mjs +14 -3
  11. package/cli/ci-report.mjs +17 -5
  12. package/cli/command-definition.mjs +7 -6
  13. package/cli/discovery-scan-command-runner.mjs +4 -6
  14. package/cli/doctor.mjs +37 -3
  15. package/cli/domain-feed.mjs +61 -0
  16. package/cli/formatters/html.mjs +3 -0
  17. package/cli/formatters/json.mjs +9 -14
  18. package/cli/formatters/terminal-lookup.mjs +24 -1
  19. package/cli/history-command-definitions.mjs +1 -1
  20. package/cli/indicator-set-command.mjs +1 -2
  21. package/cli/intake-command.mjs +43 -3
  22. package/cli/investigation-artifacts.mjs +2 -3
  23. package/cli/investigation-package-review.mjs +2 -0
  24. package/cli/investigation-plan.mjs +15 -0
  25. package/cli/local-mmdb-review.mjs +27 -4
  26. package/cli/lookup-brief.mjs +11 -5
  27. package/cli/lookup-command-runner.mjs +21 -13
  28. package/cli/offline-evidence-review.mjs +51 -5
  29. package/cli/review-arguments.mjs +3 -2
  30. package/cli/review-command-definitions.mjs +23 -17
  31. package/cli/review-command-runner.mjs +6 -3
  32. package/cli/risk-calibration.mjs +6 -4
  33. package/cli/saved-lookup.mjs +22 -7
  34. package/cli/selected-input-worker.mjs +12 -2
  35. package/cli/source-reliability.mjs +12 -10
  36. package/cli/support-arguments.mjs +1 -1
  37. package/cli/support-command-definitions.mjs +5 -4
  38. package/cli/support-command-runner.mjs +9 -7
  39. package/cli/watchlist-review.mjs +43 -0
  40. package/cli/workflow-arguments.mjs +14 -0
  41. package/cli/workflow-command-definitions.mjs +27 -1
  42. package/cli/workflow-command-runner.mjs +32 -0
  43. package/docs/cli-reference.md +49 -11
  44. package/docs/cli.md +200 -26
  45. package/docs/contextual-reviews.md +38 -5
  46. package/docs/infrastructure-observations.md +135 -0
  47. package/docs/offline-intake-context.md +213 -0
  48. package/lib/credential-form-attribution.mjs +136 -0
  49. package/lib/credential-surface-profile.mjs +6 -2
  50. package/lib/dmarc-discovery.mjs +4 -2
  51. package/lib/dns-delegation-health.mjs +23 -7
  52. package/lib/dns-inheritance-review.mjs +14 -1
  53. package/lib/dns-intelligence.mjs +18 -4
  54. package/lib/domain-posture-analysis.mjs +53 -2
  55. package/lib/domain-posture-parsers.mjs +9 -0
  56. package/lib/domain-posture.mjs +29 -10
  57. package/lib/evidence-export-privacy.mjs +5 -2
  58. package/lib/evidence-export.mjs +10 -4
  59. package/lib/evidence-report.mjs +3 -1
  60. package/lib/favicon.mjs +13 -4
  61. package/lib/generated/cisa-kev-catalog.mjs +17 -4
  62. package/lib/generated/registrar-standing-catalogue.mjs +7 -7
  63. package/lib/generated/retire-browser-catalog.mjs +178 -5
  64. package/lib/html-signals.mjs +6 -0
  65. package/lib/idn-confusables.mjs +5 -1
  66. package/lib/lookup-child-profile-contract.mjs +28 -11
  67. package/lib/lookup-collection-receipt.mjs +52 -0
  68. package/lib/lookup-diagnostics.mjs +2 -12
  69. package/lib/lookup-progress.mjs +29 -31
  70. package/lib/lookup-response-contract.mjs +10 -0
  71. package/lib/lookup-response.mjs +15 -14
  72. package/lib/lookup-source-progress.mjs +4 -2
  73. package/lib/lookup.mjs +33 -2
  74. package/lib/operation-budget.mjs +4 -0
  75. package/lib/rdap-capabilities.mjs +43 -9
  76. package/lib/registry-insights.mjs +17 -6
  77. package/lib/service-binding-dns.mjs +19 -2
  78. package/lib/sslbl-certificates.generated.mjs +142 -140
  79. package/lib/static-html-analysis.mjs +32 -4
  80. package/lib/technology-evidence-role.mjs +1 -51
  81. package/lib/tls-intelligence.mjs +30 -5
  82. package/lib/website-enrichment.mjs +39 -23
  83. package/lib/whois-authority.mjs +56 -1
  84. package/lib/whois-chain.mjs +14 -4
  85. package/lib/whois-common-formats.mjs +3 -1
  86. package/lib/whois-contacts.mjs +13 -2
  87. package/lib/whois-contracts.mjs +2 -0
  88. package/lib/whois-normalization.mjs +4 -4
  89. package/package.json +9 -7
  90. package/packages/analysis/bounded-json.mjs +12 -2
  91. package/packages/analysis/risk-scoring.mjs +12 -9
  92. package/packages/cases/case-attachment-model.mjs +36 -1
  93. package/packages/cases/case-checkpoint-receipts.mjs +68 -0
  94. package/packages/cases/case-containment-handoff.mjs +195 -0
  95. package/packages/cases/case-evidence-links.mjs +44 -0
  96. package/packages/cases/case-evidence-model.mjs +51 -7
  97. package/packages/cases/case-migration-model.mjs +9 -1
  98. package/packages/cases/case-object-coverage.mjs +119 -0
  99. package/packages/cases/case-packet-correction.mjs +106 -0
  100. package/packages/cases/case-recheck-model.mjs +35 -5
  101. package/packages/cases/case-record-operations.mjs +87 -9
  102. package/packages/cases/case-record-projection.mjs +43 -5
  103. package/packages/cases/case-record-version-input.mjs +23 -1
  104. package/packages/cases/case-report.mjs +35 -4
  105. package/packages/cases/case-requested-evidence.mjs +1 -1
  106. package/packages/cases/case-response-actions.mjs +57 -1
  107. package/packages/cases/case-response-model.mjs +7 -0
  108. package/packages/cases/case-response-object.mjs +119 -0
  109. package/packages/cases/case-response-outcomes.mjs +81 -14
  110. package/packages/cases/case-response-packet-render.mjs +22 -3
  111. package/packages/cases/case-response-packet-validation.mjs +880 -0
  112. package/packages/cases/case-response-packet-vocabulary.mjs +12 -1
  113. package/packages/cases/case-response-packet.mjs +32 -17
  114. package/packages/cases/case-response-review-inputs.mjs +66 -8
  115. package/packages/cases/case-type-presentation.mjs +10 -0
  116. package/packages/cases/case-workflow-metadata.mjs +1 -17
  117. package/packages/comparison/capture-context.mjs +3 -2
  118. package/packages/contracts/analyst-interchange.mjs +7 -5
  119. package/packages/contracts/analyst-review-state-contract.mjs +1 -1
  120. package/packages/contracts/candidate-watch-review.mjs +54 -0
  121. package/packages/contracts/case-portability.mjs +292 -43
  122. package/packages/contracts/cli-command-semantics.mjs +3 -0
  123. package/packages/contracts/cli-doctor.mjs +4 -0
  124. package/packages/contracts/cli-lookup.mjs +57 -46
  125. package/packages/contracts/context-review.mjs +2 -1
  126. package/packages/contracts/external-observation-interchange.mjs +96 -28
  127. package/packages/contracts/lookup-evidence.mjs +13 -2
  128. package/packages/contracts/message-intake.mjs +7 -1
  129. package/packages/contracts/registry-insights.mjs +6 -0
  130. package/packages/contracts/risk-calibration.mjs +44 -29
  131. package/packages/contracts/schema-lifecycle.mjs +3 -1
  132. package/packages/contracts/web-capture.mjs +1 -1
  133. package/packages/contracts/workspace-portability.mjs +86 -16
  134. package/packages/evidence/collection-quality.mjs +1 -1
  135. package/packages/evidence/credential-form-attribution.mjs +60 -0
  136. package/packages/evidence/dmarc-policy-context.mjs +80 -0
  137. package/packages/evidence/image-regions.mjs +15 -0
  138. package/packages/evidence/lookup-collection-receipt.mjs +140 -0
  139. package/packages/evidence/lookup-collection-review.mjs +74 -0
  140. package/packages/evidence/record-identity.mjs +60 -16
  141. package/packages/evidence/technology-evidence-role.mjs +51 -0
  142. package/packages/interchange/external-findings-import.mjs +40 -21
  143. package/packages/interchange/json-output.mjs +14 -0
  144. package/packages/interchange/managed-indicator-export.mjs +3 -2
  145. package/packages/interchange/managed-indicator-set.mjs +13 -0
  146. package/packages/investigation/acquisition-decision-packet.mjs +78 -0
  147. package/packages/investigation/bulk-mail-exposure.mjs +2 -2
  148. package/packages/investigation/context-review.mjs +3 -3
  149. package/packages/investigation/document-intake.mjs +9 -3
  150. package/packages/investigation/domain-history-review.mjs +50 -12
  151. package/packages/investigation/domain-transition-review.mjs +2 -0
  152. package/packages/investigation/infrastructure-observation.mjs +234 -0
  153. package/packages/investigation/intake-context.mjs +284 -0
  154. package/packages/investigation/intake-indicators.mjs +116 -0
  155. package/packages/investigation/intake-phones.mjs +155 -0
  156. package/packages/investigation/intake-report.mjs +2 -1
  157. package/packages/investigation/investigation-manifest.mjs +33 -7
  158. package/packages/investigation/link-intake.mjs +38 -8
  159. package/packages/investigation/message-intake.mjs +55 -15
  160. package/packages/monitoring/domain-feed.mjs +321 -0
  161. package/packages/workspace/brand-candidate-workflow.mjs +351 -0
  162. package/packages/workspace/brand-keyword-campaign.mjs +241 -0
  163. package/packages/workspace/brand-profile-model.mjs +46 -2
  164. package/packages/workspace/bulk-session-model.mjs +12 -0
  165. package/packages/workspace/candidate-watch-handoff.mjs +195 -0
  166. package/packages/workspace/detection-rule-model.mjs +10 -2
  167. package/packages/workspace/shortlist-model.mjs +10 -2
  168. package/packages/workspace/watchlist-history.mjs +31 -4
  169. package/packages/workspace/watchlist-store.mjs +136 -7
  170. package/third-party-notices.txt +30 -30
@@ -3,7 +3,19 @@ import { CliUsageError } from "./errors.mjs";
3
3
  import {} from "./fail-policy.mjs";
4
4
  import { commandDefaultNumber } from "./command-reference.mjs";
5
5
  import { WORKFLOW_COMMAND_DEFINITIONS } from "./workflow-command-definitions.mjs";
6
+ import { parseDomainFeedSelectors } from "./domain-feed.mjs";
6
7
  import { terminalOptions, jsonOutput, failPolicies, parseOutput, } from "./argument-values.mjs";
8
+ function parseDomainFeedArguments(parsed) {
9
+ const operation = parsed.positionalValue('operation');
10
+ const contextSource = parsed.positionalValue('context');
11
+ if ((operation === 'watch-input') !== Boolean(contextSource))
12
+ throw new CliUsageError('watch-input requires one existing candidate-watch-input context file; review does not accept a context file.');
13
+ return { action: 'domain-feed', operation, feedId: parsed.positionalValue('feed'), source: parsed.positionalValue('source'), contextSource,
14
+ selection: parseDomainFeedSelectors(parsed.optionValues('--select')), output: jsonOutput(parsed), ...terminalOptions(parsed) };
15
+ }
16
+ function parseWatchlistReviewArguments(parsed) {
17
+ return { action: 'watchlist-review', operation: parsed.positionalValue('operation'), source: parsed.positionalValue('source'), output: jsonOutput(parsed), ...terminalOptions(parsed) };
18
+ }
7
19
  function parseMonitorOnceArguments(parsed) {
8
20
  const selectedFailPolicies = failPolicies(parsed, 'monitor-once');
9
21
  return {
@@ -80,6 +92,8 @@ function parseWorkflowRunArguments(parsed) {
80
92
  };
81
93
  }
82
94
  export const WORKFLOW_ARGUMENT_PARSERS = Object.freeze({
95
+ 'domain-feed': parseDomainFeedArguments,
96
+ 'watchlist-review': parseWatchlistReviewArguments,
83
97
  'monitor-once': parseMonitorOnceArguments,
84
98
  'workflow-plan': parseWorkflowPlanArguments,
85
99
  'workflow-run': parseWorkflowRunArguments,
@@ -1,6 +1,32 @@
1
1
  import { INVESTIGATION_PLAN_RECIPES, RUNNABLE_INVESTIGATION_PLAN_RECIPES } from "./investigation-recipes.mjs";
2
- import { positional, OPTIONAL_FILE_POSITIONAL, constraint, commandSeed, integer, BASE_INTEGER_RANGE, file } from "./command-definition.mjs";
2
+ import { positional, OPTIONAL_FILE_POSITIONAL, constraint, commandSeed, integer, BASE_INTEGER_RANGE, file, optionDefinition } from "./command-definition.mjs";
3
+ import { DOMAIN_FEED_CATALOGUE } from "../packages/monitoring/domain-feed.mjs";
4
+ import { WATCHLIST_SCHEMA_VERSION } from "../packages/contracts/workspace-portability.mjs";
3
5
  export const WORKFLOW_COMMAND_DEFINITIONS = Object.freeze({
6
+ 'domain-feed': commandSeed({
7
+ reference: { description: 'Review exact hosts or literal terms in a selected local plain-domain feed.',
8
+ example: 'whoisleuth domain-feed review tif-mini feed.txt --select host:candidate.example --json',
9
+ boundary: 'Always available offline. Reads one explicit local regular file; never downloads a feed, performs a Lookup, changes scores, retains candidates or enables a schedule. Streams at most 256 MiB and 10 million physical rows with 1,024-byte lines and a ten-minute deadline; retains at most 200 matched hosts. Repeat --select host:<hostname>, term:<literal> or exclude:<literal> for at most 200 exact hosts, 20 positive and 20 negative literal terms of 3–80 characters. A negative match vetoes even an exact host before the match bound; at least one positive term or exact host is required. Matching uses canonical ASCII/punycode substrings, never token boundaries, parent equivalence, typo/confusable expansion or regular expressions. Review emits the full raw-byte digest, separate declared file/local clocks, unknown per-host clocks and omissions. watch-input also requires a candidate-watch-input v1 context file with an empty candidates array, and emits that existing format for a separate watchlist-review plan/export. Inclusion is not a maliciousness, activity, ownership or availability verdict.' },
10
+ collection: { scope: 'Local raw file: 256 MiB, 10 million physical rows, 1,024 bytes per line, ten minutes; 200 retained hosts, 20 positive and 20 negative literal terms, no requests.' },
11
+ summary: 'Review a selected local plain-domain feed', options: ['--select', '--json', '--quiet', '--no-color'],
12
+ optionOverrides: { '--select': optionDefinition('text', 'Select host:<exact-hostname>, term:<literal-text> or exclude:<literal-text>; repeat for separate explicit selectors.', { occurrence: 'repeatable', acceptsOptionLikeValue: true }) },
13
+ positionals: Object.freeze([positional('operation', 'enum', 1, 1, ['review', 'watch-input']),
14
+ positional('feed', 'enum', 1, 1, DOMAIN_FEED_CATALOGUE.map(feed => feed.id)), positional('source', 'file', 1, 1), positional('context', 'file', 0, 1)]),
15
+ constraints: Object.freeze([constraint({ kind: 'required', options: ['--select'] })]),
16
+ handlerOwner: 'workflow', networkEffect: 'offline', common: false,
17
+ schemaIdentifiers: Object.freeze(['whoisleuth.candidate-watch-input']), primaryArtefacts: Object.freeze(['Bounded feed inclusion review', 'Explicit candidate watch selection']),
18
+ planSupport: true, additionalOutputFormats: Object.freeze([]), bootstrapProfile: 'allowed',
19
+ }),
20
+ 'watchlist-review': commandSeed({
21
+ reference: { description: 'Preview or export an explicit candidate selection to a local watchlist.', example: 'whoisleuth watchlist-review plan selection.json --json',
22
+ boundary: `Reads bounded local JSON only. Plan shows exact domains, duplicates, per-domain outcomes, destination and review priority with zero additional requests. Export emits Watchlist schema ${WATCHLIST_SCHEMA_VERSION} without creating a scan or baseline. Neither mode enables collection or scheduling.` },
23
+ collection: { scope: 'At most 2,000 selected candidate domains; no target requests.' },
24
+ summary: 'Review a local candidate watchlist handoff', options: ['--json', '--quiet', '--no-color'],
25
+ positionals: Object.freeze([positional('operation', 'enum', 1, 1, ['plan', 'export']), positional('source', 'file', 0, 1, [], 'argv_or_stdin')]),
26
+ constraints: Object.freeze([]), handlerOwner: 'workflow', networkEffect: 'offline', common: false,
27
+ schemaIdentifiers: Object.freeze(['whoisleuth.candidate-watch-input', 'whoisleuth.watchlists']), primaryArtefacts: Object.freeze(['Exact candidate handoff plan', 'Local watchlist export']),
28
+ planSupport: true, additionalOutputFormats: Object.freeze([]), bootstrapProfile: 'allowed',
29
+ }),
4
30
  "monitor-once": commandSeed({
5
31
  reference: {
6
32
  description: 'Collect one bounded owned-domain review and compare it with an optional prior checkpoint.',
@@ -16,7 +16,37 @@ import { MAX_SHARING_REVIEW_BYTES } from "./sharing-review.mjs";
16
16
  import { createBufferedOutput } from "./output-file.mjs";
17
17
  import { boundedInteractiveAnswer, canReadInteractiveLine, readBoundedInteractiveLine } from "./terminal-input.mjs";
18
18
  import { WORKFLOW_INLINE_COMMANDS } from "./inline-command-families.mjs";
19
+ import { reviewCandidateWatchInput, parseCandidateWatchInput } from "./watchlist-review.mjs";
20
+ import { readDomainFeedFile } from "./domain-feed.mjs";
21
+ import { scanDomainFeed, buildDomainFeedWatchInput, normalizeDomainFeedSelection } from "../packages/monitoring/domain-feed.mjs";
22
+ import { MAX_WATCHLIST_IMPORT_BYTES } from "../packages/contracts/workspace-portability.mjs";
19
23
  import { runDiscriminatedCommandHandler } from "./discriminated-command-handlers.mjs";
24
+ async function runWatchlistReviewCommand(args, dependencies, context) {
25
+ const raw = dependencies.readArtifactInput ? await dependencies.readArtifactInput(args.source) : await context.readInput(args.source, MAX_WATCHLIST_IMPORT_BYTES, 'Candidate watch selection');
26
+ const document = reviewCandidateWatchInput(raw, args.operation, context.now());
27
+ if (!args.quiet)
28
+ context.writeStdout(formatJsonDocument(document));
29
+ return EXIT_CODES.SUCCESS;
30
+ }
31
+ async function runDomainFeedCommand(args, dependencies, context) {
32
+ context.setFailureLabel('Offline domain-feed review');
33
+ const handoff = args.contextSource ? parseCandidateWatchInput(dependencies.readArtifactInput
34
+ ? await dependencies.readArtifactInput(args.contextSource)
35
+ : await context.readInput(args.contextSource, MAX_WATCHLIST_IMPORT_BYTES, 'Candidate watch context')) : null;
36
+ const watchSelection = handoff?.selection;
37
+ if (watchSelection && watchSelection.candidates.length)
38
+ throw new CliUsageError('The watch-input context must have an empty candidates array; only reviewed feed matches may be nominated.');
39
+ const selection = normalizeDomainFeedSelection({ ...args.selection, brandProfileId: watchSelection?.brandProfileId ?? null });
40
+ const review = await scanDomainFeed((dependencies.readDomainFeedInput ?? readDomainFeedFile)(args.source, dependencies.signal), {
41
+ feedId: args.feedId, selection, importedAt: context.now(), ...(dependencies.signal ? { signal: dependencies.signal } : {}),
42
+ });
43
+ const document = watchSelection && handoff
44
+ ? buildDomainFeedWatchInput(review, review.matches.map(match => match.domain), watchSelection, handoff.watchlists)
45
+ : review;
46
+ if (!args.quiet)
47
+ context.writeStdout(formatJsonDocument(document));
48
+ return EXIT_CODES.SUCCESS;
49
+ }
20
50
  function boundWorkflowInputs(command, inputs, dependencies, context) {
21
51
  if (!inputs.size)
22
52
  return {};
@@ -181,6 +211,8 @@ async function runWorkflowRecipeCommand(args, dependencies, context) {
181
211
  return investigationRunExitCode(document);
182
212
  }
183
213
  const WORKFLOW_COMMAND_HANDLERS = Object.freeze({
214
+ 'domain-feed': runDomainFeedCommand,
215
+ 'watchlist-review': runWatchlistReviewCommand,
184
216
  'monitor-once': runMonitorOnceCommand,
185
217
  'workflow-plan': runWorkflowPlanCommand,
186
218
  'workflow-run': runWorkflowRecipeCommand,
@@ -71,11 +71,11 @@ candidate, form destination and deliberately observed navigation separately
71
71
  attributed. A similar page without a logo or brand name can still merit manual
72
72
  evidence review; correspondence does not establish authorship, rights authority
73
73
  or maliciousness. Missing optional capture or comparison evidence does not
74
- prevent ordinary Case pins and conclusions. Installed command help remains the
74
+ prevent ordinary case pins and conclusions. Installed command help remains the
75
75
  authority for offline file inputs and explicit collection plans.
76
76
 
77
77
  The [reporting guide](https://www.whoisleuth.com/resources/reporting-and-takedown-guidance)
78
- uses the same reviewed official-route catalogue as the Console for social
78
+ uses the same reviewed official-route catalogue as the console for social
79
79
  content, storefronts, app listings, hosted files/forms and advertising. Verify
80
80
  the exact object, current route, reporter authority and disclosure before any
81
81
  manual submission. Fraud, order complaints and rights notices are different
@@ -96,8 +96,10 @@ PKIX, STARTTLS and SMTP transport remain separate evidence states.
96
96
  STARTTLS detection uses the full bounded reply. If the retained capability
97
97
  inventory is shortened, the report remains partial and names the omission.
98
98
 
99
- The repository-only rendered-capture package is outside hosted and distributable
100
- collection. It executes remote page JavaScript only for an explicitly
99
+ The separate optional rendered-capture companion is distributed as a locally
100
+ assembled archive, not as part of the hosted application or main CLI, and is
101
+ not published to a package registry. Its sandboxed, bounded browser executes
102
+ remote page JavaScript only for an explicitly
101
103
  authorised route set, and each admitted resource operator receives the exact
102
104
  requested URL. Captures and screenshots remain local until the operator deletes
103
105
  them.
@@ -143,18 +145,46 @@ Readers accept only their declared public and current versions. Unknown,
143
145
  unreleased historical and future schemas fail before partial interpretation;
144
146
  an invalid import is not treated as an empty document.
145
147
 
146
- `inspect-archive` reads current workspace archive v9 and exact versions 5–8. It
148
+ `inspect-archive` reads current workspace archive v10 and exact versions 5–9. It
147
149
  reports section metadata and digest-only search results unless `--reveal` is
148
150
  explicitly selected. It never searches notes, contacts or arbitrary raw fields.
149
151
 
150
- `export` reads supported saved Lookup v1 or v2 and writes current Lookup
151
- evidence schema 29. Published v2 schemas 27 and 28 and exact v1 schema 26 remain
152
- readable. Versions 27–29 exclude raw registration payloads, expanded
152
+ `export` reads supported saved Lookup versions 1–3 and writes current Lookup
153
+ evidence schema 31. Supported schemas 27–30 and exact v1 schema 26 remain
154
+ readable. Versions 27–31 exclude raw registration payloads, expanded
153
155
  contacts, credentials, complete query-bearing URLs and provider payloads;
154
- schemas 28 and 29 also retain the bounded registrar-standing projection.
156
+ Schemas 28 and later also retain the bounded registrar-standing projection.
155
157
  Schema 29 identifies the hostname used for DNS, TLS and web observations,
156
158
  separately from the registrable domain used by registration sources. Older
157
159
  evidence retains its original collection scope.
160
+ Schema 31 retains WHOIS registration classification separately from collection
161
+ completion. Lifecycle interpretation requires positive registration evidence;
162
+ a completed negative or inconclusive WHOIS reply does not establish registration
163
+ or absence of locks. Earlier exports retain their exact original interpretation
164
+ for historical verification and are not silently relabelled.
165
+
166
+ New WHOIS collection distinguishes confirmed no-service discovery from an
167
+ incomplete discovery response. Empty, refused or unrecognised bootstrap
168
+ responses remain partial; they do not establish that WHOIS is unsupported.
169
+ `--strict-exit` treats that incomplete attempt as a source failure. Explicit
170
+ no-request access-policy restrictions remain distinct from transport failures.
171
+
172
+ Saved Lookup version 3 can retain a bounded collection receipt: original
173
+ optional selections, admitted source families, collector starts, outcomes and
174
+ explicitly known completeness. `lookup --verbose` and `lookup --browse`
175
+ show this account with source-specific explanations and qualified retry guidance.
176
+ Earlier files have no inferred receipt. The receipt contains
177
+ no target values, URLs, contacts or raw errors and does not prove network
178
+ delivery. It stays in full private Lookup JSON; the minimised `export` report
179
+ does not include it. `--plan` remains offline and produces no completed receipt.
180
+
181
+ Case schema 20 also retains this context when an analyst saves a completed Lookup
182
+ assessment or selected checkpoint facts. A selected checkpoint saves only its
183
+ selected pins and one payload-free receipt, not every Lookup result. Ordinary
184
+ backups and internal case packs preserve it. Trusted/public case packs, case
185
+ reports and response packets omit receipts; shared assessment identities are
186
+ recomputed without them. Historical case formats remain readable without
187
+ inventing missing collection choices.
158
188
 
159
189
  `verify-artifact` checks a recognised structure and its applicable integrity
160
190
  contract. `interchange-report` describes retained and omitted fields.
@@ -162,6 +192,14 @@ contract. `interchange-report` describes retained and omitted fields.
162
192
  validity and signer trust separate. None of these checks establishes that
163
193
  evidence is accurate, current, safe to share or attributable to a person.
164
194
 
195
+ The text output of `verify-artifact` suggests compatible offline follow-up
196
+ commands for recognised saved Lookups, case exports, workspace archives,
197
+ Lookup-evidence reports, Case-packs and managed indicator revisions. These are
198
+ examples with placeholder filenames, not executed actions or a sharing approval.
199
+ Replace the filenames deliberately; a case or indicator inspection can display
200
+ private contents. Encrypted workspace guidance uses a separate passphrase file,
201
+ never a passphrase in command history. JSON output and `--quiet` are unchanged.
202
+
165
203
  ### Signer trust
166
204
 
167
205
  Use `verify-signature package.json --trust-store-file trust.json --json` to
@@ -211,9 +249,9 @@ own retention policy. No automatic key discovery, key storage or trust refresh o
211
249
  control, activity, intent or maliciousness.
212
250
  - Bulk applies one declared collection contract per target. Fast accepts up to
213
251
  500 targets and Deep up to 50; each target remains a separate request.
214
- - Respond commands package browser-created Cases and prepare local packets,
252
+ - Respond commands package browser-created cases and prepare local packets,
215
253
  reports and sharing reviews. The offline [`case` workflow](cli.md#local-case-files)
216
- also creates and updates ordinary local Case files through explicit, conflict-checked
254
+ also creates and updates ordinary local case files through explicit, conflict-checked
217
255
  output. Neither workflow submits, publishes, notifies or grants recipient authorisation.
218
256
  - Assurance, comparison and calibration commands describe supplied or retained
219
257
  evidence. They do not tune the running model, change infrastructure or turn an
package/docs/cli.md CHANGED
@@ -12,6 +12,11 @@ tasks, then offers searchable commands with examples, option explanations and
12
12
  defaults. Command-section links can be bookmarked. Its option guidance and
13
13
  installed help derive from the same command definitions.
14
14
 
15
+ Not sure which interface to use? The [installation-mode comparison](https://www.whoisleuth.com/cli#choose-mode)
16
+ covers the hosted console, filesystem-local application, CLI and capture companion,
17
+ including request origin, storage, offline use and distribution. The optional
18
+ application and capture archives are separate from the public CLI package.
19
+
15
20
  The generated [privacy and data-flow catalogue](https://github.com/slicedearth/whoisleuth/blob/main/docs/privacy-data-flow-catalogue.md)
16
21
  lists the network, recipient, retention and export boundary for every command.
17
22
 
@@ -75,22 +80,35 @@ private document from the interactive view:
75
80
 
76
81
  ```bash
77
82
  whoisleuth lookup example.test --deep --browse --save-lookup lookup.json
83
+ whoisleuth verify-artifact lookup.json --json
84
+ whoisleuth export lookup.json --output evidence.json
85
+ whoisleuth verify-artifact evidence.json --json
78
86
  ```
79
87
 
80
- In Console Lookup, open **Replay exported evidence**, select the file, verify
81
- its digest and source states, then create or update a browser-local Case. The
82
- file is not uploaded. Case classification, exact incident links and response
83
- actions remain deliberate analyst steps. Ordinary Case files also support the
88
+ Keep `lookup.json` as the private CLI original. In Lookup in the console, open **Replay
89
+ exported evidence**, select `evidence.json`, check its digest and source states,
90
+ then create or update a browser-local case. The private CLI document is not a
91
+ browser replay format; the separate evidence export excludes raw registration
92
+ payloads and expanded contacts. Neither file is uploaded. Case classification, exact incident links and response
93
+ actions remain deliberate analyst steps. Ordinary case files also support the
84
94
  offline CLI workflow below.
85
- When several incident Cases share a domain, select the intended Case before
86
- retaining replay evidence. Case packs preserve each current Case ID separately;
95
+ When several incident cases share a domain, select the intended case before
96
+ retaining replay evidence. Case packs preserve each current case ID separately;
87
97
  trusted and public packs exclude analyst-entered incident titles.
88
98
  Every audience retains tags, decision summaries and rationale as well as domain
89
- evidence and Case, pin and decision IDs. Public does not mean anonymous.
99
+ evidence and case, pin and decision IDs. Public does not mean anonymous.
90
100
  Browser Lookup evidence can be verified or packaged in the CLI. `brief`,
91
101
  `export`, `compare`, `registry-doctor` and `source-report` instead require a
92
102
  saved CLI Lookup; they do not reconstruct it from a browser export.
93
103
 
104
+ Registration source states are independent: `unsupported` means no RDAP or
105
+ WHOIS service is published for the target, while `skipped` means no query was
106
+ issued in that mode or under the registry access policy. Neither is a transport
107
+ failure or a domain-absence finding. Standard terminal output explains these
108
+ non-query outcomes; JSON and evidence exports retain their separate states.
109
+ `--strict-exit` does not fail solely because a protocol is unsupported or skipped,
110
+ but continues to report genuine failed or partial sources.
111
+
94
112
  Process selected local input:
95
113
 
96
114
  ```bash
@@ -106,6 +124,18 @@ One strict domain, IP address or ASN can occupy command position as Lookup
106
124
  shorthand. URL-like or ambiguous input requires the explicit `lookup` command.
107
125
  Only `bulk` accepts multiple targets.
108
126
 
127
+ For a downloaded plain-domain feed, use
128
+ `whoisleuth domain-feed review nrd7 feed.txt --select term:example --json`.
129
+ Repeat `--select exclude:literal` to veto a literal substring before the result
130
+ bound, including for exact-host selections. At most 20 positive and 20 negative
131
+ terms of 3–80 characters are supported; at least one positive term or exact host
132
+ is required. Matching uses canonical ASCII/punycode text without token, typo,
133
+ confusable or regular-expression expansion. These offline selectors do not infer
134
+ saved browser campaign identity or default priority.
135
+ This is an offline literal match, not a feed download or target Lookup. The
136
+ [candidate-review guide](https://github.com/slicedearth/whoisleuth/blob/main/docs/brand-candidate-review.md#review-a-domain-feed) covers
137
+ source attribution and the separate Watchlist handoff.
138
+
109
139
  `discover` accepts a brand label or registrable domain and supports multi-part
110
140
  public suffixes in `--tlds` (for example, `co.uk,com.au`). It preserves the full
111
141
  suffix and does not collect candidate evidence; use `discover-scan` only after
@@ -132,6 +162,8 @@ For an explicit inherited-policy and direct parent-delegation review, use
132
162
  option, posture collection keeps its existing exact-name scope. The extra
133
163
  results distinguish recursive policy publication from sampled parent referrals;
134
164
  they do not establish name existence, message authentication or DNSSEC validity.
165
+ The machine-readable posture document keeps its existing versioned fields;
166
+ the website's transient structured policy scope is not included in that export.
135
167
 
136
168
  | Mode | Behaviour |
137
169
  | --- | --- |
@@ -146,6 +178,16 @@ their focused help. They do not use the hosted login or hosted usage controls.
146
178
  `lookup --plan` lists planned source families and disclosure targets before
147
179
  collection. `doctor` is offline unless `--network` is selected.
148
180
 
181
+ For troubleshooting that you intend to share, use
182
+ `whoisleuth doctor --summary-json > diagnostics.json`. This summary keeps numeric
183
+ CLI/Node versions, fixed check codes and public format identifiers from the command
184
+ catalogue. Those identifiers describe declared formats, not a compatibility test.
185
+ Targets, URLs, filenames, paths, user names, credentials, headers, timestamps and
186
+ free-form diagnostic details are omitted. Version prerelease/build metadata is
187
+ also omitted. Normal terminal and `--json` diagnostics can include error details;
188
+ inspect them before sharing. The summary does not enable network checks or upload
189
+ anything, and it has the same success/partial exit behaviour as the detailed report.
190
+
149
191
  Deep Lookup reports IPv4 and IPv6 results separately, including empty answers,
150
192
  NODATA, resolver-reported missing names and query failures. These outcomes remain
151
193
  distinct in evidence exports; none alone establishes domain removal.
@@ -179,6 +221,30 @@ Excluded links and unsupported QR payloads make the review partial, with
179
221
  category counts rather than private payload text. Bare QR hostnames are not
180
222
  automatically treated as URLs.
181
223
 
224
+ Current message-intake reports also retain bounded literal IPv4/IPv6 and
225
+ explicitly labelled MD5, SHA-1 and SHA-256 observations from supported body and
226
+ document text. Each observation links to its selected source part and digest;
227
+ it is not a Lookup target, file identification or threat verdict. Complete URLs,
228
+ message headers, QR payloads, HAR and identity-event fields are excluded from
229
+ this text scan. Historical version-1 reports do not acquire new observations.
230
+
231
+ Use `intake text selected.txt --intake-context context.json --json` to add an
232
+ explicit analyst-supplied distribution declaration. The separate context file
233
+ is limited to 8 KiB and cannot use stdin. Its channel, non-sensitive source
234
+ label and optional declared time, reference, observer and vantage labels are
235
+ included in the output, not verified as delivery or capture conditions. See
236
+ [selected-input indicators and declarations](offline-intake-context.md) for the
237
+ exact schema, privacy exclusions and extraction bounds.
238
+
239
+ Plain-text phone discovery reports only candidate counts, the source digest and
240
+ UTF-16 selection ranges to standard error; unselected numbers do not enter JSON
241
+ output. Use a version-2 `--intake-context` file to select exact ranges and declare
242
+ their source, basis, role and known time. The same optional file can supply a
243
+ displayed/destination pair for local hostname comparison. See the
244
+ [selection example](offline-intake-context.md#select-phone-observations-in-the-cli).
245
+ `--quiet` suppresses discovery guidance, and `--strict-exit` reports partial
246
+ phone coverage as well as other incomplete intake work.
247
+
182
248
  ### Local IP-location database review
183
249
 
184
250
  `review-evidence query.json --mmdb selected.mmdb --json --strict-exit` reads
@@ -227,12 +293,29 @@ metadata or freshness admission; terminal output identifies that limitation.
227
293
 
228
294
  ### Contextual reviews
229
295
 
296
+ `review-evidence` reviews bounded source-qualified infrastructure snapshots and explicit earlier/later comparisons offline. See [infrastructure observations](infrastructure-observations.md) for supported inputs and how to interpret their outcomes.
297
+
230
298
  `review-evidence` also accepts [contextual review inputs](contextual-reviews.md)
231
299
  for source-qualified incident sequences, retained domain history, platform-object continuity, authorised storefront
232
300
  comparison and connector provenance. These workflows are offline; partial
233
301
  context reviews return 4 when `--strict-exit` is selected. Browser-generated
234
302
  reusable inputs use the same validators.
235
303
 
304
+ Domain-history input version 2 adds analyst-declared registration, deletion,
305
+ re-registration, transfer or review boundaries linked to retained snapshot or
306
+ pin IDs. Version 1 remains supported with its original output. Boundaries prompt
307
+ reassessment; they do not establish ownership or discard prior evidence.
308
+
309
+ `review-evidence containment.json --json --strict-exit` accepts a
310
+ `whoisleuth.internal-containment.input` version-1 document containing a supported
311
+ case export, selected next-step assertion and linked pin IDs, internal or trusted
312
+ audience, recipient role and an explicit disclosure-review acknowledgement.
313
+ It exports only that selected handoff, never writes the case or performs a
314
+ control. Missing or unselected supporting context stays partial (exit 4 under
315
+ `--strict-exit`), and a resolved case or provider outcome does not close an open
316
+ internal follow-up. See [internal containment](offline-intake-context.md#selected-internal-containment-handoff)
317
+ for the complete input and audience limitations.
318
+
236
319
  ### Message-header review
237
320
 
238
321
  `mail-headers` parses only the bounded header block from a selected message file
@@ -282,9 +365,48 @@ selected file, including an envelope when supplied. No provider API is contacted
282
365
 
283
366
  ## Output and automation
284
367
 
285
- ### Local Case files
286
-
287
- Create a working file, inspect its Case IDs, then append a note:
368
+ ### Local case files
369
+
370
+ Manual response scope is explicit and offline. `case incident-link` takes
371
+ `{"url":"https://example.test/reported-page"}`; use its retained ID to bind
372
+ `responseObjects` on `case action`. Each object contains `kind`, `identifier`
373
+ and `incidentTargetId` (null for the case domain or an associated hostname).
374
+ New URL objects must match that exact retained link. Exact historical identities
375
+ already retained in response records remain selectable after a link edit or
376
+ removal; they are not rebound to its replacement. An action may bind up to 20
377
+ objects; missing historical scope stays unknown.
378
+
379
+ `case action-event` takes `{"id":"action-id","transition":{...}}`, using
380
+ the same reviewed, authorised and manually submitted workflow as the console.
381
+ After submission, a typed `objectOutcome` such as `removed`, `restricted`,
382
+ `suspended`, `delisted`, `restored` or `disputed` requires explicit event
383
+ `responseObjects`: a partial receipt never applies to all objects by default.
384
+ Acknowledgement is a workflow event, not independently observed remediation.
385
+
386
+ A self-initiated correction uses a new `case action`, with `originActionId`
387
+ identifying the original action and `responseObjects` selecting a subset of that
388
+ delivery's exact objects. Its `correction` block identifies one delivery event,
389
+ packet digest/version and profile, plus the purpose (`correction` or
390
+ `retraction_request`), reason, previous and corrected statements, and retained
391
+ evidence-pin IDs. This is separate from `amendment`, which still requires an
392
+ actual provider-request event. The correction must match a retained typed packet
393
+ receipt on the selected delivery; a historical free-text digest alone is
394
+ insufficient. The usual recipient, authority and disclosure review then applies
395
+ to the new draft. The original packet and delivery remain unchanged.
396
+
397
+ `case recheck-question` saves `statement` and `recheck` comparison conditions.
398
+ Pins, questions and recheck input can retain one `responseObject`; exact-object
399
+ non-reproduction requires matching complete baseline/current evidence, a later
400
+ observation and comparable conditions. Hostname similarity is insufficient.
401
+ Independent removal, restriction, suspension, delisting, transfer and restoration
402
+ use those same comparison requirements. Disputes and warnings retain procedural
403
+ source attribution separately; provider claims are not independent observations.
404
+ `case close-object` requires one `responseObject` and the usual typed closure
405
+ reason/evidence links. It leaves the case and other objects unchanged.
406
+ All these JSON operations require `--input`, `--output`, deliberate file
407
+ replacement and the existing digest/lease checks. No report or request is sent.
408
+
409
+ Create a working file, inspect its case IDs, then append a note:
288
410
 
289
411
  ```sh
290
412
  whoisleuth case open --domain example.test --title "Review the selected form" --output cases.json
@@ -292,8 +414,8 @@ whoisleuth case show cases.json
292
414
  whoisleuth case note cases.json --note-file note.txt --output cases.json --force
293
415
  ```
294
416
 
295
- Use `--case-id` when the file contains several Cases. `open` reuses the selected
296
- Case; `--new-incident --title "Another incident"` creates a distinct ID for the
417
+ Use `--case-id` when the file contains several cases. `open` reuses the selected
418
+ case; `--new-incident --title "Another incident"` creates a distinct ID for the
297
419
  same domain. It does not open a browser or collect anything.
298
420
 
299
421
  `pin`, `link`, `withdraw-link`, `assess` and `recheck` read a selected JSON file with `--input`:
@@ -350,23 +472,23 @@ A recheck input contains `state`, `observedAt`, `completeness`, `source` and
350
472
  `comparisonSummary`, with optional limitations and follow-up time. For example,
351
473
  use `state: "unavailable"` and `completeness: "partial"` when a supplied capture
352
474
  did not complete. `not_reproduced` requires a saved recheck question from the
353
- Case's console workflow, its exact `recheck` context, the target
475
+ case's console workflow, its exact `recheck` context, the target
354
476
  `observationHostname`, a complete later observation where a baseline exists,
355
477
  and `conditionsMatch: "comparable"`. It never means global removal or takedown.
356
478
 
357
- Mutations require an explicit output path and write the complete current Case
479
+ Mutations require an explicit output path and write the complete current case
358
480
  export. They never prune old evidence to fit. Input formatting may use up to
359
- 16 MiB; the canonical store remains limited to 4 MiB and 500 Cases. `show --json`
360
- can export a selected subset to a **different** path. Ordinary Case imports also
481
+ 16 MiB; the canonical store remains limited to 4 MiB and 500 cases. `show --json`
482
+ can export a selected subset to a **different** path. Ordinary case imports also
361
483
  allow 16 MiB for formatting and export metadata; the stored-data limit remains
362
- 4 MiB. Case packs retain their separate 25-Case selection and 4-MiB source-file
484
+ 4 MiB. Case packs retain their separate 25-case selection and 4-MiB source-file
363
485
  limits, and include generated reports within the bounded import allowance.
364
486
 
365
487
  `show` reports the exact file digest. Add `--expect-file-digest sha256:<digest>`
366
488
  to reject a file changed since that review. Source/output locks and atomic
367
489
  replacement also detect changes during execution. After an interrupted process,
368
490
  inspect its adjacent `.workflow.lock` before deliberately removing it. Validation
369
- or conflict failures leave the Case file unchanged and return 2. Filesystem
491
+ or conflict failures leave the case file unchanged and return 2. Filesystem
370
492
  publication failures return 3.
371
493
 
372
494
  Working files contain private analyst content and file references, not attachment
@@ -386,21 +508,26 @@ whoisleuth manifest evidence.json screenshot.png --workflow "Evidence review" \
386
508
  whoisleuth verify-artifact --folder ./evidence-project --json --strict-exit
387
509
  ```
388
510
 
389
- Packages admit the 128-file, 64-MiB original selection plus one bounded Case
511
+ Packages admit the 128-file, 64-MiB original selection plus one bounded case
390
512
  export, with a separate manifest allowance. Original-file limits remain unchanged.
391
- `verify-artifact` uses the same exact ordinary Case reader as `case show`,
513
+ `verify-artifact` uses the same exact ordinary case reader as `case show`,
392
514
  without repairing content. Package verification also reports original-reference
393
- completeness for each Case file; `--strict-exit` returns 4 when a referenced original is missing.
394
- An ordinary Case JSON file alone provides structural validity, not a checksum.
515
+ completeness for each case file; `--strict-exit` returns 4 when a referenced original is missing.
516
+ An ordinary case JSON file alone provides structural validity, not a checksum.
395
517
  They use generated entry names, not original paths. Ordinary ZIPs and folders
396
518
  are private and unencrypted; packaging does not redact selected files. The report distinguishes
397
519
  file identity, supported source formats, opaque content, exact capsule/source
398
- links and capture-manifest attachment matches. Include the capture manifest and
520
+ links, declared image derivation and capture-manifest attachment matches.
521
+ Image derivation reports an immediate-parent digest and byte length, editing
522
+ method and operation kinds, not proof of transformation. Parent inclusion matches
523
+ another selected manifest entry by exact byte digest and size; an unselected case
524
+ attachment reference does not count. Parent pixels are optional; absent declarations
525
+ leave editing history unknown. Include the capture manifest and
399
526
  its screenshot/DOM-digest files together to check their declared bytes; original
400
527
  filenames are not needed to establish a match. It does not import files or establish source truth, signature trust or a
401
528
  trusted timestamp. Unsupported or rejected entries produce a partial report;
402
529
  `--strict-exit` returns 4. Without `--package` or `--folder`, `manifest` produces a
403
- standalone JSON manifest; exact public version-2 manifests remain readable.
530
+ standalone JSON manifest; exact version-2 and version-3 manifests remain readable.
404
531
 
405
532
  For [BagIt 1.0](https://www.rfc-editor.org/rfc/rfc8493.html) interchange, use
406
533
  `--bagit` with `--package` or `--folder` on both commands:
@@ -454,6 +581,35 @@ trees and unlisted files. Its package digest and byte count describe the
454
581
  canonical stored-ZIP representation, not filesystem metadata. These are
455
582
  selected evidence exports, not complete workspace backups.
456
583
 
584
+ ### Exporting a fixed workflow for review
585
+
586
+ Terminal plans and recipe explanations distinguish networked steps, offline
587
+ steps and declared reuse of earlier outputs. These are step and handoff counts,
588
+ not request totals or confirmation that a saved file is available. The planner
589
+ does not inspect files or caches; collection and evidence selection remain
590
+ deliberate actions. JSON exports keep their existing versioned fields.
591
+
592
+ Select one installed recipe and save its declared steps for review:
593
+
594
+ ```sh
595
+ whoisleuth workflow-plan --explain domain-triage --json --output recipe.json
596
+ ```
597
+
598
+ The document includes the subject requirement, command and argument examples,
599
+ network modes, required approvals, output artefact families and completion guidance.
600
+ To prepare the same recipe for a specific subject:
601
+
602
+ ```sh
603
+ whoisleuth workflow-plan domain-triage example.test --json --output plan.json
604
+ ```
605
+
606
+ Both commands plan offline. The files contain declarative steps rather than
607
+ shell history or executable scripts. Argument examples and approval fields
608
+ describe requirements; they do not record a completed review or grant approval.
609
+ These are review documents, not `workflow-run` checkpoints or custom recipe
610
+ imports. Run an installed recipe deliberately with its required inputs and
611
+ fresh approvals.
612
+
457
613
  ### Resuming a fixed workflow
458
614
 
459
615
  Inspect a paused run without executing any step or rewriting its checkpoint:
@@ -486,7 +642,7 @@ whoisleuth workflow-run domain-triage example.test --approve-network \
486
642
  ```
487
643
 
488
644
  Bindings accept Lookup outputs for export, diff, timeline, comparison, source
489
- reports and briefs; evidence exports for verification; and Case-pack output
645
+ reports and briefs; evidence exports for verification; and case-pack output
490
646
  for sharing review. Remaining placeholders use `--select` in order;
491
647
  for example, `--select diff=previous.json` keeps the current observation as the
492
648
  second input. Supply every input for an uncompleted step to use files instead,
@@ -601,6 +757,9 @@ and machine output contains no ANSI or transient progress text.
601
757
  - `lookup --browse` provides an interactive terminal view; press `?` for keys.
602
758
  - `--events` writes versioned lifecycle events to stderr.
603
759
  - `--strict-exit` and `--fail-on` expose selected evidence states to automation.
760
+ - `lookup --junit` reports retained diagnostic source states, not version or
761
+ timing metadata. Nested diagnostic failures remain failures; target-bearing
762
+ diagnostic paths are not copied into the XML. This is not a truth verdict.
604
763
  - `--output` writes a private local file atomically and refuses an existing path
605
764
  unless `--force` is also selected.
606
765
 
@@ -611,7 +770,22 @@ options override profile defaults.
611
770
 
612
771
  `source-report` summarises collector-owned health envelopes in saved Lookup
613
772
  and Bulk output. Raw publications and extension fields are not interpreted as
614
- diagnostics, and missing health metadata remains unmeasured.
773
+ diagnostics, and missing health metadata remains unmeasured. Its text view groups
774
+ collection states, sample counts and durations under each source. Observation
775
+ durations and Lookup timings stay separate; merged-report distributions describe
776
+ retained report statistics, not a new measurement of individual requests.
777
+
778
+ After verifying a domain-control or Lookup-claim passport, `verify-artifact`
779
+ suggests the compatible offline `interchange-report` command. Suggestions use
780
+ example filenames and never run automatically. Other artefact families receive
781
+ guidance only when the suggested reader supports that format.
782
+
783
+ The text output of `brief` groups selected facts in an indented source tree.
784
+ Each source keeps its collection state and observation time, including sources
785
+ with no fact selected for the brief; that does not mean the source was empty.
786
+ Conflicting values remain separate and include both the value state and source
787
+ state. The brief uses retained evidence only. Use `--json` for the
788
+ structured report, including its facts, source health, unknowns and action plan.
615
789
 
616
790
  `mail-review` keeps failed input targets, incomplete DNS coverage and original
617
791
  collection times visible. Source-output and review-generation times do not