538b180824
## What
A single, region-aware DPA that serves all customers, generated
automatically from the customer's deployment. Two layers:
1. **Click-through DPA** — recorded at signup (acceptance = execution),
resolving merge fields from the deployment region. Cloud only.
2. **In-app signed-PDF generator** — Settings → Legal → Generate DPA:
preview the agreement, enter legal entity + authorized signatory,
download a PDF pre-signed by Twenty, and store the executed copy against
the workspace with its template version + timestamp. Deep-linkable at
`/dpa` (login-gated) for `twenty.com/dpa`.
## How it resolves
A typed variable matrix (`dpa-region-config.constant.ts`) maps the
deployment region to the contracting Processor entity and terms:
- **EU (default)** → Twenty.com SAS, hosting EU/Frankfurt, governing law
France, SCC section dormant.
- **US (custom)** → Twenty, Inc., hosting US, SCC section active.
Region is a deployment-wide setting (`DPA_DEPLOYMENT_REGION`, default
EU) behind a `DpaRegionService` seam so it can later become
per-workspace without touching callers. The legal text is verbatim from
the template (generated into `dpa-template.constant.ts` directly from
the source `.docx`); only the 6 merge fields are filled and the SCC
sections (7.2–7.5) stay in the document for every region per the spec —
only field values branch. Sub-processors are deferred to
trust.twenty.com (not enumerated). Billing stays decoupled (Twenty, Inc.
remains merchant of record regardless of Processor).
## UI
Standard list + create-page pattern (mirrors API keys / webhooks): a
list of executed copies (with re-download) — or the agreement preview
when none exists — and a top-right blue **Generate DPA** CTA opening a
standard create page. The "Legal" item is intentionally **not** in the
settings menu; the page is reached via the `/dpa` deep link.
## Notable implementation details
- **PDF** is rendered server-side with `@react-pdf/renderer`. The
built-in standard-14 fonts only encode ASCII and crash on the template's
curly quotes / em–en dashes / accented Latin, so Liberation Sans (OFL)
is **subset to a Latin glyph set and embedded as base64 data: URLs** —
no font files to ship or resolve at runtime (works in dev, prod-Docker
and CI).
- New `core.dpaAgreement` table via a fast instance command (FK hash
reproduced to match TypeORM).
- Self-hosted deployments (billing disabled) skip click-through
recording and stamp a prominent "not a valid agreement" banner on the
preview and PDF.
## Tests
- Unit: resolver (per-region entity/law/SCC state, EU default, no
unresolved `{{ }}`, SCC sections present in both regions, self-hosted
notice) and HTML renderer.
- Integration (`test/integration/graphql/suites/dpa`): preview has no
unresolved fields; `generateSignedDpa` renders + persists + returns a
downloadable PDF (asserted with accented input to guard the font
regression); list re-download.
## ⚠ Needs legal input before go-live (marked `TODO_CONFIRM` in
`dpa-region-config.constant.ts`)
- Registered-office addresses for Twenty.com SAS and Twenty, Inc.
- US deployment governing law (the template only specifies France).
- DPO name and the Twenty pre-signed authorized signatory name/title.
## Out of scope (flagged per spec)
Intra-group legal agreement and any Stripe/billing-entity changes. A
future e-sign provider would plug in at `DpaService.generateSignedDpa` +
the signatory input.
> Draft until the integration test passes in CI and the legal
`TODO_CONFIRM` values are supplied.
https://claude.ai/code/session_01Ahjydxx6J1souz1s1NeA9a
---
_Generated by [Claude
Code](https://claude.ai/code/session_01Ahjydxx6J1souz1s1NeA9a)_
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22243?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
62 lines
2.4 KiB
JavaScript
62 lines
2.4 KiB
JavaScript
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
|
|
|
module.exports = {
|
|
schema:
|
|
(process.env.REACT_APP_SERVER_BASE_URL ?? 'http://localhost:3000') +
|
|
'/metadata',
|
|
documents: [
|
|
'./src/modules/auth/graphql/**/*.{ts,tsx}',
|
|
'./src/modules/users/graphql/**/*.{ts,tsx}',
|
|
'./src/modules/views/graphql/**/*.{ts,tsx}',
|
|
'./src/modules/ai/graphql/**/*.{ts,tsx}',
|
|
'./src/modules/applications/graphql/**/*.{ts,tsx}',
|
|
|
|
'./src/modules/workspace/graphql/**/*.{ts,tsx}',
|
|
'./src/modules/workspace-member/graphql/**/*.{ts,tsx}',
|
|
'./src/modules/workspace-invitation/graphql/**/*.{ts,tsx}',
|
|
|
|
'./src/modules/settings/**/graphql/**/*.{ts,tsx}',
|
|
'!./src/modules/settings/admin-panel/**/graphql/**/*.{ts,tsx}',
|
|
// DPA operations are served by the core (/graphql) schema via @CoreResolver,
|
|
// not the metadata schema, so they must not be validated against /metadata.
|
|
'!./src/modules/settings/legal/**/graphql/**/*.{ts,tsx}',
|
|
'./src/modules/logic-functions/graphql/**/*.{ts,tsx}',
|
|
|
|
'./src/modules/databases/graphql/**/*.{ts,tsx}',
|
|
'./src/modules/analytics/graphql/**/*.{ts,tsx}',
|
|
'./src/modules/object-metadata/graphql/**/*.{ts,tsx}',
|
|
'./src/modules/navigation-menu-item/**/graphql/**/*.{ts,tsx}',
|
|
'./src/modules/command-menu-item/graphql/**/*.{ts,tsx}',
|
|
'./src/modules/attachments/graphql/**/*.{ts,tsx}',
|
|
'./src/modules/file/graphql/**/*.{ts,tsx}',
|
|
'./src/modules/onboarding/graphql/**/*.{ts,tsx}',
|
|
'./src/modules/front-components/graphql/**/*.{ts,tsx}',
|
|
|
|
'./src/modules/page-layout/widgets/**/graphql/**/*.{ts,tsx}',
|
|
'./src/modules/activities/emails/graphql/mutations/**/*.{ts,tsx}',
|
|
'./src/modules/activities/emails/graphql/metadata-queries/**/*.{ts,tsx}',
|
|
|
|
'./src/modules/dashboards/graphql/**/*.{ts,tsx}',
|
|
'./src/modules/page-layout/graphql/**/*.{ts,tsx}',
|
|
'./src/modules/marketplace/graphql/**/*.{ts,tsx}',
|
|
'!./src/**/*.test.{ts,tsx}',
|
|
'!./src/**/*.stories.{ts,tsx}',
|
|
'!./src/**/__mocks__/*.ts',
|
|
],
|
|
overwrite: true,
|
|
generates: {
|
|
'./src/generated-metadata/graphql.ts': {
|
|
plugins: ['typescript', 'typescript-operations', 'typed-document-node'],
|
|
config: {
|
|
skipTypename: false,
|
|
defaultScalarType: 'any',
|
|
scalars: {
|
|
DateTime: 'string',
|
|
UUID: 'string',
|
|
},
|
|
namingConvention: { enumValues: 'keep' },
|
|
},
|
|
},
|
|
},
|
|
};
|