@slicedearth/whoisleuth-cli 2.5.1 → 2.6.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 (122) hide show
  1. package/DISCLOSURE +1 -1
  2. package/cli/artifact-validation/case-response.mjs +51 -8
  3. package/cli/artifact-validation/lookup-evidence.mjs +15 -1
  4. package/cli/artifact-validation/signed-review.mjs +2 -51
  5. package/cli/artifact-verify.mjs +9 -2
  6. package/cli/bulk-command-runner.mjs +4 -6
  7. package/cli/case-command.mjs +79 -3
  8. package/cli/case-pack.mjs +7 -2
  9. package/cli/command-definition.mjs +2 -1
  10. package/cli/discovery-scan-command-runner.mjs +4 -6
  11. package/cli/domain-feed.mjs +61 -0
  12. package/cli/formatters/json.mjs +1 -13
  13. package/cli/indicator-set-command.mjs +1 -2
  14. package/cli/intake-command.mjs +43 -3
  15. package/cli/investigation-artifacts.mjs +2 -3
  16. package/cli/investigation-package-review.mjs +2 -0
  17. package/cli/local-mmdb-review.mjs +27 -4
  18. package/cli/lookup-command-runner.mjs +1 -0
  19. package/cli/offline-evidence-review.mjs +51 -5
  20. package/cli/review-arguments.mjs +3 -2
  21. package/cli/review-command-definitions.mjs +14 -8
  22. package/cli/review-command-runner.mjs +6 -3
  23. package/cli/risk-calibration.mjs +6 -4
  24. package/cli/selected-input-worker.mjs +12 -2
  25. package/cli/support-command-runner.mjs +3 -3
  26. package/cli/watchlist-review.mjs +43 -0
  27. package/cli/workflow-arguments.mjs +14 -0
  28. package/cli/workflow-command-definitions.mjs +26 -1
  29. package/cli/workflow-command-runner.mjs +32 -0
  30. package/docs/cli.md +99 -2
  31. package/docs/contextual-reviews.md +28 -5
  32. package/docs/infrastructure-observations.md +126 -0
  33. package/docs/offline-intake-context.md +213 -0
  34. package/lib/credential-form-attribution.mjs +136 -0
  35. package/lib/credential-surface-profile.mjs +6 -2
  36. package/lib/dns-delegation-health.mjs +23 -7
  37. package/lib/dns-intelligence.mjs +18 -4
  38. package/lib/domain-posture-analysis.mjs +53 -2
  39. package/lib/domain-posture-parsers.mjs +9 -0
  40. package/lib/domain-posture.mjs +29 -10
  41. package/lib/evidence-export-privacy.mjs +5 -2
  42. package/lib/evidence-report.mjs +1 -0
  43. package/lib/favicon.mjs +13 -4
  44. package/lib/html-signals.mjs +6 -0
  45. package/lib/lookup-child-profile-contract.mjs +28 -11
  46. package/lib/lookup-response-contract.mjs +1 -0
  47. package/lib/operation-budget.mjs +4 -0
  48. package/lib/service-binding-dns.mjs +19 -2
  49. package/lib/sslbl-certificates.generated.mjs +138 -137
  50. package/lib/static-html-analysis.mjs +32 -4
  51. package/lib/technology-evidence-role.mjs +1 -51
  52. package/lib/tls-intelligence.mjs +30 -5
  53. package/lib/website-enrichment.mjs +39 -23
  54. package/lib/whois-chain.mjs +12 -3
  55. package/lib/whois-common-formats.mjs +3 -1
  56. package/lib/whois-contacts.mjs +13 -2
  57. package/package.json +7 -5
  58. package/packages/analysis/risk-scoring.mjs +12 -9
  59. package/packages/cases/case-containment-handoff.mjs +195 -0
  60. package/packages/cases/case-evidence-links.mjs +16 -0
  61. package/packages/cases/case-evidence-model.mjs +14 -3
  62. package/packages/cases/case-object-coverage.mjs +33 -0
  63. package/packages/cases/case-packet-correction.mjs +106 -0
  64. package/packages/cases/case-recheck-model.mjs +35 -5
  65. package/packages/cases/case-record-operations.mjs +74 -9
  66. package/packages/cases/case-record-projection.mjs +15 -3
  67. package/packages/cases/case-record-version-input.mjs +23 -1
  68. package/packages/cases/case-report.mjs +30 -0
  69. package/packages/cases/case-requested-evidence.mjs +1 -1
  70. package/packages/cases/case-response-actions.mjs +57 -1
  71. package/packages/cases/case-response-model.mjs +7 -0
  72. package/packages/cases/case-response-object.mjs +119 -0
  73. package/packages/cases/case-response-outcomes.mjs +81 -14
  74. package/packages/cases/case-response-packet-render.mjs +22 -3
  75. package/packages/cases/case-response-packet.mjs +30 -2
  76. package/packages/cases/case-response-review-inputs.mjs +48 -7
  77. package/packages/cases/case-workflow-metadata.mjs +1 -9
  78. package/packages/contracts/analyst-interchange.mjs +7 -5
  79. package/packages/contracts/candidate-watch-review.mjs +54 -0
  80. package/packages/contracts/case-portability.mjs +260 -42
  81. package/packages/contracts/cli-command-semantics.mjs +3 -0
  82. package/packages/contracts/context-review.mjs +2 -1
  83. package/packages/contracts/external-observation-interchange.mjs +96 -28
  84. package/packages/contracts/lookup-evidence.mjs +3 -2
  85. package/packages/contracts/message-intake.mjs +7 -1
  86. package/packages/contracts/risk-calibration.mjs +44 -29
  87. package/packages/contracts/schema-lifecycle.mjs +3 -1
  88. package/packages/contracts/web-capture.mjs +1 -1
  89. package/packages/contracts/workspace-portability.mjs +86 -16
  90. package/packages/evidence/collection-quality.mjs +1 -1
  91. package/packages/evidence/credential-form-attribution.mjs +60 -0
  92. package/packages/evidence/image-regions.mjs +15 -0
  93. package/packages/evidence/record-identity.mjs +60 -16
  94. package/packages/evidence/technology-evidence-role.mjs +51 -0
  95. package/packages/interchange/external-findings-import.mjs +32 -17
  96. package/packages/interchange/json-output.mjs +14 -0
  97. package/packages/interchange/managed-indicator-export.mjs +3 -2
  98. package/packages/interchange/managed-indicator-set.mjs +13 -0
  99. package/packages/investigation/acquisition-decision-packet.mjs +78 -0
  100. package/packages/investigation/context-review.mjs +3 -3
  101. package/packages/investigation/document-intake.mjs +9 -3
  102. package/packages/investigation/domain-history-review.mjs +43 -11
  103. package/packages/investigation/domain-transition-review.mjs +2 -0
  104. package/packages/investigation/infrastructure-observation.mjs +234 -0
  105. package/packages/investigation/intake-context.mjs +284 -0
  106. package/packages/investigation/intake-indicators.mjs +116 -0
  107. package/packages/investigation/intake-phones.mjs +155 -0
  108. package/packages/investigation/intake-report.mjs +2 -1
  109. package/packages/investigation/investigation-manifest.mjs +33 -7
  110. package/packages/investigation/link-intake.mjs +38 -8
  111. package/packages/investigation/message-intake.mjs +55 -15
  112. package/packages/monitoring/domain-feed.mjs +321 -0
  113. package/packages/workspace/brand-candidate-workflow.mjs +351 -0
  114. package/packages/workspace/brand-keyword-campaign.mjs +231 -0
  115. package/packages/workspace/brand-profile-model.mjs +46 -2
  116. package/packages/workspace/bulk-session-model.mjs +4 -0
  117. package/packages/workspace/candidate-watch-handoff.mjs +195 -0
  118. package/packages/workspace/detection-rule-model.mjs +10 -2
  119. package/packages/workspace/shortlist-model.mjs +10 -2
  120. package/packages/workspace/watchlist-history.mjs +31 -4
  121. package/packages/workspace/watchlist-store.mjs +136 -7
  122. package/third-party-notices.txt +29 -29
@@ -1,9 +1,12 @@
1
1
  // Exact, bounded validation of current and published response-review inputs.
2
2
  import { readCaseEvidenceRequest, readCasePacketAmendment, assertEvidenceRequestEvent, assertEvidenceRequestHistory, assertPacketAmendmentSelection } from "./case-requested-evidence.mjs";
3
+ import { readCaseDeliveryPacketReceipt, readCasePacketCorrection, assertDeliveryPacketReceipt, correctionDelivery } from "./case-packet-correction.mjs";
4
+ import { CASE_SELECTED_EVIDENCE_SOURCE_LIMITATION } from "./case-evidence-links.mjs";
3
5
  import { CASE_RESPONSE_REVIEW_INPUTS_SCHEMA, MAX_ABUSE_CATEGORY_LENGTH, MAX_ABUSIVE_URLS, MAX_AFFECTED_PARTY_LENGTH, MAX_CASE_ACTIONS, MAX_CASE_ACTION_EVENTS_PER_ACTION, MAX_CASE_ACTION_EVENTS_PER_CASE, MAX_EXACT_URL_LENGTH, MAX_RESPONSE_ACTION_HISTORY, MAX_RESPONSE_ARTEFACT_REFERENCES, MAX_RESPONSE_CONTACTS, MAX_RESPONSE_CONTRADICTIONS, MAX_RESPONSE_HARM_LENGTH, MAX_RESPONSE_LIMITATIONS, MAX_RESPONSE_LIMITATION_LENGTH, MAX_RESPONSE_REFERENCE_LENGTH, MAX_RESPONSE_SELECTED_EVIDENCE, MAX_RESPONSE_VALUE_LENGTH, PUBLISHED_V2_2_CASE_RESPONSE_REVIEW_INPUTS_VERSION, PUBLISHED_V2_3_CASE_RESPONSE_REVIEW_INPUTS_VERSION, SUPPORTED_CASE_RESPONSE_REVIEW_INPUTS_VERSIONS, } from "../contracts/case-portability.mjs";
4
6
  import { CASE_ACTION_EVENT_SOURCE_CLASSES, CASE_ACTION_STATES, CASE_ACTION_TYPES, CASE_ASSERTION_STATES, CASE_CLOSURE_REASONS, CASE_OBSERVED_EFFECT_SOURCE_CLASSES, CASE_OBSERVED_EFFECT_STATES, CASE_PIN_COMPLETENESS, CASE_PROVIDER_OUTCOMES, } from "./case-response-records.mjs";
5
7
  import { RESPONSE_CONTACT_KINDS, RESPONSE_PACKET_PROFILE_IDS, RESPONSE_READINESS_ROW_IDS, RESPONSE_READINESS_STATES, } from "./case-response-packet-vocabulary.mjs";
6
8
  import { isValidAsciiHostname } from "../contracts/domain-name.mjs";
9
+ import { readCaseResponseObject, readCaseResponseObjects, readCaseResponseObjectOutcome, assertCaseObjectOutcome } from "./case-response-object.mjs";
7
10
  const CONTACT_KINDS = new Set(RESPONSE_CONTACT_KINDS);
8
11
  const PRE_PLATFORM_CONTACT_KINDS = new Set(RESPONSE_CONTACT_KINDS.filter((kind) => kind !== 'application_platform'));
9
12
  const CONTROL_RE = /[\u0000-\u001f\u007f]/u;
@@ -27,6 +30,7 @@ const CASE_RESPONSE_REVIEW_INPUT_KEYS = Object.freeze([
27
30
  'escalationHistoryOmitted',
28
31
  'escalationHistoryLimitations',
29
32
  'responseLifecycle',
33
+ 'sourceQualifications',
30
34
  ]);
31
35
  const PUBLISHED_V2_CASE_RESPONSE_REVIEW_INPUT_KEYS = Object.freeze(CASE_RESPONSE_REVIEW_INPUT_KEYS.filter((key) => key !== 'recipientRoute' && key !== 'actionBinding'));
32
36
  function exactReviewRecord(value, keys, label, optionalKeys = []) {
@@ -88,7 +92,7 @@ function reviewEnum(value, values, label) {
88
92
  function reviewNullableEnum(value, values, label) {
89
93
  return value === null ? null : reviewEnum(value, values, label);
90
94
  }
91
- function validateReviewLifecycle(value) {
95
+ function validateReviewLifecycle(value, version) {
92
96
  const lifecycle = exactReviewRecord(value, [
93
97
  'providerOutcomeState', 'latestProviderOutcome', 'observedChangeState',
94
98
  'latestObservedEffect', 'latestObservedChangeAt', 'closure', 'limitations',
@@ -110,7 +114,10 @@ function validateReviewLifecycle(value) {
110
114
  if (lifecycle.latestObservedEffect !== null) {
111
115
  const effect = exactReviewRecord(lifecycle.latestObservedEffect, [
112
116
  'reviewId', 'state', 'observedAt', 'sourceClass', 'source',
113
- ], 'Case-response latest observed effect');
117
+ ], 'Case-response latest observed effect', version >= 6 ? ['responseObject', 'objectOutcome'] : []);
118
+ const responseObject = readCaseResponseObject(effect.responseObject);
119
+ const objectOutcome = readCaseResponseObjectOutcome(effect.objectOutcome);
120
+ assertCaseObjectOutcome(objectOutcome, responseObject);
114
121
  reviewText(effect.reviewId, 64, 'Case-response observed-effect review id');
115
122
  reviewEnum(effect.state, CASE_OBSERVED_EFFECT_STATES, 'Case-response observed-effect state');
116
123
  reviewText(effect.observedAt, 64, 'Case-response observed-effect time');
@@ -151,7 +158,12 @@ export function validateCaseResponseReviewInputs(value) {
151
158
  }
152
159
  const hasPlatformRoutes = version >= PUBLISHED_V2_3_CASE_RESPONSE_REVIEW_INPUTS_VERSION;
153
160
  const hasActionBinding = version >= PUBLISHED_V2_2_CASE_RESPONSE_REVIEW_INPUTS_VERSION;
154
- const source = exactReviewRecord(value, hasActionBinding ? CASE_RESPONSE_REVIEW_INPUT_KEYS : PUBLISHED_V2_CASE_RESPONSE_REVIEW_INPUT_KEYS, 'Case-response review inputs');
161
+ const source = exactReviewRecord(value, (hasActionBinding ? CASE_RESPONSE_REVIEW_INPUT_KEYS : PUBLISHED_V2_CASE_RESPONSE_REVIEW_INPUT_KEYS).filter(key => version >= 7 || key !== 'sourceQualifications'), 'Case-response review inputs');
162
+ if (version >= 7)
163
+ for (const qualification of boundedReviewArray(source.sourceQualifications, 1, 'Selected evidence source qualifications')) {
164
+ if (qualification !== CASE_SELECTED_EVIDENCE_SOURCE_LIMITATION)
165
+ throw new TypeError('Unknown selected-source qualification.');
166
+ }
155
167
  const profile = exactReviewRecord(source.profile, [
156
168
  'id', 'label', 'audience', 'subject', 'checklist', 'includedEvidence',
157
169
  'excludedEvidence', 'redactions',
@@ -236,7 +248,11 @@ export function validateCaseResponseReviewInputs(value) {
236
248
  for (const candidate of boundedReviewArray(source.selectedEvidence, MAX_RESPONSE_SELECTED_EVIDENCE, 'Case-response selected evidence')) {
237
249
  const evidence = exactReviewRecord(candidate, [
238
250
  'id', 'label', 'source', 'observedAt', 'completeness', 'limitations',
239
- ], 'Case-response selected evidence item', version > PUBLISHED_V2_3_CASE_RESPONSE_REVIEW_INPUTS_VERSION ? ['observationHostname', 'webObservationMode'] : []);
251
+ ], 'Case-response selected evidence item', [
252
+ ...(version > PUBLISHED_V2_3_CASE_RESPONSE_REVIEW_INPUTS_VERSION ? ['observationHostname', 'webObservationMode'] : []),
253
+ ...(version >= 6 ? ['responseObject'] : []),
254
+ ]);
255
+ readCaseResponseObject(evidence.responseObject);
240
256
  reviewText(evidence.id, 64, 'Case-response evidence id');
241
257
  reviewText(evidence.label, 80, 'Case-response evidence label');
242
258
  reviewText(evidence.source, 120, 'Case-response evidence source');
@@ -319,8 +335,11 @@ export function validateCaseResponseReviewInputs(value) {
319
335
  ...(hasPlatformRoutes ? ['routeReviewAfter'] : []),
320
336
  'providerOutcome', 'outcomeDetail', 'originActionId', 'historyOmitted',
321
337
  'historyLimitations', 'transitions', 'createdAt', 'updatedAt',
322
- ], 'Case-response escalation action', version >= 5 ? ['amendment'] : []);
338
+ ], 'Case-response escalation action', [...(version >= 5 ? ['amendment'] : []), ...(version >= 6 ? ['responseObjects'] : []), ...(version >= 7 ? ['correction'] : [])]);
339
+ readCaseResponseObjects(action.responseObjects);
323
340
  readCasePacketAmendment(action.amendment);
341
+ if (readCasePacketCorrection(action.correction) && action.amendment)
342
+ throw new TypeError('Correction and provider amendment must remain separate.');
324
343
  for (const key of ['actionId', 'recipient', 'contactSource', 'createdAt', 'updatedAt']) {
325
344
  reviewText(action[key], MAX_RESPONSE_VALUE_LENGTH, `Case-response action ${key}`);
326
345
  }
@@ -346,7 +365,21 @@ export function validateCaseResponseReviewInputs(value) {
346
365
  'id', 'previousState', 'nextState', 'occurredAt', 'sourceClass', 'provenance',
347
366
  'reference', 'evidencePinId', 'limitations', 'providerOutcome', 'outcomeDetail',
348
367
  'originActionId', 'applied',
349
- ], 'Case-response transition', version >= 5 ? ['evidenceRequest'] : []);
368
+ ], 'Case-response transition', [...(version >= 5 ? ['evidenceRequest'] : []), ...(version >= 6 ? ['responseObjects', 'objectOutcome'] : []), ...(version >= 7 ? ['packetReceipt'] : [])]);
369
+ const receipt = readCaseDeliveryPacketReceipt(transition.packetReceipt);
370
+ assertDeliveryPacketReceipt(receipt, transition, action.actionId);
371
+ if (receipt && (receipt.caseId !== source.case.id || receipt.target !== source.case.domain
372
+ || receipt.recipient !== action.recipient))
373
+ throw new TypeError('The delivery receipt must retain the same Case, target and recipient.');
374
+ const responseObjects = readCaseResponseObjects(transition.responseObjects);
375
+ const objectOutcome = readCaseResponseObjectOutcome(transition.objectOutcome);
376
+ if (objectOutcome) {
377
+ if (!responseObjects?.length || !['submitted', 'acknowledged', 'terminal'].includes(String(transition.nextState))
378
+ || !['analyst', 'provider'].includes(String(transition.sourceClass))
379
+ || transition.previousState === 'authorised')
380
+ throw new TypeError('Case-response object outcome requires an explicit affected subset after submission.');
381
+ responseObjects.forEach(object => assertCaseObjectOutcome(objectOutcome, object));
382
+ }
350
383
  const evidenceRequest = readCaseEvidenceRequest(transition.evidenceRequest);
351
384
  assertEvidenceRequestEvent(evidenceRequest, transition);
352
385
  requestEvents.push({ id: transition.id, ...(evidenceRequest ? { evidenceRequest } : {}) });
@@ -376,7 +409,15 @@ export function validateCaseResponseReviewInputs(value) {
376
409
  assertPacketAmendmentSelection(source.escalationHistory.map(action => ({
377
410
  id: action.actionId, history: action.transitions, originActionId: action.originActionId, amendment: action.amendment,
378
411
  })), currentLineageActionIds?.[0] ?? null, new Set(source.selectedEvidence.map(pin => pin.id)));
412
+ if (version >= 7) {
413
+ const actions = source.escalationHistory.map(action => ({ ...action, id: action.actionId, history: action.transitions }));
414
+ const selected = actions.find(action => action.id === currentLineageActionIds?.[0]);
415
+ if (selected?.correction)
416
+ correctionDelivery(actions, selected, { caseId: source.case.id,
417
+ target: source.case.domain, profile: profile.id,
418
+ selectedPinIds: new Set(source.selectedEvidence.map(pin => pin.id)) });
419
+ }
379
420
  reviewStrings(source.escalationHistoryLimitations, MAX_RESPONSE_LIMITATIONS, MAX_RESPONSE_LIMITATION_LENGTH, 'Case-response escalation limitations');
380
- validateReviewLifecycle(source.responseLifecycle);
421
+ validateReviewLifecycle(source.responseLifecycle, version);
381
422
  return recursivelyFreezeReviewValue(structuredClone(source));
382
423
  }
@@ -102,15 +102,7 @@ export function normalizeCaseIncidentTargetUrl(value) {
102
102
  const normalized = parsed.toString();
103
103
  return normalized.length <= MAX_CASE_INCIDENT_TARGET_URL_LENGTH ? normalized : null;
104
104
  }
105
- /** Local review only: analyst link state is not independent observed coverage. */
106
- export function buildCaseIncidentCoverage(record) {
107
- return caseIncidentTargets(record, { includeResolved: true }).map(target => ({
108
- target,
109
- hostname: new URL(target.url).hostname,
110
- actionCoverage: 'unknown',
111
- observationCoverage: 'unknown',
112
- }));
113
- }
105
+ export { buildCaseIncidentCoverage } from "./case-object-coverage.mjs";
114
106
  export function emptyCaseWorkflowMetadata() {
115
107
  return { types: [], incidentTargets: [], investigationContext: null };
116
108
  }
@@ -33,7 +33,8 @@ export const MAIL_REPORT_CANONICALIZATION_ROUTES = Object.freeze([
33
33
  export const REGISTRATION_DISCLOSURE_PLAN_SCHEMA = 'whoisleuth.registration-disclosure-plan';
34
34
  export const REGISTRATION_DISCLOSURE_PLAN_VERSION = 2;
35
35
  export const STATIC_PAGE_PATTERN_PACK_SCHEMA = 'whoisleuth.static-page-pattern-pack';
36
- export const STATIC_PAGE_PATTERN_PACK_VERSION = 2;
36
+ export const STATIC_PAGE_PATTERN_PACK_VERSION = 3;
37
+ export const STATIC_PAGE_PATTERN_PACK_SUPPORTED_VERSIONS = [2, STATIC_PAGE_PATTERN_PACK_VERSION];
37
38
  export const MAX_STATIC_PAGE_PATTERN_PACK_BYTES = 256 * 1024;
38
39
  export const WEB_CAPTURE_SUMMARY_SCHEMA = 'whoisleuth.web-capture-summary';
39
40
  export const WEB_CAPTURE_SUMMARY_VERSION = 1;
@@ -136,11 +137,11 @@ const REGISTRATION_DISCLOSURE_COMPATIBILITY = defineSchemaCompatibility({
136
137
  });
137
138
  const STATIC_PATTERN_PACK_COMPATIBILITY = defineSchemaCompatibility({
138
139
  id: 'export.static-page-pattern-pack', kind: 'export', schema: STATIC_PAGE_PATTERN_PACK_SCHEMA,
139
- currentVersion: STATIC_PAGE_PATTERN_PACK_VERSION, supportedVersions: [STATIC_PAGE_PATTERN_PACK_VERSION],
140
- acceptsUnversionedLegacy: false, futureVersionBehavior: 'reject', migration: 'exact_current_only',
140
+ currentVersion: STATIC_PAGE_PATTERN_PACK_VERSION, supportedVersions: STATIC_PAGE_PATTERN_PACK_SUPPORTED_VERSIONS,
141
+ acceptsUnversionedLegacy: false, futureVersionBehavior: 'reject', migration: 'read_only',
141
142
  writeSemantics: 'non_destructive_merge', byteBudget: MAX_STATIC_PAGE_PATTERN_PACK_BYTES,
142
143
  owner: ANALYST_INTERCHANGE_CONTRACT_OWNER,
143
- note: 'Exact-current bounded page-pattern pack imported by explicit analyst action and merged without executing code or starting collection.',
144
+ note: 'Versions 2 and 3 remain readable. Version 3 can use attributed password-form destinations; older rules retain their declared meaning. Import remains explicit and does not execute code or start collection.',
144
145
  });
145
146
  export function serialiseAnalystInterchangeJson(value) {
146
147
  return `${JSON.stringify(value, null, 2)}\n`;
@@ -229,6 +230,7 @@ export const ANALYST_INTERCHANGE_LIFECYCLE_FAMILY = defineSchemaLifecycleFamily(
229
230
  { descriptor: STATIC_PATTERN_PACK_COMPATIBILITY, lifecycleSchema: STATIC_PAGE_PATTERN_PACK_SCHEMA,
230
231
  requiredKeys: ['schema', 'version', 'id', 'label', 'description', 'evidenceBoundary', 'relationship', 'confidence', 'rules'], optionalKeys: [],
231
232
  hook: { module: 'packages/interchange/static-page-pattern-packs.mts', exportName: 'validateStaticPagePatternPack', role: 'structure_validator', runtime: 'shared' },
232
- fixtures: [{ id: 'static-page-pattern-pack-v2', path: `${F}static-page-pattern-pack-v2.json`, bytes: 1_094, sha256: 'c409e527850bcb924f5be0708a58c5e2c32d98947c42150f6a79765991c0eb6f', version: STATIC_PAGE_PATTERN_PACK_VERSION }] },
233
+ fixtures: [{ id: 'static-page-pattern-pack-v2', path: `${F}static-page-pattern-pack-v2.json`, bytes: 1_094, sha256: 'c409e527850bcb924f5be0708a58c5e2c32d98947c42150f6a79765991c0eb6f', version: 2 },
234
+ { id: 'static-page-pattern-pack-v3', path: `${F}static-page-pattern-pack-v3.json`, bytes: 746, sha256: '5be19b621ad604de8c430191a34fcd2d624b22b8bdf9a5cb2653ef7d1b86c71a', version: 3 }] },
233
235
  ],
234
236
  }));
@@ -0,0 +1,54 @@
1
+ import { defineSchemaCompatibility } from "./schema-compatibility.mjs";
2
+ import { defineSchemaLifecycleFamily } from "./schema-lifecycle.mjs";
3
+ import { buildExtractedLifecycleFamily } from "./extracted-domain-lifecycle.mjs";
4
+ import { MAX_WATCHLIST_IMPORT_BYTES } from "./workspace-portability.mjs";
5
+ export const CANDIDATE_WATCH_INPUT_SCHEMA = 'whoisleuth.candidate-watch-input';
6
+ export const CANDIDATE_WATCH_INPUT_VERSION = 1;
7
+ export const CANDIDATE_WATCH_INPUT_COMPATIBILITY = defineSchemaCompatibility({
8
+ id: 'cli.candidate-watch-input',
9
+ kind: 'cli_document',
10
+ schema: CANDIDATE_WATCH_INPUT_SCHEMA,
11
+ currentVersion: 1,
12
+ supportedVersions: [1],
13
+ acceptsUnversionedLegacy: false,
14
+ futureVersionBehavior: 'reject',
15
+ migration: 'exact_current_only',
16
+ writeSemantics: 'read_only',
17
+ byteBudget: MAX_WATCHLIST_IMPORT_BYTES,
18
+ owner: 'packages/contracts/candidate-watch-review.mts',
19
+ note: 'Bounded offline analyst selection and optional portable Watchlist document; no collection or schedule authority.',
20
+ });
21
+ export const CANDIDATE_WATCH_REVIEW_LIFECYCLE_FAMILY = defineSchemaLifecycleFamily(buildExtractedLifecycleFamily({
22
+ id: 'candidate-watch-review',
23
+ owner: 'packages/contracts/candidate-watch-review.mts',
24
+ plane: 'node',
25
+ projection: 'review_output',
26
+ retention: 'transient_report',
27
+ serializerModule: 'cli/formatters/json.mts',
28
+ serializerExportName: 'formatJsonDocument',
29
+ includedCategories: ['candidate-provenance', 'analyst-watch-reasons', 'review-priorities'],
30
+ excludedCategories: ['raw-upstream-payloads', 'credentials', 'cookies', 'query-bearing-urls'],
31
+ formats: [
32
+ {
33
+ descriptor: CANDIDATE_WATCH_INPUT_COMPATIBILITY,
34
+ lifecycleSchema: CANDIDATE_WATCH_INPUT_SCHEMA,
35
+ requiredKeys: ['schema', 'version', 'watchlists', 'selection'],
36
+ optionalKeys: [],
37
+ hook: {
38
+ module: 'cli/watchlist-review.mts',
39
+ exportName: 'parseCandidateWatchInput',
40
+ role: 'structure_validator',
41
+ runtime: 'node',
42
+ },
43
+ fixtures: [
44
+ {
45
+ id: 'candidate-watch-input-v1',
46
+ path: 'test/fixtures/brand-candidate-workflow/candidate-watch-input-v1.json',
47
+ bytes: 895,
48
+ sha256: '40a0d73e5aa3afc3b6836bb289c79cf9556dd755a5086a9666506b31468b5d3d',
49
+ version: 1,
50
+ },
51
+ ],
52
+ },
53
+ ],
54
+ }));