@chainpatrol/sdk 1.8.0 → 1.10.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.
package/dist/index.js CHANGED
@@ -601,45 +601,6 @@ zod.z.object({
601
601
  name: zod.z.string().optional().describe("Asset name"),
602
602
  description: zod.z.string().nullish().optional().describe("Asset description")
603
603
  });
604
- zod.z.object({
605
- slug: zod.z.string(),
606
- assetId: zod.z.number(),
607
- name: zod.z.string().optional(),
608
- description: zod.z.string().optional(),
609
- assetGroupId: zod.z.number().nullable().optional(),
610
- updatedByOrganizationId: zod.z.number().optional()
611
- });
612
- zod.z.object({
613
- content: zod.z.string().min(1, { message: "Content must be at least 3 character" }).max(1e3, { message: "Content must be less than 1000 characters" }),
614
- email: zod.z.string().email(),
615
- description: zod.z.string().optional().default("")
616
- });
617
- zod.z.object({
618
- /** Canonicalization algorithm version the client computed its prefixes with. */
619
- algoVersion: zod.z.number().int().positive(),
620
- /** 4-byte (8-hex-char) prefixes that hit the client's local index. */
621
- prefixes: zod.z.array(zod.z.string().regex(/^[0-9a-f]{8}$/)).min(1).max(200)
622
- }).describe("Blocklist confirm request");
623
- zod.z.object({
624
- /** Full 64-char SHA-256 hashes of blocked expressions, grouped by prefix. */
625
- fullHashes: zod.z.record(zod.z.string(), zod.z.array(zod.z.string()))
626
- }).describe("Blocklist confirm response");
627
- zod.z.void().describe("Blocklist version request");
628
- zod.z.object({
629
- /** Canonicalization algorithm version the artifacts + confirm are built with. */
630
- algoVersion: zod.z.number().int().positive(),
631
- snapshot: zod.z.object({
632
- token: zod.z.string(),
633
- url: zod.z.string().url(),
634
- prefixBytes: zod.z.number().int().positive(),
635
- count: zod.z.number().int().nonnegative()
636
- }),
637
- recent: zod.z.object({
638
- token: zod.z.string(),
639
- url: zod.z.string().url(),
640
- windowSeconds: zod.z.number().int().positive()
641
- })
642
- }).describe("Blocklist version response");
643
604
 
644
605
  // ../../node_modules/luxon/src/errors.js
645
606
  var LuxonError = class extends Error {
@@ -1313,17 +1274,17 @@ var PolyDateFormatter = class {
1313
1274
  constructor(dt, intl, opts) {
1314
1275
  this.opts = opts;
1315
1276
  this.originalZone = void 0;
1316
- let z55 = void 0;
1277
+ let z61 = void 0;
1317
1278
  if (this.opts.timeZone) {
1318
1279
  this.dt = dt;
1319
1280
  } else if (dt.zone.type === "fixed") {
1320
1281
  const gmtOffset = -1 * (dt.offset / 60);
1321
1282
  const offsetZ = gmtOffset >= 0 ? `Etc/GMT+${gmtOffset}` : `Etc/GMT${gmtOffset}`;
1322
1283
  if (dt.offset !== 0 && IANAZone.create(offsetZ).valid) {
1323
- z55 = offsetZ;
1284
+ z61 = offsetZ;
1324
1285
  this.dt = dt;
1325
1286
  } else {
1326
- z55 = "UTC";
1287
+ z61 = "UTC";
1327
1288
  this.dt = dt.offset === 0 ? dt : dt.setZone("UTC").plus({ minutes: dt.offset });
1328
1289
  this.originalZone = dt.zone;
1329
1290
  }
@@ -1331,14 +1292,14 @@ var PolyDateFormatter = class {
1331
1292
  this.dt = dt;
1332
1293
  } else if (dt.zone.type === "iana") {
1333
1294
  this.dt = dt;
1334
- z55 = dt.zone.name;
1295
+ z61 = dt.zone.name;
1335
1296
  } else {
1336
- z55 = "UTC";
1297
+ z61 = "UTC";
1337
1298
  this.dt = dt.setZone("UTC").plus({ minutes: dt.offset });
1338
1299
  this.originalZone = dt.zone;
1339
1300
  }
1340
1301
  const intlOpts = __spreadValues({}, this.opts);
1341
- intlOpts.timeZone = intlOpts.timeZone || z55;
1302
+ intlOpts.timeZone = intlOpts.timeZone || z61;
1342
1303
  this.dtf = getCachedDTF(intl, intlOpts);
1343
1304
  }
1344
1305
  format() {
@@ -7054,6 +7015,63 @@ function friendlyDateTime(dateTimeish) {
7054
7015
  );
7055
7016
  }
7056
7017
  }
7018
+ var MAX_ASSET_NOTE_LENGTH = 5e3;
7019
+ var assetNoteBodySchema = zod.z.string().trim().min(1, "Note is required").max(MAX_ASSET_NOTE_LENGTH, `Note must be at most ${MAX_ASSET_NOTE_LENGTH} characters`);
7020
+ var assetNoteTimelineAtSchema = zod.z.coerce.date().refine((value) => value.getTime() <= DateTime.now().toUTC().toMillis(), {
7021
+ message: "Timeline date cannot be in the future"
7022
+ });
7023
+ zod.z.object({
7024
+ assetId: zod.z.number().int().positive(),
7025
+ note: assetNoteBodySchema,
7026
+ timelineAt: assetNoteTimelineAtSchema.optional()
7027
+ });
7028
+ zod.z.object({
7029
+ noteId: zod.z.number().int().positive(),
7030
+ note: assetNoteBodySchema,
7031
+ timelineAt: assetNoteTimelineAtSchema.nullish()
7032
+ });
7033
+ zod.z.object({
7034
+ noteId: zod.z.number().int().positive()
7035
+ });
7036
+ zod.z.object({
7037
+ slug: zod.z.string(),
7038
+ assetId: zod.z.number(),
7039
+ name: zod.z.string().optional(),
7040
+ description: zod.z.string().optional(),
7041
+ assetGroupId: zod.z.number().nullable().optional(),
7042
+ updatedByOrganizationId: zod.z.number().optional()
7043
+ });
7044
+ zod.z.object({
7045
+ content: zod.z.string().min(1, { message: "Content must be at least 3 character" }).max(1e3, { message: "Content must be less than 1000 characters" }),
7046
+ email: zod.z.string().email(),
7047
+ description: zod.z.string().optional().default("")
7048
+ });
7049
+ zod.z.object({
7050
+ /** Canonicalization algorithm version the client computed its prefixes with. */
7051
+ algoVersion: zod.z.number().int().positive(),
7052
+ /** 4-byte (8-hex-char) prefixes that hit the client's local index. */
7053
+ prefixes: zod.z.array(zod.z.string().regex(/^[0-9a-f]{8}$/)).min(1).max(200)
7054
+ }).describe("Blocklist confirm request");
7055
+ zod.z.object({
7056
+ /** Full 64-char SHA-256 hashes of blocked expressions, grouped by prefix. */
7057
+ fullHashes: zod.z.record(zod.z.string(), zod.z.array(zod.z.string()))
7058
+ }).describe("Blocklist confirm response");
7059
+ zod.z.void().describe("Blocklist version request");
7060
+ zod.z.object({
7061
+ /** Canonicalization algorithm version the artifacts + confirm are built with. */
7062
+ algoVersion: zod.z.number().int().positive(),
7063
+ snapshot: zod.z.object({
7064
+ token: zod.z.string(),
7065
+ url: zod.z.string().url(),
7066
+ prefixBytes: zod.z.number().int().positive(),
7067
+ count: zod.z.number().int().nonnegative()
7068
+ }),
7069
+ recent: zod.z.object({
7070
+ token: zod.z.string(),
7071
+ url: zod.z.string().url(),
7072
+ windowSeconds: zod.z.number().int().positive()
7073
+ })
7074
+ }).describe("Blocklist version response");
7057
7075
 
7058
7076
  // ../../internal/database/src/generated/prisma/enums.ts
7059
7077
  var BrandType = {
@@ -11488,6 +11506,50 @@ zod.z.object({
11488
11506
  endDate: zod.z.date()
11489
11507
  })
11490
11508
  });
11509
+ var DATE_ONLY_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
11510
+ var utcDateSchema = (endOfDay) => zod.z.preprocess((value) => {
11511
+ if (typeof value !== "string") {
11512
+ return value;
11513
+ }
11514
+ const parsed = DateTime.fromISO(value, { zone: "utc" });
11515
+ if (!parsed.isValid) {
11516
+ return new Date(Number.NaN);
11517
+ }
11518
+ return endOfDay && DATE_ONLY_PATTERN.test(value) ? parsed.endOf("day").toJSDate() : parsed.toJSDate();
11519
+ }, zod.z.coerce.date());
11520
+ var funnelCountsSchema = zod.z.object({
11521
+ blocked: zod.z.number().int().nonnegative().describe(
11522
+ "Distinct assets whose earliest report for this org falls in the range and whose current status or pending status is BLOCKED"
11523
+ )
11524
+ });
11525
+ zod.z.object({
11526
+ slug: zod.z.string().min(1).describe("Organization slug"),
11527
+ startDate: utcDateSchema(false).describe(
11528
+ "Only include assets whose earliest report was created on or after this date. A date-only value (YYYY-MM-DD) starts at midnight UTC"
11529
+ ),
11530
+ endDate: utcDateSchema(true).describe(
11531
+ "Only include assets whose earliest report was created on or before this date. A date-only value (YYYY-MM-DD) covers the whole day in UTC; pass a full timestamp for a precise cut-off"
11532
+ ),
11533
+ brandIds: zod.z.array(zod.z.number().int().positive()).optional().describe("Only include assets belonging to these brands")
11534
+ }).refine((input) => input.startDate.getTime() <= input.endDate.getTime(), {
11535
+ message: "startDate must be on or before endDate",
11536
+ path: ["startDate"]
11537
+ });
11538
+ zod.z.object({
11539
+ range: zod.z.object({
11540
+ startDate: zod.z.date(),
11541
+ endDate: zod.z.date()
11542
+ }),
11543
+ customer: funnelCountsSchema.describe(
11544
+ "Currently blocked assets first reported by the customer (reportedByCustomer, external reporter, or customer-role user)"
11545
+ ),
11546
+ chainpatrol: funnelCountsSchema.describe(
11547
+ "Currently blocked assets first reported by ChainPatrol staff or automation. Staff and automation are collapsed together."
11548
+ ),
11549
+ totals: funnelCountsSchema.describe(
11550
+ "customer + chainpatrol. This is a first-report blocked cohort and does not equal metrics/found, which counts assets by blockedAt."
11551
+ )
11552
+ });
11491
11553
  zod.z.object({
11492
11554
  slug: zod.z.string().min(1),
11493
11555
  startDate: zod.z.date().optional(),
@@ -11519,8 +11581,8 @@ zod.z.object({
11519
11581
  })
11520
11582
  )
11521
11583
  });
11522
- var DATE_ONLY_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
11523
- var utcDateSchema = (endOfDay) => zod.z.preprocess((value) => {
11584
+ var DATE_ONLY_PATTERN2 = /^\d{4}-\d{2}-\d{2}$/;
11585
+ var utcDateSchema2 = (endOfDay) => zod.z.preprocess((value) => {
11524
11586
  if (typeof value !== "string") {
11525
11587
  return value;
11526
11588
  }
@@ -11528,14 +11590,47 @@ var utcDateSchema = (endOfDay) => zod.z.preprocess((value) => {
11528
11590
  if (!parsed.isValid) {
11529
11591
  return new Date(Number.NaN);
11530
11592
  }
11531
- return endOfDay && DATE_ONLY_PATTERN.test(value) ? parsed.endOf("day").toJSDate() : parsed.toJSDate();
11593
+ return endOfDay && DATE_ONLY_PATTERN2.test(value) ? parsed.endOf("day").toJSDate() : parsed.toJSDate();
11532
11594
  }, zod.z.coerce.date());
11595
+
11596
+ // ../../internal/validation/src/public/metrics/takedown-success-rate.ts
11597
+ zod.z.object({
11598
+ slug: zod.z.string().min(1).describe("Organization slug"),
11599
+ startDate: utcDateSchema2(false).optional().describe(
11600
+ "Only include takedowns first submitted to a provider on or after this date. A date-only value (YYYY-MM-DD) starts at midnight UTC"
11601
+ ),
11602
+ endDate: utcDateSchema2(true).optional().describe(
11603
+ "Only include takedowns first submitted to a provider on or before this date. A date-only value (YYYY-MM-DD) covers the whole day in UTC; pass a full timestamp for a precise cut-off"
11604
+ ),
11605
+ blockLabel: zod.z.string().optional().describe(
11606
+ 'Only include assets blocked with this review label, e.g. "Brand Impersonation", "Employee Impersonation" or "Targeting Org Users"'
11607
+ ),
11608
+ brandType: zod.z.nativeEnum(BrandType).optional().describe("Only include assets belonging to brands of this type"),
11609
+ countryCodes: zod.z.array(zod.z.string().length(2)).optional().describe("Only include assets scanned from these ISO 3166-1 alpha-2 countries"),
11610
+ brandIds: zod.z.array(zod.z.number().int().positive()).optional().describe("Only include assets belonging to these brands")
11611
+ });
11612
+ zod.z.object({
11613
+ byAssetType: zod.z.array(
11614
+ zod.z.object({
11615
+ assetType: zod.z.nativeEnum(AssetType).describe("Asset type the takedowns were filed against"),
11616
+ submitted: zod.z.number().describe("Takedowns whose first submission to a provider fell in the range"),
11617
+ completed: zod.z.number().describe("Of those, how many ended in a takedown"),
11618
+ stillOpen: zod.z.number().describe("Of those, how many have not yet reached an outcome"),
11619
+ successRate: zod.z.number().describe("completed / submitted, 0-1. Still-open takedowns count against it"),
11620
+ resolvedSuccessRate: zod.z.number().nullable().describe(
11621
+ "completed / (submitted - stillOpen), 0-1, or null when nothing has resolved yet. Higher than successRate whenever work is still outstanding"
11622
+ )
11623
+ })
11624
+ ).describe(
11625
+ "Takedown success rate, one entry per asset type, descending by number submitted"
11626
+ )
11627
+ });
11533
11628
  zod.z.object({
11534
11629
  slug: zod.z.string().min(1).describe("Organization slug"),
11535
- startDate: utcDateSchema(false).optional().describe(
11630
+ startDate: utcDateSchema2(false).optional().describe(
11536
11631
  "Only include takedowns completed on or after this date. A date-only value (YYYY-MM-DD) starts at midnight UTC"
11537
11632
  ),
11538
- endDate: utcDateSchema(true).optional().describe(
11633
+ endDate: utcDateSchema2(true).optional().describe(
11539
11634
  "Only include takedowns completed on or before this date. A date-only value (YYYY-MM-DD) covers the whole day in UTC; pass a full timestamp for a precise cut-off"
11540
11635
  ),
11541
11636
  blockLabel: zod.z.string().optional().describe(
@@ -11558,6 +11653,46 @@ zod.z.object({
11558
11653
  "Median time from takedown start to completion, one entry per asset type, ascending by median"
11559
11654
  )
11560
11655
  });
11656
+ var TakedownProviderRoleSchema = zod.z.enum([
11657
+ "HOSTING_PROVIDER",
11658
+ "DOMAIN_REGISTRAR",
11659
+ "TLD_REGISTRAR"
11660
+ ]);
11661
+ zod.z.object({
11662
+ slug: zod.z.string().min(1).describe("Organization slug"),
11663
+ startDate: utcDateSchema2(false).optional().describe(
11664
+ "Only include takedowns completed on or after this date. A date-only value (YYYY-MM-DD) starts at midnight UTC"
11665
+ ),
11666
+ endDate: utcDateSchema2(true).optional().describe(
11667
+ "Only include takedowns completed on or before this date. A date-only value (YYYY-MM-DD) covers the whole day in UTC; pass a full timestamp for a precise cut-off"
11668
+ ),
11669
+ roles: zod.z.array(TakedownProviderRoleSchema).optional().describe(
11670
+ "Only break down these provider roles. Omit for every role. A takedown filed against both a host and a registrar is counted once under each"
11671
+ ),
11672
+ blockLabel: zod.z.string().optional().describe(
11673
+ 'Only include assets blocked with this review label, e.g. "Brand Impersonation", "Employee Impersonation" or "Targeting Org Users"'
11674
+ ),
11675
+ brandType: zod.z.nativeEnum(BrandType).optional().describe("Only include assets belonging to brands of this type"),
11676
+ countryCodes: zod.z.array(zod.z.string().length(2)).optional().describe("Only include assets scanned from these ISO 3166-1 alpha-2 countries"),
11677
+ brandIds: zod.z.array(zod.z.number().int().positive()).optional().describe("Only include assets belonging to these brands")
11678
+ });
11679
+ zod.z.object({
11680
+ byProvider: zod.z.array(
11681
+ zod.z.object({
11682
+ providerId: zod.z.number().describe("Takedown provider id"),
11683
+ providerName: zod.z.string().describe("Takedown provider name"),
11684
+ role: TakedownProviderRoleSchema.describe(
11685
+ "Which provider slot of the takedown this median is computed over"
11686
+ ),
11687
+ medianTakedownMinutes: zod.z.number(),
11688
+ medianTakedownHours: zod.z.number(),
11689
+ medianTakedownDays: zod.z.number(),
11690
+ count: zod.z.number().describe("Number of completed takedowns behind the median")
11691
+ })
11692
+ ).describe(
11693
+ "Median time from takedown start to completion, one entry per provider and role, ascending by median"
11694
+ )
11695
+ });
11561
11696
  zod.z.object({
11562
11697
  slug: zod.z.string().min(1).describe("Organization slug"),
11563
11698
  months: zod.z.number().int().min(2).max(36).default(12).describe("Size of the trailing window in whole months, including the current month"),
@@ -11813,6 +11948,13 @@ zod.z.object({
11813
11948
  brands: zod.z.array(brandItemSchema).describe("List of brands")
11814
11949
  }).describe("Successful operation");
11815
11950
  var FILTERABLE_SUBSCRIPTION_STATUSES = [
11951
+ SubscriptionStatus.PROSPECT,
11952
+ SubscriptionStatus.POC,
11953
+ SubscriptionStatus.ACTIVE,
11954
+ SubscriptionStatus.INTEGRATION,
11955
+ SubscriptionStatus.INACTIVE
11956
+ ];
11957
+ [
11816
11958
  SubscriptionStatus.PROSPECT,
11817
11959
  SubscriptionStatus.POC,
11818
11960
  SubscriptionStatus.ACTIVE,
@@ -11847,7 +11989,9 @@ var userOrgsPendingServiceApprovalFilterSchema = zod.z.object({
11847
11989
  }).optional();
11848
11990
  zod.z.object({
11849
11991
  query: zod.z.string().default(""),
11850
- subscriptionStatus: zod.z.array(UserOrgsSubscriptionStatusSchema).min(1).optional(),
11992
+ subscriptionStatus: zod.z.array(UserOrgsSubscriptionStatusSchema).min(1).optional().describe(
11993
+ "Statuses to include. Defaults to the live set (PROSPECT/POC/ACTIVE/INTEGRATION); pass INACTIVE explicitly to see churned organizations."
11994
+ ),
11851
11995
  services: userOrgsServicesFilterObjectSchema.optional(),
11852
11996
  obligatoryAdminApproval: userOrgsObligatoryAdminApprovalFilterSchema,
11853
11997
  pendingServiceApproval: userOrgsPendingServiceApprovalFilterSchema
@@ -11993,7 +12137,9 @@ var organizationMetricsIncludeFieldSchema = zod.z.enum([
11993
12137
  "newThreats",
11994
12138
  "threatsWatchlisted",
11995
12139
  "takedownsFiled",
12140
+ "takedownsInProgress",
11996
12141
  "takedownsCompleted",
12142
+ "takedownsCancelled",
11997
12143
  "domainThreats",
11998
12144
  "twitterThreats",
11999
12145
  "telegramThreats",
@@ -12026,7 +12172,7 @@ zod.z.object({
12026
12172
  "Shortcut for 'every org the caller is authorized to read'. Reuses the same filter logic as `/user/orgs`: org-scoped API keys are rejected (they're pinned to one org \u2014 use the default form); user API keys / Bearer sessions resolve to the user's memberships (or every active org for staff). Combine with `subscriptionStatus` and `services` to narrow (e.g. only paying customers with takedowns enabled). The resolved org set is still capped at " + String(ORGANIZATION_METRICS_MAX_SLUGS) + " and a 413 is returned if exceeded. Mutually exclusive with `organizationSlug`, `slugs`, and `brandSlug`."
12027
12173
  ),
12028
12174
  subscriptionStatus: zod.z.array(UserOrgsSubscriptionStatusSchema).min(1).optional().describe(
12029
- "Only meaningful with `allMyOrgs: true`. Defaults to the same set `/user/orgs` uses (ACTIVE/POC/PROSPECT/INTEGRATION \u2014 INACTIVE is always excluded)."
12175
+ "Only meaningful with `allMyOrgs: true`. Defaults to the same set `/user/orgs` uses (ACTIVE/POC/PROSPECT/INTEGRATION); pass INACTIVE explicitly to include churned organizations."
12030
12176
  ),
12031
12177
  services: organizationMetricsServicesFilterSchema.describe(
12032
12178
  "Only meaningful with `allMyOrgs: true`. Same shape as `/user/orgs`'s `services` filter \u2014 every service accepts `{ active }`, and **only `takedowns` additionally accepts `automated`** (the one service where manual-vs-automated changes real platform behavior). E.g. `{ takedowns: { active: true, automated: false } }` for 'orgs with takedowns enabled but automation off'."
@@ -12035,7 +12181,7 @@ zod.z.object({
12035
12181
  startDate: zod.z.coerce.date().optional(),
12036
12182
  endDate: zod.z.coerce.date().optional(),
12037
12183
  include: zod.z.array(organizationMetricsIncludeFieldSchema).optional().describe(
12038
- "Subset of metrics to compute. When omitted or empty, every metric is computed (legacy behavior). Each value in `include` costs roughly one Prisma aggregate, so narrow the list when an agent only needs a couple of numbers \u2014 this is the main lever for keeping big-org calls inside the 30s function cap. Fields not in `include` come back as `null`."
12184
+ "Subset of metrics to compute. When omitted or empty, every metric is computed (legacy behavior). Each value in `include` costs roughly one aggregate query, so narrow the list when an agent only needs a couple of numbers \u2014 this is the main lever for keeping big-org calls inside the 30s function cap. Fields not in `include` come back as `null`. `takedownsInProgress` and `takedownsCancelled` count takedowns currently in that status whose `updatedAt` falls in the window (same windowing as `takedownsFiled`), so they reconcile with the takedowns list filtered by status over the same date range."
12039
12185
  )
12040
12186
  }).refine(
12041
12187
  (value) => {
@@ -12063,7 +12209,9 @@ var metricsObjectSchema = zod.z.object({
12063
12209
  newThreats: metricValue,
12064
12210
  threatsWatchlisted: metricValue,
12065
12211
  takedownsFiled: metricValue,
12212
+ takedownsInProgress: metricValue,
12066
12213
  takedownsCompleted: metricValue,
12214
+ takedownsCancelled: metricValue,
12067
12215
  domainThreats: metricValue,
12068
12216
  twitterThreats: metricValue,
12069
12217
  telegramThreats: metricValue,
@@ -12681,6 +12829,74 @@ zod.z.object({
12681
12829
  hiddenMetrics: zod.z.record(zod.z.string(), zod.z.boolean()).default({}),
12682
12830
  overriddenMetrics: zod.z.record(zod.z.string(), zod.z.boolean()).default({})
12683
12831
  });
12832
+ var ONE_PAGER_BUDGETS = {
12833
+ headline: 60,
12834
+ badge: 28,
12835
+ frontLabel: 44,
12836
+ paragraph: 480,
12837
+ paragraphsMax: 2,
12838
+ heroPhrase: 80,
12839
+ heroNarrative: 300,
12840
+ divergenceLine: 220,
12841
+ xp01Line: 220
12842
+ };
12843
+ var onePagerFactPayloadSchema = zod.z.object({
12844
+ /** Raw (not defanged) asset contents the prose may reference. */
12845
+ assetContents: zod.z.array(zod.z.string()),
12846
+ /** Every numeric token (as string) the prose is allowed to contain. */
12847
+ numbers: zod.z.array(zod.z.string()),
12848
+ /** ISO dates the prose may reference. */
12849
+ dates: zod.z.array(zod.z.string()),
12850
+ platforms: zod.z.array(zod.z.string()),
12851
+ groupSize: zod.z.number(),
12852
+ takedownCompletedAt: zod.z.string().nullable(),
12853
+ activeSince: zod.z.string().nullable()
12854
+ });
12855
+ var onePagerStorySchema = zod.z.object({
12856
+ archetypeId: zod.z.string(),
12857
+ archetypeCategory: zod.z.string(),
12858
+ /** 1-5 = placed on the page; null = alternate available for swap. */
12859
+ slot: zod.z.number().int().min(1).max(5).nullable(),
12860
+ frontLabel: zod.z.string().max(ONE_PAGER_BUDGETS.frontLabel),
12861
+ headline: zod.z.string().max(ONE_PAGER_BUDGETS.headline),
12862
+ badge: zod.z.string().max(ONE_PAGER_BUDGETS.badge),
12863
+ paragraphs: zod.z.array(zod.z.string().max(ONE_PAGER_BUDGETS.paragraph)).min(1).max(ONE_PAGER_BUDGETS.paragraphsMax),
12864
+ /** Defanged, display-ready asset identifiers. */
12865
+ assetList: zod.z.array(zod.z.string()).max(10),
12866
+ screenshotUrls: zod.z.array(zod.z.string()),
12867
+ selectedScreenshotUrls: zod.z.array(zod.z.string()).max(2),
12868
+ xp01Line: zod.z.string().max(ONE_PAGER_BUDGETS.xp01Line).nullable(),
12869
+ factPayload: onePagerFactPayloadSchema,
12870
+ proseSource: zod.z.enum(["llm", "template"])
12871
+ });
12872
+ var onePagerLandscapeRowSchema = zod.z.object({
12873
+ platform: zod.z.string(),
12874
+ count: zod.z.number(),
12875
+ sharePct: zod.z.number()
12876
+ });
12877
+ var onePagerFormDataObjectSchema = zod.z.object({
12878
+ orgName: zod.z.string(),
12879
+ orgSlug: zod.z.string(),
12880
+ periodLabel: zod.z.string(),
12881
+ heroStat: zod.z.object({
12882
+ value: zod.z.string(),
12883
+ phrase: zod.z.string().max(ONE_PAGER_BUDGETS.heroPhrase)
12884
+ }),
12885
+ heroNarrative: zod.z.string().max(ONE_PAGER_BUDGETS.heroNarrative),
12886
+ metricsRows: zod.z.array(zod.z.object({ label: zod.z.string(), value: zod.z.string() })).length(4),
12887
+ landscape: zod.z.object({
12888
+ rows: zod.z.array(onePagerLandscapeRowSchema).max(8),
12889
+ otherCount: zod.z.number().nullable(),
12890
+ otherPlatformsLabel: zod.z.string().nullable(),
12891
+ divergenceLine: zod.z.string().max(ONE_PAGER_BUDGETS.divergenceLine).nullable()
12892
+ }),
12893
+ stories: zod.z.array(onePagerStorySchema).min(1).max(6),
12894
+ footer: zod.z.object({ contactName: zod.z.string(), contactLine: zod.z.string() })
12895
+ });
12896
+ onePagerFormDataObjectSchema.refine(
12897
+ (data) => data.stories.some((s2) => s2.slot !== null),
12898
+ { message: "at least one story must be placed", path: ["stories"] }
12899
+ );
12684
12900
  zod.z.object({
12685
12901
  x: zod.z.number().min(0).max(1),
12686
12902
  y: zod.z.number().min(0).max(1),
@@ -13446,20 +13662,20 @@ var convertA98ToXyz65_default = convertA98ToXyz65;
13446
13662
 
13447
13663
  // ../../node_modules/culori/src/a98/convertXyz65ToA98.js
13448
13664
  var gamma = (v) => Math.pow(Math.abs(v), 256 / 563) * Math.sign(v);
13449
- var convertXyz65ToA98 = ({ x, y, z: z55, alpha }) => {
13665
+ var convertXyz65ToA98 = ({ x, y, z: z61, alpha }) => {
13450
13666
  if (x === void 0) x = 0;
13451
13667
  if (y === void 0) y = 0;
13452
- if (z55 === void 0) z55 = 0;
13668
+ if (z61 === void 0) z61 = 0;
13453
13669
  let res = {
13454
13670
  mode: "a98",
13455
13671
  r: gamma(
13456
- x * 2.0415879038107465 - y * 0.5650069742788597 - 0.3447313507783297 * z55
13672
+ x * 2.0415879038107465 - y * 0.5650069742788597 - 0.3447313507783297 * z61
13457
13673
  ),
13458
13674
  g: gamma(
13459
- x * -0.9692436362808798 + y * 1.8759675015077206 + 0.0415550574071756 * z55
13675
+ x * -0.9692436362808798 + y * 1.8759675015077206 + 0.0415550574071756 * z61
13460
13676
  ),
13461
13677
  b: gamma(
13462
- x * 0.0134442806320312 - y * 0.1183623922310184 + 1.0151749943912058 * z55
13678
+ x * 0.0134442806320312 - y * 0.1183623922310184 + 1.0151749943912058 * z61
13463
13679
  )
13464
13680
  };
13465
13681
  if (alpha !== void 0) {
@@ -13526,14 +13742,14 @@ var convertLrgbToRgb = ({ r, g, b, alpha }, mode = "rgb") => {
13526
13742
  var convertLrgbToRgb_default = convertLrgbToRgb;
13527
13743
 
13528
13744
  // ../../node_modules/culori/src/xyz65/convertXyz65ToRgb.js
13529
- var convertXyz65ToRgb = ({ x, y, z: z55, alpha }) => {
13745
+ var convertXyz65ToRgb = ({ x, y, z: z61, alpha }) => {
13530
13746
  if (x === void 0) x = 0;
13531
13747
  if (y === void 0) y = 0;
13532
- if (z55 === void 0) z55 = 0;
13748
+ if (z61 === void 0) z61 = 0;
13533
13749
  let res = convertLrgbToRgb_default({
13534
- r: x * 3.2409699419045226 - y * 1.537383177570094 - 0.4986107602930034 * z55,
13535
- g: x * -0.9692436362808796 + y * 1.8759675015077204 + 0.0415550574071756 * z55,
13536
- b: x * 0.0556300796969936 - y * 0.2039769588889765 + 1.0569715142428784 * z55
13750
+ r: x * 3.2409699419045226 - y * 1.537383177570094 - 0.4986107602930034 * z61,
13751
+ g: x * -0.9692436362808796 + y * 1.8759675015077204 + 0.0415550574071756 * z61,
13752
+ b: x * 0.0556300796969936 - y * 0.2039769588889765 + 1.0569715142428784 * z61
13537
13753
  });
13538
13754
  if (alpha !== void 0) {
13539
13755
  res.alpha = alpha;
@@ -13783,13 +13999,13 @@ var convertLab65ToRgb_default = convertLab65ToRgb;
13783
13999
 
13784
14000
  // ../../node_modules/culori/src/lab65/convertXyz65ToLab65.js
13785
14001
  var f = (value) => value > e ? Math.cbrt(value) : (k * value + 16) / 116;
13786
- var convertXyz65ToLab65 = ({ x, y, z: z55, alpha }) => {
14002
+ var convertXyz65ToLab65 = ({ x, y, z: z61, alpha }) => {
13787
14003
  if (x === void 0) x = 0;
13788
14004
  if (y === void 0) y = 0;
13789
- if (z55 === void 0) z55 = 0;
14005
+ if (z61 === void 0) z61 = 0;
13790
14006
  let f0 = f(x / D65.X);
13791
14007
  let f1 = f(y / D65.Y);
13792
- let f22 = f(z55 / D65.Z);
14008
+ let f22 = f(z61 / D65.Z);
13793
14009
  let res = {
13794
14010
  mode: "lab65",
13795
14011
  l: 116 * f1 - 16,
@@ -14446,10 +14662,10 @@ var convertItpToXyz65_default = convertItpToXyz65;
14446
14662
 
14447
14663
  // ../../node_modules/culori/src/itp/convertXyz65ToItp.js
14448
14664
  var toAbs = (c2 = 0) => Math.max(c2 * YW, 0);
14449
- var convertXyz65ToItp = ({ x, y, z: z55, alpha }) => {
14665
+ var convertXyz65ToItp = ({ x, y, z: z61, alpha }) => {
14450
14666
  const absX = toAbs(x);
14451
14667
  const absY = toAbs(y);
14452
- const absZ = toAbs(z55);
14668
+ const absZ = toAbs(z61);
14453
14669
  const l2 = transferPqEncode(
14454
14670
  0.3592832590121217 * absX + 0.6976051147779502 * absY - 0.0358915932320289 * absZ
14455
14671
  );
@@ -14507,15 +14723,15 @@ var jabPqEncode = (v) => {
14507
14723
  return Math.pow((C1 + C2 * vn3) / (1 + C3 * vn3), p);
14508
14724
  };
14509
14725
  var abs = (v = 0) => Math.max(v * 203, 0);
14510
- var convertXyz65ToJab = ({ x, y, z: z55, alpha }) => {
14726
+ var convertXyz65ToJab = ({ x, y, z: z61, alpha }) => {
14511
14727
  x = abs(x);
14512
14728
  y = abs(y);
14513
- z55 = abs(z55);
14514
- let xp = 1.15 * x - 0.15 * z55;
14729
+ z61 = abs(z61);
14730
+ let xp = 1.15 * x - 0.15 * z61;
14515
14731
  let yp = 0.66 * y + 0.34 * x;
14516
- let l2 = jabPqEncode(0.41478972 * xp + 0.579999 * yp + 0.014648 * z55);
14517
- let m = jabPqEncode(-0.20151 * xp + 1.120649 * yp + 0.0531008 * z55);
14518
- let s2 = jabPqEncode(-0.0166008 * xp + 0.2648 * yp + 0.6684799 * z55);
14732
+ let l2 = jabPqEncode(0.41478972 * xp + 0.579999 * yp + 0.014648 * z61);
14733
+ let m = jabPqEncode(-0.20151 * xp + 1.120649 * yp + 0.0531008 * z61);
14734
+ let s2 = jabPqEncode(-0.0166008 * xp + 0.2648 * yp + 0.6684799 * z61);
14519
14735
  let i = (l2 + m) / 2;
14520
14736
  let res = {
14521
14737
  mode: "jab",
@@ -14701,14 +14917,14 @@ var convertLabToXyz50 = ({ l: l2, a, b, alpha }) => {
14701
14917
  var convertLabToXyz50_default = convertLabToXyz50;
14702
14918
 
14703
14919
  // ../../node_modules/culori/src/xyz50/convertXyz50ToRgb.js
14704
- var convertXyz50ToRgb = ({ x, y, z: z55, alpha }) => {
14920
+ var convertXyz50ToRgb = ({ x, y, z: z61, alpha }) => {
14705
14921
  if (x === void 0) x = 0;
14706
14922
  if (y === void 0) y = 0;
14707
- if (z55 === void 0) z55 = 0;
14923
+ if (z61 === void 0) z61 = 0;
14708
14924
  let res = convertLrgbToRgb_default({
14709
- r: x * 3.1341359569958707 - y * 1.6173863321612538 - 0.4906619460083532 * z55,
14710
- g: x * -0.978795502912089 + y * 1.916254567259524 + 0.03344273116131949 * z55,
14711
- b: x * 0.07195537988411677 - y * 0.2289768264158322 + 1.405386058324125 * z55
14925
+ r: x * 3.1341359569958707 - y * 1.6173863321612538 - 0.4906619460083532 * z61,
14926
+ g: x * -0.978795502912089 + y * 1.916254567259524 + 0.03344273116131949 * z61,
14927
+ b: x * 0.07195537988411677 - y * 0.2289768264158322 + 1.405386058324125 * z61
14712
14928
  });
14713
14929
  if (alpha !== void 0) {
14714
14930
  res.alpha = alpha;
@@ -14739,13 +14955,13 @@ var convertRgbToXyz50_default = convertRgbToXyz50;
14739
14955
 
14740
14956
  // ../../node_modules/culori/src/lab/convertXyz50ToLab.js
14741
14957
  var f2 = (value) => value > e3 ? Math.cbrt(value) : (k3 * value + 16) / 116;
14742
- var convertXyz50ToLab = ({ x, y, z: z55, alpha }) => {
14958
+ var convertXyz50ToLab = ({ x, y, z: z61, alpha }) => {
14743
14959
  if (x === void 0) x = 0;
14744
14960
  if (y === void 0) y = 0;
14745
- if (z55 === void 0) z55 = 0;
14961
+ if (z61 === void 0) z61 = 0;
14746
14962
  let f0 = f2(x / D50.X);
14747
14963
  let f1 = f2(y / D50.Y);
14748
- let f22 = f2(z55 / D50.Z);
14964
+ let f22 = f2(z61 / D50.Z);
14749
14965
  let res = {
14750
14966
  mode: "lab",
14751
14967
  l: 116 * f1 - 16,
@@ -14980,18 +15196,18 @@ var convertLchuvToLuv = ({ l: l2, c: c2, h, alpha }) => {
14980
15196
  var convertLchuvToLuv_default = convertLchuvToLuv;
14981
15197
 
14982
15198
  // ../../node_modules/culori/src/luv/convertXyz50ToLuv.js
14983
- var u_fn = (x, y, z55) => 4 * x / (x + 15 * y + 3 * z55);
14984
- var v_fn = (x, y, z55) => 9 * y / (x + 15 * y + 3 * z55);
15199
+ var u_fn = (x, y, z61) => 4 * x / (x + 15 * y + 3 * z61);
15200
+ var v_fn = (x, y, z61) => 9 * y / (x + 15 * y + 3 * z61);
14985
15201
  var un = u_fn(D50.X, D50.Y, D50.Z);
14986
15202
  var vn = v_fn(D50.X, D50.Y, D50.Z);
14987
15203
  var l_fn = (value) => value <= e3 ? k3 * value : 116 * Math.cbrt(value) - 16;
14988
- var convertXyz50ToLuv = ({ x, y, z: z55, alpha }) => {
15204
+ var convertXyz50ToLuv = ({ x, y, z: z61, alpha }) => {
14989
15205
  if (x === void 0) x = 0;
14990
15206
  if (y === void 0) y = 0;
14991
- if (z55 === void 0) z55 = 0;
15207
+ if (z61 === void 0) z61 = 0;
14992
15208
  let l2 = l_fn(y / D50.Y);
14993
- let u = u_fn(x, y, z55);
14994
- let v = v_fn(x, y, z55);
15209
+ let u = u_fn(x, y, z61);
15210
+ let v = v_fn(x, y, z61);
14995
15211
  if (!isFinite(u) || !isFinite(v)) {
14996
15212
  l2 = u = v = 0;
14997
15213
  } else {
@@ -15012,8 +15228,8 @@ var convertXyz50ToLuv = ({ x, y, z: z55, alpha }) => {
15012
15228
  var convertXyz50ToLuv_default = convertXyz50ToLuv;
15013
15229
 
15014
15230
  // ../../node_modules/culori/src/luv/convertLuvToXyz50.js
15015
- var u_fn2 = (x, y, z55) => 4 * x / (x + 15 * y + 3 * z55);
15016
- var v_fn2 = (x, y, z55) => 9 * y / (x + 15 * y + 3 * z55);
15231
+ var u_fn2 = (x, y, z61) => 4 * x / (x + 15 * y + 3 * z61);
15232
+ var v_fn2 = (x, y, z61) => 9 * y / (x + 15 * y + 3 * z61);
15017
15233
  var un2 = u_fn2(D50.X, D50.Y, D50.Z);
15018
15234
  var vn2 = v_fn2(D50.X, D50.Y, D50.Z);
15019
15235
  var convertLuvToXyz50 = ({ l: l2, u, v, alpha }) => {
@@ -15027,8 +15243,8 @@ var convertLuvToXyz50 = ({ l: l2, u, v, alpha }) => {
15027
15243
  let vp = v / (13 * l2) + vn2;
15028
15244
  let y = D50.Y * (l2 <= 8 ? l2 / k3 : Math.pow((l2 + 16) / 116, 3));
15029
15245
  let x = y * (9 * up) / (4 * vp);
15030
- let z55 = y * (12 - 3 * up - 20 * vp) / (4 * vp);
15031
- let res = { mode: "xyz50", x, y, z: z55 };
15246
+ let z61 = y * (12 - 3 * up - 20 * vp) / (4 * vp);
15247
+ let res = { mode: "xyz50", x, y, z: z61 };
15032
15248
  if (alpha !== void 0) {
15033
15249
  res.alpha = alpha;
15034
15250
  }
@@ -15645,15 +15861,15 @@ var convertP3ToXyz65 = (rgb2) => {
15645
15861
  var convertP3ToXyz65_default = convertP3ToXyz65;
15646
15862
 
15647
15863
  // ../../node_modules/culori/src/p3/convertXyz65ToP3.js
15648
- var convertXyz65ToP3 = ({ x, y, z: z55, alpha }) => {
15864
+ var convertXyz65ToP3 = ({ x, y, z: z61, alpha }) => {
15649
15865
  if (x === void 0) x = 0;
15650
15866
  if (y === void 0) y = 0;
15651
- if (z55 === void 0) z55 = 0;
15867
+ if (z61 === void 0) z61 = 0;
15652
15868
  let res = convertLrgbToRgb_default(
15653
15869
  {
15654
- r: x * 2.4934969119414263 - y * 0.9313836179191242 - 0.402710784450717 * z55,
15655
- g: x * -0.8294889695615749 + y * 1.7626640603183465 + 0.0236246858419436 * z55,
15656
- b: x * 0.0358458302437845 - y * 0.0761723892680418 + 0.9568845240076871 * z55
15870
+ r: x * 2.4934969119414263 - y * 0.9313836179191242 - 0.402710784450717 * z61,
15871
+ g: x * -0.8294889695615749 + y * 1.7626640603183465 + 0.0236246858419436 * z61,
15872
+ b: x * 0.0358458302437845 - y * 0.0761723892680418 + 0.9568845240076871 * z61
15657
15873
  },
15658
15874
  "p3"
15659
15875
  );
@@ -15688,19 +15904,19 @@ var gamma2 = (v) => {
15688
15904
  }
15689
15905
  return 16 * v;
15690
15906
  };
15691
- var convertXyz50ToProphoto = ({ x, y, z: z55, alpha }) => {
15907
+ var convertXyz50ToProphoto = ({ x, y, z: z61, alpha }) => {
15692
15908
  if (x === void 0) x = 0;
15693
15909
  if (y === void 0) y = 0;
15694
- if (z55 === void 0) z55 = 0;
15910
+ if (z61 === void 0) z61 = 0;
15695
15911
  let res = {
15696
15912
  mode: "prophoto",
15697
15913
  r: gamma2(
15698
- x * 1.3457868816471585 - y * 0.2555720873797946 - 0.0511018649755453 * z55
15914
+ x * 1.3457868816471585 - y * 0.2555720873797946 - 0.0511018649755453 * z61
15699
15915
  ),
15700
15916
  g: gamma2(
15701
- x * -0.5446307051249019 + y * 1.5082477428451466 + 0.0205274474364214 * z55
15917
+ x * -0.5446307051249019 + y * 1.5082477428451466 + 0.0205274474364214 * z61
15702
15918
  ),
15703
- b: gamma2(x * 0 + y * 0 + 1.2119675456389452 * z55)
15919
+ b: gamma2(x * 0 + y * 0 + 1.2119675456389452 * z61)
15704
15920
  };
15705
15921
  if (alpha !== void 0) {
15706
15922
  res.alpha = alpha;
@@ -15760,20 +15976,20 @@ var gamma3 = (v) => {
15760
15976
  }
15761
15977
  return 4.5 * v;
15762
15978
  };
15763
- var convertXyz65ToRec2020 = ({ x, y, z: z55, alpha }) => {
15979
+ var convertXyz65ToRec2020 = ({ x, y, z: z61, alpha }) => {
15764
15980
  if (x === void 0) x = 0;
15765
15981
  if (y === void 0) y = 0;
15766
- if (z55 === void 0) z55 = 0;
15982
+ if (z61 === void 0) z61 = 0;
15767
15983
  let res = {
15768
15984
  mode: "rec2020",
15769
15985
  r: gamma3(
15770
- x * 1.7166511879712683 - y * 0.3556707837763925 - 0.2533662813736599 * z55
15986
+ x * 1.7166511879712683 - y * 0.3556707837763925 - 0.2533662813736599 * z61
15771
15987
  ),
15772
15988
  g: gamma3(
15773
- x * -0.6666843518324893 + y * 1.6164812366349395 + 0.0157685458139111 * z55
15989
+ x * -0.6666843518324893 + y * 1.6164812366349395 + 0.0157685458139111 * z61
15774
15990
  ),
15775
15991
  b: gamma3(
15776
- x * 0.0176398574453108 - y * 0.0427706132578085 + 0.9421031212354739 * z55
15992
+ x * 0.0176398574453108 - y * 0.0427706132578085 + 0.9421031212354739 * z61
15777
15993
  )
15778
15994
  };
15779
15995
  if (alpha !== void 0) {
@@ -15926,15 +16142,15 @@ var definition_default26 = definition26;
15926
16142
 
15927
16143
  // ../../node_modules/culori/src/xyz65/convertXyz65ToXyz50.js
15928
16144
  var convertXyz65ToXyz50 = (xyz652) => {
15929
- let { x, y, z: z55, alpha } = xyz652;
16145
+ let { x, y, z: z61, alpha } = xyz652;
15930
16146
  if (x === void 0) x = 0;
15931
16147
  if (y === void 0) y = 0;
15932
- if (z55 === void 0) z55 = 0;
16148
+ if (z61 === void 0) z61 = 0;
15933
16149
  let res = {
15934
16150
  mode: "xyz50",
15935
- x: 1.0479298208405488 * x + 0.0229467933410191 * y - 0.0501922295431356 * z55,
15936
- y: 0.0296278156881593 * x + 0.990434484573249 * y - 0.0170738250293851 * z55,
15937
- z: -0.0092430581525912 * x + 0.0150551448965779 * y + 0.7518742899580008 * z55
16151
+ x: 1.0479298208405488 * x + 0.0229467933410191 * y - 0.0501922295431356 * z61,
16152
+ y: 0.0296278156881593 * x + 0.990434484573249 * y - 0.0170738250293851 * z61,
16153
+ z: -0.0092430581525912 * x + 0.0150551448965779 * y + 0.7518742899580008 * z61
15938
16154
  };
15939
16155
  if (alpha !== void 0) {
15940
16156
  res.alpha = alpha;
@@ -15945,15 +16161,15 @@ var convertXyz65ToXyz50_default = convertXyz65ToXyz50;
15945
16161
 
15946
16162
  // ../../node_modules/culori/src/xyz65/convertXyz50ToXyz65.js
15947
16163
  var convertXyz50ToXyz65 = (xyz502) => {
15948
- let { x, y, z: z55, alpha } = xyz502;
16164
+ let { x, y, z: z61, alpha } = xyz502;
15949
16165
  if (x === void 0) x = 0;
15950
16166
  if (y === void 0) y = 0;
15951
- if (z55 === void 0) z55 = 0;
16167
+ if (z61 === void 0) z61 = 0;
15952
16168
  let res = {
15953
16169
  mode: "xyz65",
15954
- x: 0.9554734527042182 * x - 0.0230985368742614 * y + 0.0632593086610217 * z55,
15955
- y: -0.0283697069632081 * x + 1.0099954580058226 * y + 0.021041398966943 * z55,
15956
- z: 0.0123140016883199 * x - 0.0205076964334779 * y + 1.3303659366080753 * z55
16170
+ x: 0.9554734527042182 * x - 0.0230985368742614 * y + 0.0632593086610217 * z61,
16171
+ y: -0.0283697069632081 * x + 1.0099954580058226 * y + 0.021041398966943 * z61,
16172
+ z: 0.0123140016883199 * x - 0.0205076964334779 * y + 1.3303659366080753 * z61
15957
16173
  };
15958
16174
  if (alpha !== void 0) {
15959
16175
  res.alpha = alpha;
@@ -16223,6 +16439,571 @@ zod.z.string().optional().nullable().transform((value) => {
16223
16439
  }
16224
16440
  );
16225
16441
 
16442
+ // ../../internal/utils/src/industry.ts
16443
+ var Industry = {
16444
+ // Finance
16445
+ BANKING: "banking",
16446
+ FINANCIAL_SERVICES: "financial_services",
16447
+ FINTECH: "fintech",
16448
+ PAYMENTS: "payments"};
16449
+
16450
+ // ../../internal/validation/src/dark-web/templates.ts
16451
+ var BRAND = { kind: "brand" };
16452
+ var lit = (value) => ({ kind: "literal", value });
16453
+ var phrase = (...tokens) => ({ type: "phrase", tokens });
16454
+ var bn = (slop, ...words) => ({
16455
+ type: "phrase",
16456
+ tokens: [BRAND, ...words.map(lit)],
16457
+ slop
16458
+ });
16459
+ var nb = (slop, ...words) => ({
16460
+ type: "phrase",
16461
+ tokens: [...words.map(lit), BRAND],
16462
+ slop
16463
+ });
16464
+ var word = (value) => ({ type: "term", value, match: "word" });
16465
+ var prefix = (value) => ({
16466
+ type: "term",
16467
+ value,
16468
+ match: "prefix"
16469
+ });
16470
+ var or = (...children) => ({
16471
+ type: "group",
16472
+ op: "OR",
16473
+ children
16474
+ });
16475
+ var and = (...children) => ({
16476
+ type: "group",
16477
+ op: "AND",
16478
+ children
16479
+ });
16480
+ var notOneOf = (...children) => ({
16481
+ type: "group",
16482
+ op: "OR",
16483
+ children,
16484
+ negated: true
16485
+ });
16486
+ var brandWith = (...values) => and(phrase(BRAND), or(...values.map(word)));
16487
+ var SCAN_TEMPLATES = [
16488
+ {
16489
+ id: "p2p-instant-transfer",
16490
+ category: "payment-fraud",
16491
+ title: "P2P and instant transfer abuse",
16492
+ description: "Actors advertising instant peer-to-peer transfers into or out of the brand, a common way to move stolen funds before a hold clears.",
16493
+ // Source rows: 1, 55. The original ("[brand]"~3) was proximity on one
16494
+ // position, a no-op; dropped rather than ported.
16495
+ query: and(
16496
+ or(
16497
+ phrase(BRAND, lit("to"), BRAND),
16498
+ bn(2, "instant"),
16499
+ bn(2, "anyone"),
16500
+ bn(1, "pay"),
16501
+ bn(3, "pay anyone"),
16502
+ bn(2, "p2p"),
16503
+ bn(3, "instant transfer"),
16504
+ bn(3, "receive from anyone"),
16505
+ bn(4, "phone number"),
16506
+ bn(2, "member to member"),
16507
+ nb(3, "send")
16508
+ ),
16509
+ notOneOf(word("rate"), word("price"))
16510
+ )
16511
+ },
16512
+ {
16513
+ id: "transfers-ach-direct-debit",
16514
+ category: "payment-fraud",
16515
+ title: "Transfers, ACH and direct debit",
16516
+ description: "Discussion of pushing or pulling funds via ACH, direct debit or bill pay, including chatter about pending and settlement timing.",
16517
+ // Source rows: 6, 8, 18, 41. Row 18 was missing an OR before "Direct Debit".
16518
+ query: or(
16519
+ bn(1, "transfer"),
16520
+ nb(1, "transfer"),
16521
+ bn(5, "ach"),
16522
+ nb(4, "pull"),
16523
+ nb(4, "push"),
16524
+ bn(4, "pending"),
16525
+ bn(4, "settle"),
16526
+ nb(4, "link"),
16527
+ brandWith("billpay", "acctverify"),
16528
+ and(phrase(BRAND), or(phrase(lit("direct debit")), phrase(lit("direct deposit"))))
16529
+ )
16530
+ },
16531
+ {
16532
+ id: "wallet-linking",
16533
+ category: "payment-fraud",
16534
+ title: "Wallet linking and instant funding",
16535
+ description: "Linking the brand to mobile wallets for instant funding, and 'linkable' account trading where a name match is being sold.",
16536
+ // Source rows: 19, 44, 51
16537
+ query: and(
16538
+ or(
16539
+ bn(5, "apple pay"),
16540
+ bn(5, "apple"),
16541
+ bn(3, "apple linkable"),
16542
+ bn(2, "linkable"),
16543
+ bn(2, "linkables"),
16544
+ nb(2, "linkable"),
16545
+ nb(2, "link"),
16546
+ and(phrase(BRAND), phrase(lit("name match")))
16547
+ ),
16548
+ notOneOf(word("espn"), word("airtag"))
16549
+ )
16550
+ },
16551
+ {
16552
+ id: "card-data-exposure",
16553
+ category: "payment-fraud",
16554
+ title: "Card data and virtual cards",
16555
+ description: "Brand-issued card details being traded: expiry, CVV, virtual card numbers and card activation.",
16556
+ // Source rows: 43
16557
+ query: and(
16558
+ brandWith("exp", "cvv", "vcc", "debit"),
16559
+ notOneOf(word("checking"), word("limits"))
16560
+ )
16561
+ },
16562
+ {
16563
+ id: "card-loading",
16564
+ category: "payment-fraud",
16565
+ title: "Card and account loading",
16566
+ description: "'Loading' services that push stolen funds onto brand accounts or cards for a cut.",
16567
+ // Source rows: 42
16568
+ query: or(
16569
+ nb(1, "loading"),
16570
+ nb(1, "load"),
16571
+ bn(1, "loads"),
16572
+ bn(1, "loading"),
16573
+ bn(3, "drop"),
16574
+ bn(2, "cash"),
16575
+ and(
16576
+ phrase(BRAND),
16577
+ or(
16578
+ phrase(lit("loading method")),
16579
+ phrase(lit("loading guide")),
16580
+ phrase(lit("loading tut")),
16581
+ phrase(lit("loading tutorial"))
16582
+ )
16583
+ )
16584
+ )
16585
+ },
16586
+ {
16587
+ id: "cross-platform-cashout",
16588
+ category: "payment-fraud",
16589
+ title: "Cross-platform cash-out",
16590
+ description: "Moving funds from the brand into other consumer payment apps to break the trail.",
16591
+ // Source rows: 56
16592
+ query: and(
16593
+ phrase(BRAND),
16594
+ or(
16595
+ phrase(lit("cash app")),
16596
+ word("venmo"),
16597
+ phrase(lit("transfer to paypal")),
16598
+ phrase(lit("visa money transfer"))
16599
+ )
16600
+ )
16601
+ },
16602
+ {
16603
+ id: "crypto-offramp",
16604
+ category: "payment-fraud",
16605
+ title: "Crypto off-ramp",
16606
+ description: "Converting funds held at the brand into cryptocurrency through exchanges and P2P markets.",
16607
+ // Source rows: 63
16608
+ query: and(
16609
+ phrase(BRAND),
16610
+ or(
16611
+ word("crypto"),
16612
+ word("moonpay"),
16613
+ word("switchere"),
16614
+ word("kraken"),
16615
+ word("paxful"),
16616
+ word("noones"),
16617
+ word("coinomi"),
16618
+ word("coinzoom"),
16619
+ word("bitcoin"),
16620
+ word("btc")
16621
+ )
16622
+ )
16623
+ },
16624
+ {
16625
+ id: "mobile-check-deposit",
16626
+ category: "deposit-fraud",
16627
+ title: "Mobile check deposit fraud",
16628
+ description: "Check fraud against mobile deposit, traded under slang including 'glass', 'MCD', 'slips' and 'persy'.",
16629
+ // Source rows: 2, 13, 35, 46
16630
+ industries: [
16631
+ Industry.BANKING,
16632
+ Industry.FINANCIAL_SERVICES,
16633
+ Industry.FINTECH,
16634
+ Industry.PAYMENTS
16635
+ ],
16636
+ query: or(
16637
+ bn(5, "checks"),
16638
+ bn(5, "check"),
16639
+ bn(5, "mobile deposit"),
16640
+ nb(5, "mobile deposit"),
16641
+ bn(5, "slip"),
16642
+ bn(5, "slips"),
16643
+ bn(5, "glass"),
16644
+ bn(5, "md"),
16645
+ bn(5, "mcd"),
16646
+ bn(5, "treasury"),
16647
+ nb(5, "treasury"),
16648
+ bn(5, "persy"),
16649
+ bn(5, "deposit limit"),
16650
+ and(phrase(BRAND), or(prefix("stimm"), word("mcd"), phrase(lit("pop on"))))
16651
+ )
16652
+ },
16653
+ {
16654
+ id: "atm-abuse",
16655
+ category: "deposit-fraud",
16656
+ title: "ATM abuse and 'box work'",
16657
+ description: "ATM deposit and withdrawal manipulation, including dispute-driven 'box work' against cash machines.",
16658
+ // Source rows: 9, 47, 57, 70
16659
+ industries: [
16660
+ Industry.BANKING,
16661
+ Industry.FINANCIAL_SERVICES,
16662
+ Industry.FINTECH,
16663
+ Industry.PAYMENTS
16664
+ ],
16665
+ query: or(
16666
+ bn(2, "atm"),
16667
+ bn(2, "withdrawal"),
16668
+ and(phrase(BRAND), phrase(lit("box work"))),
16669
+ and(
16670
+ phrase(BRAND),
16671
+ or(
16672
+ phrase(lit("atm deposit")),
16673
+ phrase(lit("atm withdrawal")),
16674
+ phrase(lit("atm withdraw")),
16675
+ phrase(lit("atm limit")),
16676
+ phrase(lit("atm card")),
16677
+ phrase(lit("cash withdrawal fee"))
16678
+ )
16679
+ )
16680
+ )
16681
+ },
16682
+ {
16683
+ id: "retail-cashout-network",
16684
+ category: "deposit-fraud",
16685
+ title: "Retail cash-out network",
16686
+ description: "Cash-out through retail and ATM partner networks, where funds are pulled at a storefront rather than a branch.",
16687
+ // Source rows: 30
16688
+ industries: [
16689
+ Industry.BANKING,
16690
+ Industry.FINANCIAL_SERVICES,
16691
+ Industry.FINTECH,
16692
+ Industry.PAYMENTS
16693
+ ],
16694
+ query: or(
16695
+ bn(5, "allpoint"),
16696
+ bn(5, "walgreens"),
16697
+ bn(5, "cvs"),
16698
+ bn(5, "7-eleven"),
16699
+ bn(5, "7eleven"),
16700
+ bn(5, "711"),
16701
+ bn(5, "circle k"),
16702
+ bn(5, "target"),
16703
+ bn(5, "visa plus alliance"),
16704
+ bn(3, "moneypass"),
16705
+ bn(3, "vanilladirect"),
16706
+ bn(3, "vanilla"),
16707
+ bn(3, "dollar general"),
16708
+ bn(3, "dollar tree"),
16709
+ bn(3, "safeway"),
16710
+ bn(3, "walmart"),
16711
+ bn(3, "speedway")
16712
+ )
16713
+ },
16714
+ {
16715
+ id: "otp-bots",
16716
+ category: "account-takeover",
16717
+ title: "OTP bots and 2FA bypass",
16718
+ description: "Automated one-time-passcode interception services targeting the brand's login and step-up flows.",
16719
+ // Source rows: 34
16720
+ query: or(
16721
+ bn(2, "otp"),
16722
+ nb(1, "otp"),
16723
+ phrase(BRAND, lit("otp bot")),
16724
+ bn(2, "2fa"),
16725
+ bn(2, "code"),
16726
+ bn(2, "bypass"),
16727
+ bn(2, "bot"),
16728
+ bn(2, "bots")
16729
+ )
16730
+ },
16731
+ {
16732
+ id: "remote-access-tooling",
16733
+ category: "account-takeover",
16734
+ title: "Remote-access tooling",
16735
+ description: "Legitimate remote-management tools named alongside the brand, the standard toolkit for support-impersonation account takeover.",
16736
+ // Source rows: 29, 64. Truncated by the export; recoverable portion only.
16737
+ query: and(
16738
+ phrase(BRAND),
16739
+ or(
16740
+ word("action1"),
16741
+ phrase(lit("ammy admin")),
16742
+ word("ammyadmin"),
16743
+ word("anydesk"),
16744
+ phrase(lit("any desk")),
16745
+ word("atera"),
16746
+ word("beanywhere"),
16747
+ phrase(lit("chrome remote desktop")),
16748
+ word("connectwise"),
16749
+ word("screenconnect"),
16750
+ word("dameware"),
16751
+ word("domotz"),
16752
+ word("dwservice"),
16753
+ word("fleetdeck"),
16754
+ word("intelliadmin"),
16755
+ phrase(lit("itarian endpoint manager")),
16756
+ phrase(lit("level.io")),
16757
+ word("meshcentral"),
16758
+ word("mremoteng"),
16759
+ phrase(lit("n-able")),
16760
+ word("ngrok"),
16761
+ phrase(lit("pdq deploy")),
16762
+ word("pulseway"),
16763
+ word("realvnc"),
16764
+ phrase(lit("remote utilities")),
16765
+ word("rport")
16766
+ )
16767
+ )
16768
+ },
16769
+ {
16770
+ id: "antidetect-kyc-evasion",
16771
+ category: "account-takeover",
16772
+ title: "Anti-detect and KYC evasion tooling",
16773
+ description: "Fingerprint spoofing, residential proxies and emulators used to defeat the brand's device and identity checks.",
16774
+ // Source row: 40. The original filtered to Telegram OR Dread; DarkOwl's
16775
+ // `source` takes one value, so precision comes from the tooling terms.
16776
+ query: and(
16777
+ phrase(BRAND),
16778
+ or(
16779
+ word("tlo"),
16780
+ word("scamalytics"),
16781
+ word("burner"),
16782
+ prefix("prox"),
16783
+ word("vpn"),
16784
+ word("vps"),
16785
+ word("antidetect"),
16786
+ phrase(lit("anti-detect")),
16787
+ prefix("emulat")
16788
+ )
16789
+ )
16790
+ },
16791
+ {
16792
+ id: "brand-domains-sso",
16793
+ category: "account-takeover",
16794
+ title: "Brand domains and SSO endpoints",
16795
+ description: "The brand's own domains and identity-provider endpoints appearing in credential and access chatter.",
16796
+ // Source row: 31, as proximity not literal hostnames — the index tokenizes
16797
+ // `chime.okta.com`, so a glued literal would never match. Row 23's
16798
+ // "[brand] pass*"~1 is omitted: no wildcards inside a phrase.
16799
+ query: and(
16800
+ or(bn(2, "okta"), bn(2, "login"), bn(2, "portal"), bn(1, "bank")),
16801
+ notOneOf(word("zendesk"))
16802
+ )
16803
+ },
16804
+ {
16805
+ id: "account-opening-abuse",
16806
+ category: "onboarding-abuse",
16807
+ title: "Account opening and self-registration",
16808
+ description: "Bulk or fraudulent account creation against the brand, including 'self-reg' and 'drop' accounts opened on stolen identities.",
16809
+ // Source rows: 5, 17, 54
16810
+ query: or(
16811
+ bn(5, "enroll"),
16812
+ nb(5, "enroll"),
16813
+ bn(5, "open"),
16814
+ nb(5, "open"),
16815
+ bn(5, "drop"),
16816
+ nb(5, "drop"),
16817
+ nb(5, "make"),
16818
+ nb(5, "create"),
16819
+ nb(5, "fresh"),
16820
+ nb(5, "register"),
16821
+ bn(5, "self-reg"),
16822
+ bn(5, "selfreg"),
16823
+ bn(5, "self-registered"),
16824
+ bn(5, "self register"),
16825
+ bn(1, "fullz")
16826
+ )
16827
+ },
16828
+ {
16829
+ id: "identity-documents",
16830
+ category: "onboarding-abuse",
16831
+ title: "Identity documents and fullz",
16832
+ description: "Trade in identity packages and lookup services used to pass onboarding checks. Anchored to the brand at the root, since the source query was brand-independent.",
16833
+ // Source row: 15. Lexicon-only, so the renderer AND-s the anchor on.
16834
+ query: or(
16835
+ word("fullz"),
16836
+ phrase(lit("get pros")),
16837
+ phrase(lit("driver's license")),
16838
+ phrase(lit("tlo bot")),
16839
+ phrase(lit("tlo plug")),
16840
+ phrase(lit("need dl")),
16841
+ phrase(lit("get id"))
16842
+ )
16843
+ },
16844
+ {
16845
+ id: "referral-bonus-abuse",
16846
+ category: "onboarding-abuse",
16847
+ title: "Referral and bonus abuse",
16848
+ description: "Methods for farming the brand's referral bonuses and promotional offers, often paired with fake direct deposits.",
16849
+ // Source rows: 48
16850
+ query: and(
16851
+ or(bn(3, "referral"), bn(3, "bonus"), bn(3, "promotion"), bn(3, "offer")),
16852
+ or(
16853
+ word("bypass"),
16854
+ word("dd"),
16855
+ phrase(lit("direct deposit")),
16856
+ phrase(lit("work around")),
16857
+ word("trick"),
16858
+ word("method"),
16859
+ word("glitch"),
16860
+ word("tutorial"),
16861
+ word("tut"),
16862
+ word("wave"),
16863
+ word("sauce")
16864
+ )
16865
+ )
16866
+ },
16867
+ {
16868
+ id: "insider-recruitment",
16869
+ category: "insider-threat",
16870
+ title: "Insider recruitment",
16871
+ description: "Actors seeking or advertising an employee on the inside ('inny'), the highest-severity signal in this catalog.",
16872
+ // Source rows: 7, 38
16873
+ query: and(
16874
+ or(
16875
+ bn(4, "inny"),
16876
+ bn(4, "insider"),
16877
+ and(
16878
+ phrase(BRAND),
16879
+ or(
16880
+ word("insider"),
16881
+ word("inny"),
16882
+ phrase(lit("in office")),
16883
+ word("employee"),
16884
+ phrase(lit("i work at")),
16885
+ phrase(lit("i am an employee")),
16886
+ phrase(lit("i can get access")),
16887
+ phrase(lit("i have access to")),
16888
+ phrase(lit("employed at")),
16889
+ word("occupation")
16890
+ )
16891
+ )
16892
+ ),
16893
+ notOneOf(phrase(lit("welcome to insider chat")))
16894
+ )
16895
+ },
16896
+ {
16897
+ id: "mule-crew-recruitment",
16898
+ category: "insider-threat",
16899
+ title: "Mule and cash-out crew recruitment",
16900
+ description: "Recruitment of account holders to receive and forward funds, priced as a revenue split ('50/50', '60/40').",
16901
+ // Source rows: 39
16902
+ query: or(
16903
+ bn(5, "cl"),
16904
+ bn(5, "mule"),
16905
+ bn(5, "tap in"),
16906
+ bn(5, "50/50"),
16907
+ bn(5, "60/40"),
16908
+ bn(5, "striker")
16909
+ )
16910
+ },
16911
+ {
16912
+ id: "methods-and-tutorials",
16913
+ category: "tradecraft",
16914
+ title: "Methods, 'sauce' and tutorials",
16915
+ description: "Fraud methods being taught or sold against the brand, under the usual slang: sauce, glitch, tut, wave, motion, tap in.",
16916
+ // Source rows: 4, 21, 59
16917
+ query: or(
16918
+ bn(3, "sauce"),
16919
+ bn(3, "method"),
16920
+ bn(3, "guide"),
16921
+ bn(3, "tut"),
16922
+ bn(3, "tutorial"),
16923
+ bn(4, "hack"),
16924
+ bn(4, "glitch"),
16925
+ bn(4, "wave"),
16926
+ bn(4, "motion"),
16927
+ bn(4, "walkthrough"),
16928
+ bn(4, "walk through"),
16929
+ bn(2, "tap in"),
16930
+ bn(2, "tapin"),
16931
+ nb(2, "tapn")
16932
+ )
16933
+ },
16934
+ {
16935
+ id: "hack-leak-chatter",
16936
+ category: "tradecraft",
16937
+ title: "Hack and leak chatter",
16938
+ description: "Claims of having hacked, breached or leaked the brand, whether credible or not.",
16939
+ // Source rows: 28
16940
+ query: or(
16941
+ bn(4, "hack"),
16942
+ bn(4, "glitch"),
16943
+ nb(4, "hacking"),
16944
+ bn(4, "leak"),
16945
+ bn(4, "leaked")
16946
+ )
16947
+ },
16948
+ {
16949
+ id: "support-impersonation",
16950
+ category: "brand-abuse",
16951
+ title: "Support impersonation",
16952
+ description: "Actors impersonating the brand's support or service staff to social-engineer customers.",
16953
+ // Source rows: 36
16954
+ query: or(
16955
+ bn(1, "agent"),
16956
+ bn(1, "support"),
16957
+ bn(1, "service"),
16958
+ bn(1, "customer"),
16959
+ nb(1, "impersonate"),
16960
+ bn(1, "impersonation"),
16961
+ bn(3, "scam"),
16962
+ nb(1, "scam")
16963
+ )
16964
+ },
16965
+ {
16966
+ id: "social-impersonation",
16967
+ category: "brand-abuse",
16968
+ title: "Social account impersonation",
16969
+ description: "The brand discussed alongside social platforms, where fake support and giveaway accounts are staged.",
16970
+ // Source rows: 25
16971
+ query: or(
16972
+ bn(5, "instagram"),
16973
+ bn(5, "insta"),
16974
+ bn(5, "facebook"),
16975
+ bn(5, "twitter"),
16976
+ bn(5, "tiktok")
16977
+ )
16978
+ },
16979
+ {
16980
+ id: "vendor-supply-chain",
16981
+ category: "data-exposure",
16982
+ title: "Vendor and BPO supply chain",
16983
+ description: "Outsourced support and processing vendors named alongside the brand, where a third-party breach exposes brand customer data.",
16984
+ // Source row: 60, truncated by the export. Generic BPO/CX providers; a
16985
+ // per-org vendor list would be materially better.
16986
+ query: and(
16987
+ phrase(BRAND),
16988
+ or(
16989
+ word("zendesk"),
16990
+ word("teleperformance"),
16991
+ word("concentrix"),
16992
+ word("taskus"),
16993
+ word("genpact"),
16994
+ word("ubiquity"),
16995
+ word("hioperator"),
16996
+ word("galileo"),
16997
+ word("fiserv"),
16998
+ phrase(lit("etech global"))
16999
+ )
17000
+ )
17001
+ }
17002
+ ];
17003
+ new Map(
17004
+ SCAN_TEMPLATES.map((template) => [template.id, template])
17005
+ );
17006
+
16226
17007
  // src/client.ts
16227
17008
  function trimTrailingSlashes(url) {
16228
17009
  return url.replace(/\/+$/, "");