Files
twenty/packages/twenty-apps/internal/people-data-labs
Charles Bochet 0403762516 security: refresh twenty-apps lockfiles for vulnerable transitive deps (#21316)
## What

The standalone apps under `packages/twenty-apps/*` each ship **their own
`yarn.lock`** (they're not part of the root workspace), and those
lockfiles still pulled vulnerable transitive versions of `axios`,
`undici`, `tmp`, `qs`, `ws`, `brace-expansion`, `uuid` (via `twenty-sdk`
/ `twenty-client-sdk`). This was ~130 of the open Dependabot alerts —
none of them reachable from the root-lockfile PRs.

Ran `yarn up -R` per app to re-resolve the vulnerable transitives within
their existing ranges, across all 13 flagged apps:

- **`axios` → 1.17.0** — clears the entire proxy-auth-leak / ReDoS /
config-merge MITM advisory set (the 56 axios alerts)
- **`qs`, `brace-expansion`, `uuid`** → patched
- **`undici`, `ws`** → patched on the in-range majors (older majors that
parents pin exactly remain, same situation as the root lockfile)

## Scope
- **Lockfile-only**, 13 apps. No `package.json` changes.
- Test **fixtures** (`packages/twenty-apps/fixtures/*`) intentionally
left untouched — Dependabot didn't flag them and they back snapshot
tests.
2026-06-08 16:45:19 +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).