Files
twenty/packages/twenty-apps/internal/people-data-labs
Charles Bochet a48c158a66 security(apps): bump twenty-sdk to 2.10.1 across twenty-apps (tmp, undici) (#21344)
## Summary

Propagates the just-published **`twenty-sdk@2.10.1`** security patch
into the `twenty-apps/*` mini-apps, clearing the bulk of the
nested-lockfile Dependabot alerts (the `tmp` + `undici` clusters).

Each app carries its own `yarn.lock`, so the fix only reaches them once
they bump the SDK. `2.10.1` drops the two vulnerable transitive deps
every app inherited:

| Vuln dep | Source | Fixed by |
|---|---|---|
| `tmp@0.0.33` (GHSA-ph9p / GHSA-52f5) | `inquirer ^10 →
external-editor` | `inquirer ^14` → `@inquirer/editor@5` (no
external-editor) |
| `undici@<6.24` (5 GHSAs) | `@genql/cli` | vendored genql codegen
(`@genql/cli` removed) |

## Changes
Bumps `twenty-sdk` **and** `twenty-client-sdk` (whichever each app pins
— several pin both) to `2.10.1` and regenerates each lockfile.

**10 apps updated** (all on the v2 line — minor bump, low risk):
`twenty-slack`, `twenty-discord`, `twenty-linear`, `twenty-partners`,
`twenty-fireflies`, `people-data-labs`, `twenty-for-twenty`, `exa`,
`github-connector`, `postcard`.

Verified per-app after regen: **`tmp@0.0.33` = 0** and **`undici@5` =
0** in every updated lockfile.

## Deliberately excluded
Three apps pin a **pre-2.0** SDK, where `→ 2.10.1` is a major jump that
risks breaking the app and needs per-app validation:
- `examples/hello-world` (`0.9.0`)
- `internal/call-recording` (`0.6.3-alpha`)
- `internal/self-hosting` (`1.22.0-canary.6`)

These still carry one `tmp`/`undici` alert each and should be handled in
a follow-up.

## Related
- `twenty-sdk@2.10.1` release (tag `sdk/v2.10.1`) — backport of #21339
(undici) + #21340 (tmp) from `main`.
2026-06-08 21:31:14 +02:00
..

People Data Labs enrichment app

Enriches Person and Company records with People Data Labs (PDL) data.

Status: data-model scaffold. This package defines the fields, relation, indexes, views, role, and manifest. The enrichment logic function (the "mapper") is not yet implemented — see What the mapper must do.


Data-model decisions

Bundle scope

Only the core PDL company fields are defined. Premium / Comprehensive / specialized fields (inferred_revenue, linkedin_follower_count, employee growth/churn/tenure, parent / subsidiary, exec movement, top employers, funding_details, …) are out of scope for this app.

Enums → SELECT / MULTI_SELECT

Every PDL enum that has a canonical file is a SELECT, validated 0-missing/0-extra against PDL schema v34.1:

Field Type Options
pdlSeniority (job_title_levels, array) MULTI_SELECT 10
pdlFundingStages (funding_stages, array) MULTI_SELECT 29
pdlIndustry / pdlJobCompanyIndustry (industry) SELECT 147
pdlJobTitleSubRole (job_title_sub_role) SELECT 106
pdlJobTitleClass, pdlInferredSalary, pdlSex, pdlCompanyType, pdlSizeRange, pdlJobCompanySize, pdlLatestFundingStage, pdlLocationContinent, pdlLocationMetro, pdlMicExchange SELECT 5 / 11 / 2 / 6 / 8 / 8 / 29 / 7 / 384 / 70
  • Option values are normalized to GraphQL enum names (united statesUNITED_STATES): uppercase, accents stripped, non-alphanumeric → _, digit-leading prefixed.
  • Option universalIdentifiers are unique per field (shared enums like industry get a separate id-set per field).
  • Stays TEXT (no canonical PDL enum file exists): pdlIndustryDetail (industry_v2), pdlJobOnetCode, pdlLocationRegion.

Standard-field mapping

pdl* shadows are removed where an equivalent standard field exists; the mapper writes the standard field instead:

Object Removed shadow → standard target
Person pdlLinkedinUrllinkedinLink, pdlJobTitlejobTitle, pdlFullNamename, pdlWorkEmail/pdlPersonalEmailsemails, pdlMobilePhone/pdlPhoneNumbersphones
Company pdlLinkedinUrllinkedinLink, pdlWebsitedomainName, pdlDisplayNamename

Shadows are kept where no reliable standard field is available: pdlEmployeeCount, pdlTwitterUrl. Trade-off: PDL's work/personal-email and mobile/other-phone distinction is dropped (folded into the standard bags).

Location → ADDRESS composite

  • Company location → the standard address composite (street/city/state/postcode/country/geo).
  • Person has no standard address field → dedicated pdlLocation (ADDRESS).
  • pdlLocationMetro (both) and pdlLocationContinent (company) stay SELECT — ADDRESS has no slot. Trade-off: ADDRESS country is free text, so the country SELECT was dropped.

Relation

Dedicated pdlCurrentCompany (Person MANY_TO_ONE → Company) ↔ inverse pdlCurrentEmployees (Company ONE_TO_MANY → Person). Deliberately not the standard company relation, so PDL's detected employer can't overwrite the user's CRM account link.

Enrichment metadata

  • pdlId — PDL record id (re-enrich by id: more precise than by email).
  • pdlLikelihood (Person, NUMBER) — PDL match confidence 110.
  • pdlEnrichmentStatus (SELECT: MATCHED / NOT_FOUND / ERROR) — distinguishes "no match" from "never tried" (drives re-enrichment scheduling).
  • pdlLastEnrichedAt (DATE_TIME), pdlRawPayload (RAW_JSON, full response).

Other

  • pdlTotalFunding is CURRENCY (mapper must convert the bare USD float → micros).
  • Indexes: pdlId and pdlLastEnrichedAt on both objects.
  • Views: a curated "People Data Labs" TABLE view per object.
  • Role: read/update on Person & Company (object-level; tighten to field-scoped later).

What the mapper must do

The logic function (to be built) must:

Orchestration

  1. Trigger via manual command-menu action / record create / batch (TBD).
  2. Call PDL Person and/or Company Enrichment with PDL_API_KEY; pass min_likelihood / required_fields to control match quality.
  3. On 200 → write fields + set pdlEnrichmentStatus = MATCHED; on 404NOT_FOUND; on error → ERROR.
  4. Respect PDL rate limits (queue / throttle on 429).
  5. TTL guard: skip re-enrichment if pdlLastEnrichedAt is recent; prefer re-enriching by pdlId.

Field writing

  1. Write standard fields (fill-only-if-empty to avoid overwriting user data): Person name, emails, phones, linkedinLink, jobTitle; Company name, domainName, linkedinLink, address.
  2. Write pdl* fields for everything else.
  3. SELECT guard: only write a SELECT/MULTI_SELECT value if the normalized value exists in the field's option set; otherwise skip and keep it in pdlRawPayload (handles PDL schema versions newer than v34.1). Use the same normalization as the option values.
  4. MULTI_SELECT arrays: job_title_levelspdlSeniority; funding_stagespdlFundingStages.
  5. CURRENCY: total_funding_raised (USD float) → { amountMicros: value × 1_000_000, currencyCode: 'USD' }.
  6. ADDRESS: split PDL location.* into the composite — Company → standard address, Person → pdlLocation.
  7. Relation: resolve job_company_id → find/upsert a Company record → link pdlCurrentCompany.
  8. Dates: handle partial PDL dates (YYYY, YYYY-MM) for job_start_date, last_funding_date, birth_date.
  9. Always set pdlId, pdlLastEnrichedAt, pdlRawPayload, pdlLikelihood (person).