@cyanheads/sanctions-screening-mcp-server 0.1.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 (110) hide show
  1. package/AGENTS.md +452 -0
  2. package/CLAUDE.md +452 -0
  3. package/Dockerfile +126 -0
  4. package/LICENSE +201 -0
  5. package/README.md +354 -0
  6. package/changelog/0.1.x/0.1.0.md +26 -0
  7. package/changelog/template.md +127 -0
  8. package/dist/config/server-config.d.ts +37 -0
  9. package/dist/config/server-config.d.ts.map +1 -0
  10. package/dist/config/server-config.js +87 -0
  11. package/dist/config/server-config.js.map +1 -0
  12. package/dist/index.d.ts +11 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +70 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/mcp-server/prompts/definitions/index.d.ts +12 -0
  17. package/dist/mcp-server/prompts/definitions/index.d.ts.map +1 -0
  18. package/dist/mcp-server/prompts/definitions/index.js +9 -0
  19. package/dist/mcp-server/prompts/definitions/index.js.map +1 -0
  20. package/dist/mcp-server/prompts/definitions/vet-counterparty.prompt.d.ts +14 -0
  21. package/dist/mcp-server/prompts/definitions/vet-counterparty.prompt.d.ts.map +1 -0
  22. package/dist/mcp-server/prompts/definitions/vet-counterparty.prompt.js +42 -0
  23. package/dist/mcp-server/prompts/definitions/vet-counterparty.prompt.js.map +1 -0
  24. package/dist/mcp-server/resources/definitions/designation.resource.d.ts +25 -0
  25. package/dist/mcp-server/resources/definitions/designation.resource.d.ts.map +1 -0
  26. package/dist/mcp-server/resources/definitions/designation.resource.js +57 -0
  27. package/dist/mcp-server/resources/definitions/designation.resource.js.map +1 -0
  28. package/dist/mcp-server/resources/definitions/entity.resource.d.ts +17 -0
  29. package/dist/mcp-server/resources/definitions/entity.resource.d.ts.map +1 -0
  30. package/dist/mcp-server/resources/definitions/entity.resource.js +40 -0
  31. package/dist/mcp-server/resources/definitions/entity.resource.js.map +1 -0
  32. package/dist/mcp-server/resources/definitions/index.d.ts +32 -0
  33. package/dist/mcp-server/resources/definitions/index.d.ts.map +1 -0
  34. package/dist/mcp-server/resources/definitions/index.js +11 -0
  35. package/dist/mcp-server/resources/definitions/index.js.map +1 -0
  36. package/dist/mcp-server/resources/definitions/sources.resource.d.ts +9 -0
  37. package/dist/mcp-server/resources/definitions/sources.resource.d.ts.map +1 -0
  38. package/dist/mcp-server/resources/definitions/sources.resource.js +50 -0
  39. package/dist/mcp-server/resources/definitions/sources.resource.js.map +1 -0
  40. package/dist/mcp-server/tools/definitions/_shared.d.ts +13 -0
  41. package/dist/mcp-server/tools/definitions/_shared.d.ts.map +1 -0
  42. package/dist/mcp-server/tools/definitions/_shared.js +13 -0
  43. package/dist/mcp-server/tools/definitions/_shared.js.map +1 -0
  44. package/dist/mcp-server/tools/definitions/get-designation.tool.d.ts +78 -0
  45. package/dist/mcp-server/tools/definitions/get-designation.tool.d.ts.map +1 -0
  46. package/dist/mcp-server/tools/definitions/get-designation.tool.js +168 -0
  47. package/dist/mcp-server/tools/definitions/get-designation.tool.js.map +1 -0
  48. package/dist/mcp-server/tools/definitions/get-entity.tool.d.ts +55 -0
  49. package/dist/mcp-server/tools/definitions/get-entity.tool.d.ts.map +1 -0
  50. package/dist/mcp-server/tools/definitions/get-entity.tool.js +176 -0
  51. package/dist/mcp-server/tools/definitions/get-entity.tool.js.map +1 -0
  52. package/dist/mcp-server/tools/definitions/index.d.ts +306 -0
  53. package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -0
  54. package/dist/mcp-server/tools/definitions/index.js +21 -0
  55. package/dist/mcp-server/tools/definitions/index.js.map +1 -0
  56. package/dist/mcp-server/tools/definitions/list-sources.tool.d.ts +23 -0
  57. package/dist/mcp-server/tools/definitions/list-sources.tool.d.ts.map +1 -0
  58. package/dist/mcp-server/tools/definitions/list-sources.tool.js +106 -0
  59. package/dist/mcp-server/tools/definitions/list-sources.tool.js.map +1 -0
  60. package/dist/mcp-server/tools/definitions/resolve-entity.tool.d.ts +51 -0
  61. package/dist/mcp-server/tools/definitions/resolve-entity.tool.d.ts.map +1 -0
  62. package/dist/mcp-server/tools/definitions/resolve-entity.tool.js +148 -0
  63. package/dist/mcp-server/tools/definitions/resolve-entity.tool.js.map +1 -0
  64. package/dist/mcp-server/tools/definitions/screen-name.tool.d.ts +82 -0
  65. package/dist/mcp-server/tools/definitions/screen-name.tool.d.ts.map +1 -0
  66. package/dist/mcp-server/tools/definitions/screen-name.tool.js +172 -0
  67. package/dist/mcp-server/tools/definitions/screen-name.tool.js.map +1 -0
  68. package/dist/mcp-server/tools/definitions/trace-ownership.tool.d.ts +74 -0
  69. package/dist/mcp-server/tools/definitions/trace-ownership.tool.d.ts.map +1 -0
  70. package/dist/mcp-server/tools/definitions/trace-ownership.tool.js +273 -0
  71. package/dist/mcp-server/tools/definitions/trace-ownership.tool.js.map +1 -0
  72. package/dist/services/screening/fixtures.d.ts +17 -0
  73. package/dist/services/screening/fixtures.d.ts.map +1 -0
  74. package/dist/services/screening/fixtures.js +162 -0
  75. package/dist/services/screening/fixtures.js.map +1 -0
  76. package/dist/services/screening/gleif-ingest.d.ts +68 -0
  77. package/dist/services/screening/gleif-ingest.d.ts.map +1 -0
  78. package/dist/services/screening/gleif-ingest.js +251 -0
  79. package/dist/services/screening/gleif-ingest.js.map +1 -0
  80. package/dist/services/screening/sanctions-ingest.d.ts +46 -0
  81. package/dist/services/screening/sanctions-ingest.d.ts.map +1 -0
  82. package/dist/services/screening/sanctions-ingest.js +688 -0
  83. package/dist/services/screening/sanctions-ingest.js.map +1 -0
  84. package/dist/services/screening/schema.d.ts +52 -0
  85. package/dist/services/screening/schema.d.ts.map +1 -0
  86. package/dist/services/screening/schema.js +125 -0
  87. package/dist/services/screening/schema.js.map +1 -0
  88. package/dist/services/screening/screening-service.d.ts +203 -0
  89. package/dist/services/screening/screening-service.d.ts.map +1 -0
  90. package/dist/services/screening/screening-service.js +702 -0
  91. package/dist/services/screening/screening-service.js.map +1 -0
  92. package/dist/services/screening/text-matching.d.ts +53 -0
  93. package/dist/services/screening/text-matching.d.ts.map +1 -0
  94. package/dist/services/screening/text-matching.js +514 -0
  95. package/dist/services/screening/text-matching.js.map +1 -0
  96. package/dist/services/screening/types.d.ts +154 -0
  97. package/dist/services/screening/types.d.ts.map +1 -0
  98. package/dist/services/screening/types.js +24 -0
  99. package/dist/services/screening/types.js.map +1 -0
  100. package/dist/services/screening/xml.d.ts +29 -0
  101. package/dist/services/screening/xml.d.ts.map +1 -0
  102. package/dist/services/screening/xml.js +46 -0
  103. package/dist/services/screening/xml.js.map +1 -0
  104. package/package.json +119 -0
  105. package/scripts/_mirror-context.ts +21 -0
  106. package/scripts/mirror-init.ts +66 -0
  107. package/scripts/mirror-refresh.ts +56 -0
  108. package/scripts/mirror-seed.ts +36 -0
  109. package/scripts/mirror-verify.ts +44 -0
  110. package/server.json +148 -0
@@ -0,0 +1,251 @@
1
+ /**
2
+ * @fileoverview GLEIF ingester — the heaviest leg. Resolves the latest
3
+ * golden-copy (full) or delta download URLs from the GLEIF Golden Copy API,
4
+ * streams the ZIP-compressed file, and normalizes LEI-CDF Level 1 (who-is-who)
5
+ * and RR-CDF Level 2 (who-owns-whom) records onto {@link NormalizedLeiEntity} /
6
+ * {@link NormalizedLeiRelationship}. Level 1 init is ~3.3M records / ~490 MB
7
+ * compressed (May 2026) — it runs out-of-band via `mirror:init`, never on the
8
+ * request path; refresh uses the 8-hour deltas keyed on a checkpoint.
9
+ *
10
+ * The normalization functions are the testable core (exercised by the synthetic
11
+ * fixture). The download/decompress wiring is documented and resilient; the real
12
+ * corpus loads out-of-band.
13
+ * @module services/screening/gleif-ingest
14
+ */
15
+ import { gunzipSync, inflateRawSync } from 'node:zlib';
16
+ import { serviceUnavailable } from '@cyanheads/mcp-ts-core/errors';
17
+ import { fetchWithTimeout, requestContextService, withRetry } from '@cyanheads/mcp-ts-core/utils';
18
+ import { getServerConfig } from '../../config/server-config.js';
19
+ import { parseXml } from '../../services/screening/xml.js';
20
+ const FETCH_TIMEOUT_MS = 600_000;
21
+ /** Coerce single-child→object / many→array (fast-xml-parser behavior). */
22
+ function asArray(value) {
23
+ if (value == null)
24
+ return [];
25
+ return Array.isArray(value) ? value : [value];
26
+ }
27
+ function asText(value) {
28
+ if (value == null)
29
+ return;
30
+ if (typeof value === 'string')
31
+ return value.trim() || undefined;
32
+ if (typeof value === 'number')
33
+ return String(value);
34
+ if (typeof value === 'object' && '#text' in value) {
35
+ return asText(value['#text']);
36
+ }
37
+ return;
38
+ }
39
+ /**
40
+ * Conditional-spread fragment: `{ [key]: value }` when `value` is defined and
41
+ * non-empty, else `{}`. Keeps normalized objects honest under
42
+ * `exactOptionalPropertyTypes` without a non-null assertion.
43
+ */
44
+ function opt(key, value) {
45
+ return value ? { [key]: value } : {};
46
+ }
47
+ /**
48
+ * Resolve the download URL for a GLEIF golden-copy or delta file. The Golden
49
+ * Copy API exposes a JSON index of the latest publications per file type and
50
+ * delta window; we read it and return the matching `.xml.gz` download link.
51
+ *
52
+ * @param kind Which dataset + window.
53
+ * @param delta Delta window when `kind` is a delta variant (e.g. `'LastDay'`, `'IntraDay'`).
54
+ */
55
+ export async function resolveGleifFileUrl(kind, signal, delta = 'LastDay') {
56
+ const base = getServerConfig().gleifGoldenCopyBaseUrl.replace(/\/$/, '');
57
+ // GLEIF Golden Copy API publication index. The exact query shape:
58
+ // /api/v2/golden-copies/publishes/lei2?format=xml (full)
59
+ // /api/v2/golden-copies/publishes/lei2/{window} (delta)
60
+ const dataset = kind.startsWith('lei2') ? 'lei2' : 'rr';
61
+ const isDelta = kind.endsWith('delta');
62
+ const url = isDelta
63
+ ? `${base}/api/v2/golden-copies/publishes/${dataset}?delta.period=${delta}&format=xml`
64
+ : `${base}/api/v2/golden-copies/publishes/${dataset}?format=xml`;
65
+ const reqCtx = requestContextService.createRequestContext({ operation: `gleif:index:${kind}` });
66
+ const index = await withRetry(async () => {
67
+ const res = await fetchWithTimeout(url, 60_000, reqCtx, { signal });
68
+ return (await res.json());
69
+ }, { operation: `gleif:index:${kind}`, baseDelayMs: 2000, signal });
70
+ // The publication record carries a `data[].full_file.xml.url` (or delta_files).
71
+ const data = asArray((index.data ?? index))[0];
72
+ const fileNode = (isDelta ? data?.delta_files : data?.full_file);
73
+ const xmlNode = fileNode?.xml;
74
+ const downloadUrl = asText(xmlNode?.url) ?? asText(xmlNode?.download_link);
75
+ if (!downloadUrl) {
76
+ throw serviceUnavailable(`GLEIF Golden Copy index did not contain a download URL for ${kind}.`, { url });
77
+ }
78
+ return downloadUrl;
79
+ }
80
+ /**
81
+ * Download a GLEIF golden-copy / delta file and return the decompressed XML.
82
+ * The golden-copy downloads are **ZIP** containers (`PK\x03\x04`) wrapping a
83
+ * single XML entry — not gzip; the delta `.gz` files are gzip. Detection is by
84
+ * magic bytes (the URL suffix is unreliable behind the storage redirect), with a
85
+ * plain-XML fallback.
86
+ *
87
+ * NOTE (memory): this buffers and decompresses the whole file in memory. The
88
+ * Level 1 golden copy is ~3.3M records / ~900 MB compressed; decompressing and
89
+ * DOM-parsing it whole needs multiple GB of heap. A production `mirror:init`
90
+ * against the full L1 file requires a streaming ZIP → SAX pipeline; until then,
91
+ * real L1 ingest is bounded by available memory. Deltas and L2 are far smaller
92
+ * and fit comfortably.
93
+ */
94
+ export function downloadGleifXml(url, signal) {
95
+ const reqCtx = requestContextService.createRequestContext({ operation: 'gleif:download' });
96
+ return withRetry(async () => {
97
+ const res = await fetchWithTimeout(url, FETCH_TIMEOUT_MS, reqCtx, { signal });
98
+ const buf = Buffer.from(await res.arrayBuffer());
99
+ const xml = decompressGleifBuffer(buf);
100
+ if (/^\s*<(!DOCTYPE\s+html|html[\s>])/i.test(xml)) {
101
+ throw serviceUnavailable('GLEIF returned HTML instead of XML — likely rate-limited.');
102
+ }
103
+ return xml;
104
+ }, { operation: 'gleif:download', baseDelayMs: 3000, signal });
105
+ }
106
+ /** Decompress a downloaded GLEIF buffer by detecting ZIP / gzip / plain by magic bytes. */
107
+ export function decompressGleifBuffer(buf) {
108
+ if (isZip(buf))
109
+ return extractFirstZipEntry(buf);
110
+ if (isGzip(buf))
111
+ return gunzipSync(buf).toString('utf8');
112
+ return buf.toString('utf8');
113
+ }
114
+ function isGzip(buf) {
115
+ return buf.length > 2 && buf[0] === 0x1f && buf[1] === 0x8b;
116
+ }
117
+ function isZip(buf) {
118
+ return buf.length > 4 && buf[0] === 0x50 && buf[1] === 0x4b && buf[2] === 0x03 && buf[3] === 0x04;
119
+ }
120
+ /**
121
+ * Extract the first file entry from a ZIP buffer (stored or deflate). GLEIF
122
+ * golden-copy ZIPs wrap exactly one XML file, so a single-entry reader is
123
+ * sufficient — it reads the local file header, then inflates the entry's raw
124
+ * deflate stream (or returns it verbatim when stored).
125
+ */
126
+ export function extractFirstZipEntry(buf) {
127
+ // Local file header: sig(4)=PK\x03\x04, method@8(2), compressedSize@18(4),
128
+ // nameLen@26(2), extraLen@28(2), then [name][extra][data].
129
+ if (!isZip(buf) || buf.length < 30) {
130
+ throw serviceUnavailable('GLEIF download is not a valid ZIP archive.');
131
+ }
132
+ const method = buf.readUInt16LE(8);
133
+ let compressedSize = buf.readUInt32LE(18);
134
+ const nameLen = buf.readUInt16LE(26);
135
+ const extraLen = buf.readUInt16LE(28);
136
+ const dataStart = 30 + nameLen + extraLen;
137
+ // Streaming entries (general-purpose bit 3) report size 0 in the local header
138
+ // and place sizes in a trailing data descriptor; the entry data then runs up
139
+ // to the central-directory signature.
140
+ if (compressedSize === 0) {
141
+ const cd = buf.indexOf(Buffer.from([0x50, 0x4b, 0x01, 0x02]), dataStart);
142
+ compressedSize = (cd === -1 ? buf.length : cd) - dataStart;
143
+ }
144
+ const data = buf.subarray(dataStart, dataStart + compressedSize);
145
+ if (method === 0)
146
+ return data.toString('utf8');
147
+ if (method === 8)
148
+ return inflateRawSync(data).toString('utf8');
149
+ throw serviceUnavailable(`Unsupported ZIP compression method ${method} in GLEIF download.`);
150
+ }
151
+ /**
152
+ * Parse an LEI-CDF Level 1 XML document into normalized entity records. The
153
+ * record root is `<LEIRecords><LEIRecord>`; each carries an `<LEI>`, an
154
+ * `<Entity>` (legal name, other names, addresses, jurisdiction, status), and a
155
+ * `<Registration>` (registration status, last-update, RA).
156
+ */
157
+ export function parseLeiLevel1(doc) {
158
+ const root = (doc.LEIData ?? doc['lei:LEIData'] ?? doc);
159
+ const records = asArray((root.LEIRecords ?? root['lei:LEIRecords'])
160
+ ?.LEIRecord ?? root.LEIRecords?.['lei:LEIRecord']);
161
+ return records
162
+ .map((raw) => parseOneLei(raw))
163
+ .filter(Boolean);
164
+ }
165
+ function parseOneLei(r) {
166
+ const lei = asText(r.LEI ?? r['lei:LEI']);
167
+ if (!lei)
168
+ return null;
169
+ const entity = (r.Entity ?? r['lei:Entity']);
170
+ const registration = (r.Registration ?? r['lei:Registration']);
171
+ const legalName = asText(entity?.LegalName?.['#text'] ?? entity?.LegalName) ?? 'Unknown';
172
+ const otherNames = asArray(entity?.OtherEntityNames?.OtherEntityName)
173
+ .map((n) => asText(n?.['#text'] ?? n))
174
+ .filter((x) => Boolean(x));
175
+ const legalAddr = renderAddress(entity?.LegalAddress);
176
+ const hqAddr = renderAddress(entity?.HeadquartersAddress);
177
+ const jurisdiction = asText(entity?.LegalJurisdiction);
178
+ const ra = entity?.RegistrationAuthority;
179
+ return {
180
+ lei,
181
+ legalName,
182
+ otherNames,
183
+ ...opt('jurisdiction', jurisdiction),
184
+ ...opt('status', asText(registration?.RegistrationStatus) ?? asText(entity?.EntityStatus)),
185
+ ...opt('legalAddress', legalAddr),
186
+ ...opt('headquartersAddress', hqAddr),
187
+ ...opt('registrationAuthorityId', asText(ra?.RegistrationAuthorityID)),
188
+ ...opt('registrationAuthorityEntityId', asText(ra?.RegistrationAuthorityEntityID)),
189
+ ...opt('lastUpdate', asText(registration?.LastUpdateDate)),
190
+ };
191
+ }
192
+ function renderAddress(addr) {
193
+ if (!addr)
194
+ return;
195
+ const parts = [
196
+ asText(addr.FirstAddressLine?.['#text'] ??
197
+ addr.FirstAddressLine),
198
+ asText(addr.AdditionalAddressLine),
199
+ asText(addr.City?.['#text'] ?? addr.City),
200
+ asText(addr.Region),
201
+ asText(addr.PostalCode),
202
+ asText(addr.Country?.['#text'] ?? addr.Country),
203
+ ].filter(Boolean);
204
+ return parts.length ? parts.join(', ') : undefined;
205
+ }
206
+ /**
207
+ * Parse an RR-CDF Level 2 XML document into normalized relationship records.
208
+ * The record root is `<RelationshipRecords><RelationshipRecord>`; each carries a
209
+ * `<Relationship>` with a start node (child), end node (parent), and type.
210
+ */
211
+ export function parseLeiLevel2(doc) {
212
+ const root = (doc.RelationshipData ?? doc['rr:RelationshipData'] ?? doc);
213
+ const records = asArray((root.RelationshipRecords ?? root['rr:RelationshipRecords'])?.RelationshipRecord ??
214
+ root.RelationshipRecords?.['rr:RelationshipRecord']);
215
+ return records
216
+ .map((raw) => parseOneRelationship(raw))
217
+ .filter(Boolean);
218
+ }
219
+ function parseOneRelationship(r) {
220
+ const rel = (r.Relationship ?? r['rr:Relationship']);
221
+ if (!rel)
222
+ return null;
223
+ const startNode = (rel.StartNode ?? rel['rr:StartNode']);
224
+ const endNode = (rel.EndNode ?? rel['rr:EndNode']);
225
+ const childLei = asText(startNode?.NodeID ?? startNode?.['#text']);
226
+ const parentLei = asText(endNode?.NodeID ?? endNode?.['#text']);
227
+ const relationshipType = asText(rel.RelationshipType);
228
+ if (!childLei || !parentLei || !relationshipType)
229
+ return null;
230
+ const period = asArray(rel.RelationshipPeriods?.RelationshipPeriod)[0];
231
+ return {
232
+ childLei,
233
+ parentLei,
234
+ relationshipType,
235
+ ...opt('relationshipStatus', asText(rel.RelationshipStatus)),
236
+ ...opt('relationshipPeriod', asText(period?.StartDate)),
237
+ };
238
+ }
239
+ /** Fetch + decompress + parse Level 1 from a resolved URL. */
240
+ export async function harvestLeiLevel1(url, signal) {
241
+ const xml = await downloadGleifXml(url, signal);
242
+ const doc = parseXml(xml);
243
+ return parseLeiLevel1(doc);
244
+ }
245
+ /** Fetch + decompress + parse Level 2 from a resolved URL. */
246
+ export async function harvestLeiLevel2(url, signal) {
247
+ const xml = await downloadGleifXml(url, signal);
248
+ const doc = parseXml(xml);
249
+ return parseLeiLevel2(doc);
250
+ }
251
+ //# sourceMappingURL=gleif-ingest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gleif-ingest.js","sourceRoot":"","sources":["../../../src/services/screening/gleif-ingest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * @fileoverview Sanctions ingesters — one per source (OFAC SDN, OFAC
3
+ * Consolidated, EU FSF, UK Sanctions List, UN SC Consolidated). Each fetches the
4
+ * source file, parses the XML, and maps records onto the common
5
+ * {@link NormalizedDesignation} schema. The {@link createSanctionsSync} factory
6
+ * wires them into the MirrorService `sync` generator: each refresh re-harvests
7
+ * all sources in full (the combined corpus is tens of thousands of rows — no
8
+ * delta logic needed), yielding one page per source.
9
+ *
10
+ * The XML shapes differ wildly across sources; each parser is defensive about
11
+ * sparsity and arrays-of-one (fast-xml-parser collapses single children to
12
+ * objects), and preserves absence rather than fabricating fields.
13
+ * @module services/screening/sanctions-ingest
14
+ */
15
+ import type { NormalizedDesignation, SourceCode } from '../../services/screening/types.js';
16
+ /** A source ingester: yields a full set of normalized designations for one list. */
17
+ export interface SanctionsIngester {
18
+ /** Fetch + parse + normalize the full list. */
19
+ harvest(signal: AbortSignal): Promise<NormalizedDesignation[]>;
20
+ source: SourceCode;
21
+ /** Source file URL (for provenance). */
22
+ url(): string;
23
+ }
24
+ export declare function parseOfac(doc: Record<string, unknown>, source: SourceCode): NormalizedDesignation[];
25
+ export declare function parseEu(doc: Record<string, unknown>): NormalizedDesignation[];
26
+ export declare function parseUk(doc: Record<string, unknown>): NormalizedDesignation[];
27
+ export declare function parseUn(doc: Record<string, unknown>): NormalizedDesignation[];
28
+ /** All five sanctions ingesters, configured from the current server config. */
29
+ export declare function buildSanctionsIngesters(): SanctionsIngester[];
30
+ /**
31
+ * The MirrorService `sync` generator for the sanctions designation mirror. Each
32
+ * run harvests every source in full and yields one page per source. The mirror
33
+ * upserts the `designation` rows; the per-alias `name` index is rebuilt from
34
+ * `designation.payload` after the sync by the service's `rebuildNameIndex()`
35
+ * (the lifecycle scripts and the refresh cron call it). `init` and `refresh`
36
+ * behave identically — these are small, fully re-harvested corpora.
37
+ */
38
+ export declare function createSanctionsSync(): (ctx: {
39
+ signal: AbortSignal;
40
+ }) => AsyncGenerator<{
41
+ records: Record<string, string | number | null>[];
42
+ checkpoint?: string;
43
+ }>;
44
+ /** Map a normalized designation to its primary-table row (no aux fields). */
45
+ export declare function toDesignationRow(d: NormalizedDesignation): Record<string, string | number | null>;
46
+ //# sourceMappingURL=sanctions-ingest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanctions-ingest.d.ts","sourceRoot":"","sources":["../../../src/services/screening/sanctions-ingest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAMH,OAAO,KAAK,EAMV,qBAAqB,EACrB,UAAU,EACX,MAAM,+BAA+B,CAAC;AAGvC,oFAAoF;AACpF,MAAM,WAAW,iBAAiB;IAChC,+CAA+C;IAC/C,OAAO,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAAC;IAC/D,MAAM,EAAE,UAAU,CAAC;IACnB,wCAAwC;IACxC,GAAG,IAAI,MAAM,CAAC;CACf;AA6ED,wBAAgB,SAAS,CACvB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,MAAM,EAAE,UAAU,GACjB,qBAAqB,EAAE,CAiBzB;AAgYD,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,qBAAqB,EAAE,CA0D7E;AAsBD,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,qBAAqB,EAAE,CAyE7E;AAwBD,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,qBAAqB,EAAE,CAS7E;AAyED,+EAA+E;AAC/E,wBAAgB,uBAAuB,IAAI,iBAAiB,EAAE,CAS7D;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,KACL,KAAK;IAC/B,MAAM,EAAE,WAAW,CAAC;CACrB,KAAG,cAAc,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAY/F;AAED,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,qBAAqB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC,CAajG"}