3c48e27b2ea91c097f01a51b759a987c29caeeac
13926 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
3c48e27b2e |
Fix stuck onboarding route on failed chunk preload (#23359)
Fixes [Sentry 7604159654](https://sentry.io/issues/7604159654/) (v2.20.0, Mobile Safari). The onboarding router preloads 7 lazy chunks on entry; Vite's CSS preload for SyncEmails rejected and three defects compounded: - `void SomePage.preload()` discarded the promise, so it became an unhandled rejection and the user got a raw `Unable to preload CSS for /assets/...css` snackbar. - `lazyWithPreload` cached the *rejected* promise and rendered via `throw preload()`. React pings on the rejection, re-renders, the component throws the same settled rejected thenable, the ping listener de-dupes, and the route hangs on its loader forever. - `checkIfItsAViteStaleChunkLazyLoadingError` only matched Chrome's message, so `AppErrorBoundary`'s reload recovery never fired for the CSS-preload or Safari variants. `lazyWithPreload` now records the failure in state instead of rethrowing, so the thenable thrown into Suspense always fulfills, `preload()` returns void and can never reject, and the render path throws the real `Error` to the boundary, which reloads. Two things worth knowing for review: `React.lazy` is not a substitute here (its initializer has no synchronous fast path, so it suspends even when the module is already loaded, reintroducing the loader flash #22392 removed), and the failure is deliberately sticky because Vite marks the dep `seen` before attempting it, so an in-document retry loads the JS without its CSS and silently renders an unstyled page. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23359?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. --> |
||
|
|
79bf20515d |
feat(workflow): mirror version delete/restore/destroy to core transactionally (#23356)
Next step in the workflowVersion -> core soft-ref migration. The transactional mirror (#23243) made **content** writes (create/update) drift-free. This does the same for the **lifecycle** events (delete / restore / destroy), which were still handled only by the async best-effort listener. It's the prerequisite for dropping that listener. ## What changed `delete` and `restore` already soft-delete / restore the workflow's versions inside `handleWorkflowSubEntities` (twenty doesn't cascade soft-deletes, so it does each sub-entity explicitly). So the core delete/recreate just sits next to the existing version write: - **delete** — after `workflowVersionRepository.softDelete({ workflowId })`, `deleteCoreVersionsByWorkflowIds` removes the `core.workflowVersion` rows (`workflowId IN (...)`). (`deactivateVersionOnDelete` no longer re-mirrors the deactivated version — that was recreating the core row it just deleted; it only flips the workspace status to `DEACTIVATED` so a restore comes back deactivated.) - **restore** — after `workflowVersionRepository.restore({ workflowId })`, `recreateCoreVersionsByWorkflowId` re-reads the restored versions and reuses the existing `upsertToCore` (which reuses the stored `coreWorkflowVersionId` soft-ref, so rows come back with their original ids and current status). - **destroy** — version destroy isn't done in `handleWorkflowSubEntities` (it happens via the generic cascade), so there's no existing place to hang the core delete. New `workflow.destroyOne`/`destroyMany` **post**-hooks call `deleteCoreVersionsByWorkflowIds` (batched `IN`) only after the destroy commits, so a rejected destroy can't remove core rows while the workspace versions survive. No new transactional wrappers or raw SQL — the delete/recreate reuse the existing `WorkflowVersionCoreSyncService` methods (`deleteFromCore`-style delete, `upsertToCore`). The async listener stays as an idempotent backstop until the cron soaks zero drift. ## Async listener kept as backstop `handleRestored` / `handleDeleted` / `handleDestroyed` stay for now. Both paths are idempotent (delete-of-deleted is a no-op; upsert converges), so they don't conflict. Those handlers come out in a follow-up once the consistency cron soaks zero drift - which this PR unblocks. ## Verification Lifecycle integration test — creates a workflow, **activates** the version (the active path is where the delete re-mirror bug bit), then asserts the core row: present -> gone after delete -> back after restore (as `DEACTIVATED`, same id) -> gone after destroy. Plus the existing `workflow-resolver` delete/restore suite (regression, since `handleWorkflowSubEntities` is shared). Also verified **live** on a running instance against the real DB: the full active-version lifecycle above, plus a batched `destroyWorkflows` on two workflows removing both core rows in one `IN` delete. `nx typecheck` + oxlint + oxfmt clean. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23356?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. --> |
||
|
|
5c23ddb1ac |
Dedupe common result handlers (#23364)
## Context `CommonResultGettersService` selects field handlers by mapping every returned record field to the handler registered for its metadata type. Handlers are shared instances, and each handler already receives the complete field metadata list. This means that when multiple fields have the same handled type, the same handler is added and executed multiple times. For example, a record with two `FILES` fields previously produced this execution list: ```text [objectHandler, filesHandler, filesHandler] ``` Each `filesHandler` execution processes both `FILES` fields. As a result, both file URLs were signed twice. With several fields of the same type, this can make the work grow quadratically. The same duplication can affect rich-text processing, including JSON parsing, serialization, and embedded file URL signing. ## What changed Field handler instances are collected in an insertion-ordered `Set` before execution: ```text [objectHandler, filesHandler] ``` Each distinct field handler now runs once per record and continues to process every matching field. ## Why this is safe - The object-specific handler still runs first. - Different field-handler types keep their first-seen order. - No field is skipped, handlers still receive the complete field metadata list. - Requests with zero or one field for a handled type are unchanged. - No cache or cross-request state is introduced. ## Expected impact This removes repeated synchronous file-token signing and repeated rich-text transformations for records with multiple fields of the same handled type. It also reduces event-loop blocking when large result sets contain several file or rich-text fields. ## Tests Added a regression test with two `FILES` fields. It verifies that both fields are processed while `signFileByIdUrl` is called exactly once per file, two calls instead of the previous four. Validated with: ```bash yarn nx jest twenty-server --runInBand --runTestsByPath src/engine/api/common/common-result-getters/__tests__/common-result-getters.service.spec.ts ``` Touched files also pass type-aware Oxlint and Oxfmt. |
||
|
|
590ae069e8 |
Support workspace member Me filter for relation fields in dashboards (#23282)
<img width="3024" height="1484" alt="CleanShot 2026-07-27 at 15 04 22@2x" src="https://github.com/user-attachments/assets/a57797b7-dbe9-4748-aeff-18667f1f69bb" /> Fixes #20225 Workspace member "Me" filters worked for standard actor fields (Created by / Updated by) in dashboard widgets but not for relation fields pointing to a workspace member (e.g. "Account owner"). In the advanced-filter UI, picking such a relation forced a relation traversal and never produced a filter you could set to "Me". For a many-to-one relation targeting workspaceMember, the relation-target sub-menu now offers a "filter by record" entry that creates a direct relation filter with the same "Me" multi-select picker used by view filters. Traversal (e.g. "Account owner -> Name") is preserved. No backend change is needed: the stored value matches view filters and is already resolved server-side. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23282?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. --> |
||
|
|
0c57d7c108 |
perf: reuse Google webhook OAuth client (#23361)
## Context A new client currently downloads signing certificates for every webhook ## Fix reuse same OAuth client instance ## Impact Probably small but not really risky to merge imho |
||
|
|
dbbad1bffa |
i18n - translations (#23367)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23367?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. --> --------- Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
2899058b5f |
Warn users before front components navigate to an external site (#23270)
https://github.com/user-attachments/assets/af3fb042-d066-4e0c-9348-f86ea92a6fcd Front component anchors render a real host `<a>`, so clicking a link to another domain performed an uncontrolled full-page navigation. This adds a phishing-resistant "you're leaving Twenty" confirmation modal before navigating to an external origin (Fixes [#23260](https://github.com/twentyhq/twenty/issues/23260)). The renderer intercepts external anchor clicks in `createHtmlHostWrapper` and hands the destination to a host callback via context; twenty-front owns the modal (reuses `ConfirmationModal`) and a per-application list of trusted origins persisted in localStorage. A "Don't ask again for this site" checkbox (checked by default) skips the modal next time for that app. Scope is external cross-origin http(s) links only; same-origin links keep native behavior. External links always open in a new tab, so a component can never navigate the Twenty tab away, even once its origin is trusted. The modal is rendered by the trusted host, so components cannot style or suppress it. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23270?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. --> |
||
|
|
7804111e6c |
i18n - translations (#23360)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23360?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. --> --------- Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
56245a35af |
Stop leaking the refresh token in the social SSO redirect URL (#23061)
The Google/Microsoft callback for a sign-in with no target workspace
redirected to `/sign-in-up?tokenPair={...}`, putting a 60-day refresh
token in a query string. Those persist in browser history, `Referer`
headers and access logs.
It now carries a single-use, 5-minute opaque token in the URL fragment,
which the frontend exchanges over POST. Browsers never send the fragment
on the wire, so the token stays out of access logs, proxies and
`Referer` headers entirely. Redemption claims the row with a `DELETE`
guarded on `revokedAt`/`deletedAt` being null, so concurrent requests
cannot each mint a refresh token and a revoked token cannot redeem.
Enterprise SSO (OIDC/SAML) already used a POST exchange and is
unchanged.
```mermaid
sequenceDiagram
participant Browser
participant Server
participant DB
Note over Browser,Server: before, the redirect carried access + 60-day refresh in ?tokenPair
Browser->>Server: GET /auth/google/redirect
Server->>DB: store sha256(token), expires in 5 min
Server-->>Browser: 302 /sign-in-up#ssoExchangeToken=opaque
Note over Browser: fragment never sent back to any server
Browser->>Server: POST getAuthTokensFromSSOExchangeToken
Server->>DB: guarded DELETE, single-use claim
Server-->>Browser: access + refresh token, in the response body
```
Since the token is single-use, the refresh token is minted at redemption
instead of at callback, so an abandoned redirect leaves an inert expired
hash rather than a live credential.
Redemption lives in its own `SignInUpSSOExchangeTokenEffect` +
`useRedeemSSOExchangeToken`, mirroring the existing
`VerifyLoginTokenEffect` + `useVerifyLogin` pair, so
`SignInUpGlobalScopeFormEffect` only loses the vulnerable branch. Like
`useVerifyLogin`, the hook clears any stale token pair before
exchanging. The effect reads `window.location.hash` live and strips it
synchronously, which doubles as the StrictMode double-invocation latch.
Remaining exposure is the browser itself (history until the synchronous
strip, client-side scripts), same as any fragment-based OAuth response.
`loginToken` on the workspace-targeted branch still travels as
`/verify?loginToken=` and is replayable for 15 minutes; moving it to the
fragment too is a separate change.
A fast instance command adds a unique partial index on `("type",
"value")` for live SSO exchange tokens, so redemption is an index lookup
instead of a full scan of the shared token table and at most one row can
ever match.
|
||
|
|
b81ca99162 |
fix(twenty-front): hide layout editor UI when SystemPermissionFlag.LAYOUTS is missing (#23303) (#23343)
## Description Fixes #23303. This PR ensures that the layout editor UI and customization entry points are hidden and protected when a user lacks the `SystemPermissionFlag.LAYOUTS` permission flag. ### Changes Made: 1. **`useEnterLayoutCustomizationMode.ts`**: Added `useHasPermissionFlag(PermissionFlagType.LAYOUTS)` check inside `enterLayoutCustomizationMode` to return `false` and prevent entering customization mode if the user lacks permission. 2. **`WorkspaceSection.tsx`**: Updated the sidebar `WorkspaceSection` component to render the layout edit button (`IconTool`) only when `hasLayoutsPermission` is `true`. 3. **`ObjectLayout.tsx`**: Disabled customize and reset layout controls in the Data Model Object Details settings page if the user lacks `LAYOUTS` permission. 4. **`useEnterLayoutCustomizationMode.test.tsx`**: Added unit tests to verify that `useEnterLayoutCustomizationMode` correctly guards layout customization initialization based on permission. ## Testing - Added unit tests for `useEnterLayoutCustomizationMode` permission checks. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23343?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. --> |
||
|
|
22a01dc1c6 |
Fix: password reset link returns FORBIDDEN for logged-in users (#21248) (#23335)
## Problem Fixes #21248. After upgrading, workspace members who open a password reset link while a token pair still exists in local storage get a generic `You do not have permission to perform this action.` (FORBIDDEN) error, blocking account recovery. ## Root cause Every GraphQL request passes through `GraphQLHydrateRequestFromTokenMiddleware` before any resolver. If a token is present it validates it; if no token is present it short-circuits and lets the request through unauthenticated. The reset flow was only ever designed for the unauthenticated case (the user is logged out, so no token exists). Two intended changes broke that assumption: - A token pair now persists in local storage at reset time (unified `accessOrWorkspaceAgnosticToken` + tokenPair moved off session cookies into local storage). - The Apollo auth link attaches `authorization: Bearer <token>` whenever any token pair exists, regardless of the operation. So the public `validatePasswordResetToken` / `updatePasswordViaResetToken` operations now arrive with a token that the middleware rejects, producing FORBIDDEN before the resolver runs. Note: the `PublicEndpointGuard` / `NoPermissionGuard` on these resolvers both just `return true` — they do not inspect headers and are not the gate. The middleware is. ## Fix Add a generic `skipAuthToken` operation-context flag. The auth link omits the `Authorization` header when a request sets it, staying agnostic of any specific operation or endpoint. The two public reset operations opt in at their call site in `PasswordReset.tsx`. This restores the exact unauthenticated path the flow was designed for, regardless of whether a token pair happens to sit in local storage. Nothing is reverted; all authenticated traffic is unaffected. ## Testing Ran the built frontend against a local backend, logged in so a `tokenPairState` was present in local storage, then opened a reset link and inspected the outgoing `ValidatePasswordResetToken` request: - Request headers: `accept`, `content-type`, `x-locale` only. No `authorization` header, despite a token pair being present. - With an invalid token the response is the resolver-level `Token is invalid` error (it reaches the resolver) instead of the middleware's FORBIDDEN. - With a valid token the query succeeds (`validatePasswordResetToken` returns the email + `hasPassword`) and the Set/Change Password form renders, so the recovery flow completes. Lint and typecheck pass on the changed files. |
||
|
|
e631c986a1 |
v1.4.0 — partners: auto-link partner user on workspaceMember.created (#23295)
**App version:** `1.4.0` (partners app — `packages/twenty-apps/internal/twenty-partners`) ## What Adds partner onboarding auto-linking: when a `workspaceMember` is created (invite signup), a DB-event-triggered logic function resolves the partner by the member's email and stamps `partnerUser` across the partner and its cascade (person, company, links, services, content, applications). ## Key design decision — data-linking only, no role assignment The trigger **does not** assign the Partner role. A logic function runs as an app **agent**, with no user session; `updateWorkspaceMemberRole` is guarded by `UserAuthGuard` + `AuthWorkspaceMemberId` and is unreachable from an agent, so the mutation silently no-ops regardless of permission flags. The dead role code (`ensure-partner-role` service, its role query/mutation, and the role mocks) is removed so the trigger's responsibility is unambiguous: resolve partner by email → link `partnerUser` cascade with retry-on-partial-failure. Role assignment, if wanted, belongs on the invite path (`sendInvitations` accepts a `roleId`), not the trigger. ## Changes - `on-workspace-member-created.logic-function.ts` — DB-event trigger on `workspaceMember.created`; skips internal (`@twenty.com`) and unmatched emails - `resolve-partner-by-email` / `link-partner-user` services + typed `graphql/` operations for the cascade - `normalize-invite-email` util - `partnerUserLinkedAt` field on Partner - Seed: one contact `Person` (with `partnerId` + email) and one `Company` per partner so onboarding is testable via a seeded invite email; drops the `person.city` write removed in SDK 2.25 that broke `yarn seed` ## Verification - Unit: **173/173 pass** (27 files) · `tsc --noEmit` clean · `oxlint` 0 warnings/0 errors - End-to-end: invited + signed in a seeded partner (`lena@act-education.example`) on the workspace subdomain; the trigger linked the member to the **Act Education** partner and the self-service **My Profile** page rendered the linked profile (`POST /s/my-partner-profile → 200`) <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23295?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. --> |
||
|
|
302f46f0ea |
fix: bump brace-expansion to 5.0.8 in app lockfiles (Dependabot) (#23346)
## Summary Bumps **brace-expansion -> 5.0.8** in the three app lockfiles whose copy sits on the 5.x line, clearing **GHSA-mh99-v99m-4gvg** (high, vulnerable `<= 5.0.7`) on those manifests: - `examples/hello-world` (`^5.0.2`) - `examples/postcard` (`^5.0.5`) - `internal/self-hosting` (`^5.0.5`) All three are caret ranges, so a recursive `yarn up -R brace-expansion` lifts them with **no resolution and no `package.json` change**. ## Why the fixtures are not included This advisory declares a single vulnerable range, `<= 5.0.7`, which spans **every** major line - so the `brace-expansion@2.1.2` copies in `seed-dependencies` and `common-layer-dependencies` are flagged as well. But **2.1.2 is the last 2.x release** (1.x likewise ends at 1.1.16), and the only patched version is **5.0.8**. Those consumers declare `^2.0.1` / `^2.0.2`, which caps below 3.0.0, so there is no in-range fix: clearing them would mean forcing a cross-major jump from 2.x to 5.x via a resolution, which is a behavior risk rather than a mechanical lift. Same situation for the root alert ([1765](https://github.com/twentyhq/twenty/security/dependabot/1765)), where `nx` pins `brace-expansion` 5.0.6 exact. ## Verification - brace-expansion resolves to **5.0.8** in all three lockfiles. - `yarn install --immutable` passes in each. - 5.0.8 published 2026-07-23, clears the 3-day npm age gate. |
||
|
|
f834b020b6 |
i18n - website translations (#23348)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23348?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. --> --------- Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
88f20a3731 |
i18n - translations (#23352)
Created by Github action --------- Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
a6b36422f9 |
Fireflies: upgrade to twenty-sdk 2.23 (#23349)
Fireflies was skipped by both SDK bump sweeps (#23124, #23165) and sat on `twenty-sdk ^2.18.0` with no `engines.twenty` floor, while the rest of the published set moved to `2.23.0-alpha.2`. - `twenty-sdk` / `twenty-client-sdk` `^2.18.0` -> `2.23.0-alpha.2` - adds `engines.twenty: ">=2.23.0"` No source changes needed: the 2.19 identifier migration (#22601) only affected apps referencing a standard object's system-field identifier, defining a relation into a standard object, or calling the field-UID derivation helper. Fireflies does none of those. The `engines.twenty` floor means the app integration job needs a server image at 2.23+, so it may fail on version mismatch rather than an app defect, as in #22601. |
||
|
|
cdd78462b9 |
fix: block route triggers for suspended workspaces (#23347)
## Problem Logic function route triggers (app HTTP endpoints served under `/s/*` and public domains) kept serving traffic for suspended workspaces. A workspace suspended for non-payment (`activationStatus = SUSPENDED`) still served its route triggers for the entire suspension window until soft-deletion removed it from domain lookup. Every other trigger path gates on activation status — cron triggers only process `ACTIVE` workspaces — but the route trigger path had no check at all. ## Fix `RouteTriggerService.getLogicFunctionWithPathParamsOrFail` now rejects requests when the resolved workspace has `activationStatus = SUSPENDED`, throwing a `RouteTriggerException` with a new `WORKSPACE_SUSPENDED` code mapped to `403 Forbidden` in the REST exception filter. Scope is intentionally limited to `SUSPENDED`; other non-active statuses and the DB event trigger path are untouched. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23347?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. --> --------- Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> |
||
|
|
5948c167a0 |
i18n - website translations (#23196)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23196?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. --> --------- Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
710d4da4b1 |
fix(emails): bump @react-email/render to ^2.0.6 to fix empty transactional email bodies (#23323)
## Problem Fixes #23307. Every transactional email (workspace invite, password reset, email verification, etc.) is delivered with an **empty body** — no title, text, or CTA. ## Root cause `twenty-server` pins `@react-email/render` directly at `^1.2.3`: ```jsonc // packages/twenty-server/package.json "@react-email/render": "^1.2.3", ``` In 1.2.3, `render()` reads `renderToReadableStream` **before** the email template's async Suspense boundary (i18n/locale load) has resolved. The result is the Suspense fallback marker instead of the real markup: ```html <!DOCTYPE html ...><!--$!--><template></template><!--/$--> ``` This was fixed upstream in `@react-email/render@2.0.6` (*"await stream.allReady before reading renderToReadableStream output"*). `twenty-emails` already resolves a 2.x render via `react-email@6.5.0`, so the server's direct pin was simply stale — the two were out of sync. ## Fix Bump the direct pin to `^2.0.6` (resolves to `2.1.0`) and regenerate the lockfile. The server's `render()` imports now use the fixed 2.x. > Note: a `1.2.3` entry remains in `yarn.lock` — it is an internal transitive > pin of `@react-email/components@0.5.3`, not the server render path, so it is > expected and harmless. ## Verification Rendering `SendInviteLinkEmail` through the real `render()` (Node 24) now returns full markup (5.7 kB) with no Suspense marker and the resolved invite link + workspace content, instead of the empty fallback. A jest unit test was intentionally not added: `@react-email/render` 2.x uses a dynamic import that jest's CJS runtime rejects ("A dynamic import callback was invoked without --experimental-vm-modules") — which is exactly why the existing email specs mock `render`. The fix was verified with a standalone Node script. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23323?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. --> --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com> |
||
|
|
44ed0d5498 |
Fix phantom targetId field in workflow triggers for morph relations (#23290)
## Problem
In a workflow **record-change trigger** on `noteTarget`, the output
variables offered a `targetId` field that doesn't exist. A morph
relation reaches the frontend as a single field named `target` (the
per-target morph fields are grouped by `morphId`), so the output-schema
generators synthesized its foreign-key column as `` `${field.name}Id` ``
→ `targetId`. But `noteTarget` has no `targetId` column; its FKs are one
per target type: `targetCompanyId`, `targetPersonId`,
`targetOpportunityId`, etc. The phantom `targetId` never matched
anything in the event payload.
## Fix
New helper `getRelationIdFieldNames` returns the actual FK id column(s)
for a relation field:
- normal relation → `[`${name}Id`]`
- morph relation → one column per `morphRelations` target, via the
existing `computeMorphRelationGqlFieldJoinColumnName`
(`targetCompanyId`, `targetPersonId`, ...).
Used by the two output-schema generators:
- `generateRecordEventOutputSchema` — the record-change trigger output
variables (the reported symptom).
- `generateRecordOutputSchema` — record output for
form/find/update-record output schemas.
Scoped strictly to the output schema; no workflow component changes.
## Testing
- Unit tests updated to assert per-target columns instead of the phantom
`targetId` (both generators). 28 passing.
|
||
|
|
46a3a83866 |
feat(workflow): mirror all workflowVersion writes to core in-transaction, drop async create/update dual-write (#23243)
Switches the workflowVersion -> core dual-write from the async,
best-effort listener to a **transactional mirror**, wires every
content-write funnel through it, and drops the async create/update
handlers. Core can no longer drift from the workspace on any covered
path: the core copy commits or rolls back atomically with the workspace
write.
## Helper (`WorkflowVersionCoreSyncService`)
Two entry points, both writing `core.workflowVersion` and stamping the
`coreWorkflowVersionId` soft-ref on the **caller's transaction
manager**:
- `writeWorkflowVersionAndMirror(workspaceId, write)` - for funnels that
don't own a transaction. Opens a workspace queryRunner, runs the
caller's workspace write on that manager, re-reads the row, mirrors to
core in the same tx, commits, then invalidates the trigger-map cache
post-commit.
- `mirrorWorkflowVersionWrite({ workspaceId, entityManager,
workflowVersion })` - for funnels that already own a queryRunner tx
(activation / deactivation / delete-cascade); they just gain this one
call on their existing manager before commit.
`invalidateAutomatedTriggerMaps` is public so tx-owning callers run it
post-commit.
## Funnels wired (all content writes now mirror in-transaction)
- `updateWorkflowVersionStepsAndTrigger` (central builder step/trigger
edit)
- edge create/delete (4 trigger/step writes)
- `createDraftFromWorkflowVersion` (update + insert),
`duplicateWorkflow` (content update), `updateWorkflowVersionPositions`
- iterator / if-else empty-node step writes
- `workflow.createOne` / `createMany` post-hooks (v1 draft insert)
- AI `create_complete_workflow` tool (v1 insert)
- activation / deactivation status writes (ACTIVE / ARCHIVED /
DEACTIVATED) on their existing tx
- `deactivateVersionOnDelete` cascade (ACTIVE -> DEACTIVATED) on its
existing tx
Direct `workflowVersion.createOne/createMany` is forbidden by a
pre-hook, so there is no un-funneled create path.
## Async listener trimmed
`handleCreated` and `handleUpdated` are removed: every create/update now
mirrors in-transaction, so the post-commit handlers were redundant and
were the source of the rollback-drift (an edit that rolls back still
emitted an `UPDATED` event carrying the uncommitted payload, which the
async listener wrote to core).
`handleRestored`, `handleDeleted`, `handleDestroyed` are **kept**.
Soft-delete/restore go through the generic ORM (not a funnel):
`handleDeleted` drops the core row on soft-delete, and `handleRestored`
recreates it on restore (the restore path just calls
`workflowVersionRepository.restore()`). Removing the restore handler
would leave restored versions with no core row, so the delete/restore
pair stays async.
## Why the core write is raw SQL
`core.workflowVersion` is on the core DataSource, not the workspace
DataSource, so a repository can't be pointed at it from the workspace
queryRunner's manager. But both schemas are one Postgres DB and a
queryRunner is a single connection, so a schema-qualified `INSERT INTO
core."workflowVersion" ... ON CONFLICT` on that manager participates in
the workspace transaction (the prefill util's pattern). The workspace
write and soft-ref write-back go through the ORM's
`repository.update(criteria, data, undefined, queryRunner.manager)`, so
the source-of-truth workspace write keeps its ORM machinery (actor
stamping, search vector, events); only the dumb core mirror is raw,
confined to the helper.
**jsonb:** the raw insert has no entity transformer, so
`triggers`/`steps` are passed as JSON strings (Postgres parses them) -
the inverse of the prefill core insert (the #23204 double-encode trap),
covered by the test. `universalIdentifier` is minted only for a new
link; on conflict only `triggers`/`steps`/`status` are updated.
## Test
In-process integration test: opens a workspace queryRunner, calls the
helper, asserts the core row is visible inside the tx with correct
native jsonb, rolls back, asserts the core row is gone - empirically
confirming one workspace queryRunner writes `core.*` in the same tx.
## Review feedback addressed
- **Duplicate atomicity:** `duplicateWorkflow` now inserts the draft
version with its final steps/trigger inside
`writeWorkflowVersionAndMirror`, so a mirror failure rolls back the
whole version instead of leaving an unmirrored empty draft.
- **Delete cascade:** `deactivateVersionOnDelete` moved its command-menu
cleanup after commit, so a rolled-back deactivation can no longer strip
the menu item from a still-active version.
- **Rollback drift / unlinked rows:** resolved structurally by the
in-transaction mirror + removal of the async CREATED/UPDATED handlers,
and by the soft-ref-field guard that skips mirroring (returns null) on
workspaces without `coreWorkflowVersionId`.
- **Unit suites:** the step-helpers, step-operations and edge suites now
provide a `WorkflowVersionCoreSyncService` mock whose
`writeWorkflowVersionAndMirror` runs the write callback against the test
repo; all three pass (35 tests).
## Verification
Rebased onto `origin/main`. `nx typecheck twenty-server` is green, the
three affected unit suites pass (35 tests), and every changed file
passes type-aware oxlint + oxfmt. Integration suites were failing only
on behind-main DB-init drift (`core.keyValuePair` / data-migration),
which the rebase resolves. Live end-to-end test on a running instance
still pending.
|
||
|
|
a96dc335ab |
fix: apply configured pool size to core database (#23322)
## Context `PG_POOL_MAX_CONNECTIONS` is the server setting for the maximum number of PostgreSQL clients in a connection pool. The workspace primary and replica data sources already apply this setting, but the core TypeORM data source did not. Without an explicit `poolSize`, `node-postgres` uses its default limit of 10. As a result, deployments configured with a larger pool still kept the core pool at 10 connections per server process. During bursts of core database work, requests could therefore wait for a local pool connection even when PostgreSQL itself still had available capacity. That acquisition queue adds latency before the query starts, so database-level utilization alone does not reveal the bottleneck. ## What changes The core data source now applies: ```ts poolSize: Number(process.env.PG_POOL_MAX_CONNECTIONS ?? 10) ``` This makes the core data source consistent with the workspace data sources and with the documented meaning of `PG_POOL_MAX_CONNECTIONS`. ## Expected impact Deployments that configure a value above 10 can use that capacity for core database operations instead of queueing behind the driver's default limit. This targets short acquisition spikes affecting operations backed by the core database. The pool remains lazy, so this changes the maximum number of connections available to each process, it does not eagerly open every configured connection. ## Safety - Deployments without `PG_POOL_MAX_CONNECTIONS` keep the previous limit of 10. - Query behavior, transaction behavior, and timeouts are unchanged. - Workspace pool configuration is unchanged. - Operators remain responsible for choosing a value compatible with their total PostgreSQL connection budget and maximum server replica count. ## Scope This removes an unintended local connection-pool bottleneck. It does not address the source of synchronized database bursts, which should be handled separately by reducing unnecessary work. ## Testing Added a regression test that loads the core data source with `PG_POOL_MAX_CONNECTIONS=40` and verifies that TypeORM receives `poolSize: 40`. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23322?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. --> |
||
|
|
42c598a33a |
Filter stale sync workspaces by active channels (#23321)
## Context The hourly calendar and messaging stale-sync crons currently load every active workspace and enqueue one recovery job per workspace. Each recovery job then enters the workspace context and queries its channels, even when that workspace has no stale sync. As the number of workspaces grows, the cost of this check grows with every workspace rather than with the number of syncs that actually need recovery. Because both crons run on the hour, this also creates a synchronized burst of mostly unnecessary queue, database, and workspace-context work. ## What changes The crons now use TypeORM repository `find` operations on `calendarChannel` and `messageChannel` before enqueueing recovery jobs: - Select only `workspaceId` from matching channels. - Keep only active, non-deleted workspaces. - Keep only the scheduled and ongoing stages handled by the existing recovery jobs. - Treat a sync as stale when `syncStageStartedAt` is null or older than the existing 30-minute timeout. - Deduplicate workspace IDs in memory before enqueueing. - Enqueue the existing recovery job only for matching workspaces. - Share the stage definitions between candidate selection and recovery so they cannot drift independently. ## Before and after Before: 1. Load every active workspace. 2. Enqueue a calendar job and a messaging job for each workspace. 3. Enter every workspace context. 4. Query its channels. 5. Usually find nothing to recover. After: 1. Run one candidate lookup for calendar channels and one for message channels. 2. Return only the workspace ID for each potentially stale channel. 3. Deduplicate those IDs and enqueue one job per affected workspace. 4. Let the existing recovery jobs recheck and recover those channels. Database reads now scale with stale channel candidates, while queue and workspace-context work scale with affected workspaces rather than the total workspace count. ## Why deduplicate in memory TypeORM repository find options do not provide `DISTINCT`, so these lookups can return the same workspace ID once per stale channel. A `Set` removes those duplicates before jobs are created. This is a deliberate tradeoff: - The database returns only UUIDs, not full channel records. - The scans run hourly against channel tables, and stale candidates should remain sparse. - It keeps the query expressed through the typed repository API. - It avoids adding a database sort or hash aggregation for `DISTINCT`. If candidate volume becomes large enough for result transfer or in-process deduplication to matter, database-side deduplication can be moved into a dedicated repository query. A practical signal would be tens of thousands of candidates per run, a high duplicate ratio, or measurable lookup and event-loop latency. ## Safety - The recovery jobs and sync-state transitions are unchanged. - The candidate lookup uses the same stage lists and timeout constants as the recovery jobs. - Recovery jobs still recheck staleness after dequeueing, which protects against a channel recovering between candidate selection and execution. - Jobs are still enqueued sequentially with per-workspace exception handling. - Disabled and group channels are not newly excluded, preserving the previous recovery behavior. ## Scope This only changes hourly stale-sync detection. It does not change normal calendar or messaging scheduling, imports, retry policies, or recovery state transitions. ## Testing Added unit coverage for: - active and non-deleted workspace filtering, - selecting only workspace IDs, - scheduled and ongoing stage filtering, - null or expired `syncStageStartedAt`, - the configured stale timeout, - application-side workspace deduplication, - enqueueing one recovery job per affected workspace. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23321?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. --> |
||
|
|
ed95b8cfde |
fix: bump postcss to 8.5.22 across app lockfiles (Dependabot) (#23340)
## Summary Bumps **postcss -> 8.5.22** in the 13 twenty-apps lockfiles that carry it transitively, clearing **GHSA-r28c-9q8g-f849** (high) on those manifests: path traversal in previous source map auto-loading (`sourceMappingURL`) leading to arbitrary `.map` file disclosure, vulnerable `<= 8.5.17`. Apps covered: document-generator, hello-world, postcard, self-hosting, twenty-partners, call-recorder, people-data-labs, twenty-discord, twenty-exa, twenty-fireflies, twenty-last-contact, twenty-linear, twenty-slack. Every app reaches postcss through a caret range (`^8.5.15`), so a recursive `yarn up -R postcss` lifts it in each project with **no resolution and no `package.json` change** - the diff is 13 `yarn.lock` files and nothing else. Yarn resolves to **8.5.22**, the latest in range (above the 8.5.18 fix floor). ## Not included The **root lockfile** carries the same advisory but its postcss copies are held by exact pins - `next` (8.4.31 in every stable release, including 16.2.11) and `@mintlify/common` (8.5.14, unchanged in its latest) - so no `yarn up` reaches it. That one needs a scoped resolution and is handled separately. ## Verification - postcss resolves to **8.5.22** in all 13 lockfiles; nothing at or below 8.5.17 remains. - `yarn install --immutable` passes in each of the 13 projects. - 8.5.22 published 2026-07-22, clears the 3-day npm age gate. |
||
|
|
6060d88c54 |
fix: bump tar 7.5.20 -> 7.5.21 in the root lockfile (Dependabot) (#23330)
## Summary Bumps **tar 7.5.20 -> 7.5.21** in the root `yarn.lock`, clearing Dependabot alert [1852](https://github.com/twentyhq/twenty/security/dependabot/1852): **GHSA-r292-9mhp-454m** (medium) - uncontrolled recursion in `mapHas`/`filesFilter` allows an uncatchable stack-overflow DoS via a crafted long-path tar with member selection, vulnerable `<= 7.5.20`. Every root tar consumer declares a caret range (`^7.4.3`, `^7.5.4`, `^7.5.9`, `^7.5.11`, `^7.5.16`) and the existing scoped tar resolutions for the @electron/rebuild toolchain and @mintlify/previewing are carets as well (`npm:^7.5.16`), so a recursive `yarn up -R tar` lifts the single tar entry with **no resolution change and no `package.json` change**. ## Verification - `yarn install --immutable` passes. - Diff is `yarn.lock` only; the single tar entry resolves to 7.5.21, nothing below remains. - 7.5.21 published 2026-07-21, clears the 3-day npm age gate. The same advisory affects the twenty-apps and server fixture lockfiles; those follow in separate PRs. |
||
|
|
97bb56d471 |
fix: bump tar and brace-expansion in seed-dependencies (Dependabot) (#23333)
## Summary Bumps **tar 7.5.20 -> 7.5.21** and **brace-expansion 5.0.7 -> 5.0.8** in the `application-package/constants/seed-dependencies` fixture: | Severity | Advisory | Package | Alert | |---|---|---|---| | medium | GHSA-r292-9mhp-454m | tar (`<= 7.5.20`) | [1850](https://github.com/twentyhq/twenty/security/dependabot/1850) | | high | GHSA-mh99-v99m-4gvg | brace-expansion | [1856](https://github.com/twentyhq/twenty/security/dependabot/1856) | Both are reached through caret ranges (`^7.5.4`, `^5.0.2`), so the lockfile diff comes from a plain recursive `yarn up` - no resolution, no `package.json` change. ## Checksum coupling This fixture is read at runtime by `getDefaultApplicationPackageFields` and pinned by stored constants (first 32 hex chars of SHA512). **`DEFAULT_YARN_LOCK_CHECKSUM`** is regenerated to match the new lockfile. `package.json` is byte-untouched so `DEFAULT_PACKAGE_JSON_CHECKSUM` stays as-is. Verified in order: the hash formula reproduces **both** current constants before regenerating; the new constant matches the new content; `yarn install --immutable` passes in the fixture. ## sharp deliberately excluded The third open alert here (GHSA-f88m-g3jw-g9cj, high - inherited libvips CVEs) is **not** a lockfile lift: sharp is a *direct* dependency of this fixture at `^0.34.5`, so clearing it means moving the declared range to `^0.35.0`. That changes the package set exposed to user logic functions, shifts `DEFAULT_PACKAGE_JSON_CHECKSUM` too, and sharp 0.35 raises its engines floor from Node 18 to `>=20.9.0` while Lambda layers are still advertised as NODE18-compatible. The same `^0.34.5` ceiling gates twenty-sdk and 15 app manifests, so it deserves one coordinated decision rather than a drive-by change here. |
||
|
|
0efc92b3f3 |
fix: bump shell-quote 1.8.4 -> 1.10.0 (Dependabot) (#23331)
## Summary Bumps **shell-quote 1.8.4 -> 1.10.0**, clearing Dependabot alert [1769](https://github.com/twentyhq/twenty/security/dependabot/1769): **GHSA-395f-4hp3-45gv / CVE-2026-13311** (high) - quadratic-complexity Denial of Service in `parse()` (CWE-407), vulnerable `<= 1.8.4`, fixed 1.9.0. Both consumers declare caret ranges - `@graphql-codegen/cli` (`^1.7.3`) and `concurrently` (`^1.8.1`) - so a recursive `yarn up -R shell-quote` lifts the single entry with **no resolution and no `package.json` change**. Yarn resolves to 1.10.0, the latest in range (above the 1.9.0 fix floor). ## Verification - `yarn install --immutable` passes. - Diff is `yarn.lock` only; shell-quote resolves to 1.10.0, no 1.8.4 remains. - 1.10.0 published 2026-07-10, clears the 3-day npm age gate. |
||
|
|
155636d7d9 |
fix: bump tar to 7.5.21 across app lockfiles (Dependabot) (#23332)
## Summary Bumps **tar -> 7.5.21** in the 13 twenty-apps lockfiles that carry it transitively, clearing **GHSA-r292-9mhp-454m** (medium) on those manifests: uncontrolled recursion in `mapHas`/`filesFilter` allows an uncatchable stack-overflow DoS via a crafted long-path tar with member selection, vulnerable `<= 7.5.20`. Apps covered: document-generator, hello-world, postcard, self-hosting, twenty-partners, call-recorder, people-data-labs, twenty-discord, twenty-exa, twenty-fireflies, twenty-last-contact, twenty-linear, twenty-slack. Every app reaches tar through a caret range (`^7.5.4`), so a recursive `yarn up -R tar` lifts it in each project with **no resolution and no `package.json` change** - the diff is 13 `yarn.lock` files and nothing else. ## Not included - **Root lockfile**: same advisory, shipped separately in #23330. - **`application-package/constants/seed-dependencies`**: the 14th manifest with this advisory. Its `yarn.lock` is checksum-coupled to `DEFAULT_YARN_LOCK_CHECKSUM`, so it moves in its own PR with the constant regenerated alongside. ## Verification - tar resolves to **7.5.21** in all 13 lockfiles; nothing below remains. - `yarn install --immutable` passes in each of the 13 projects. - 7.5.21 published 2026-07-21, clears the 3-day npm age gate. |
||
|
|
ad3291f4b4 |
i18n - docs translations (#23338)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23338?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. --> Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
32a031ac0b |
i18n - translations (#23336)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23336?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. --> Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
1e5b8e6db4 |
fix(front): add accessible labels to icon-only options dropdown triggers (#23325)
## Problem Refs #23127. Icon-only `LightIconButton` "more options" triggers (`IconDotsVertical`) render without an accessible name, failing WCAG 4.1.2 (button-name) — screen readers announce nothing for them. ## Fix Add `aria-label={t`More options`}` to the affected dropdown triggers. `LightIconButton` already forwards `aria-label` and sets `aria-hidden` on the icon when a label is present, so this is purely additive — no behavioral or visual change. Scoped to a coherent set of options-menu triggers (attachments, public domains, SSO, connected accounts, field group config). Other unlabeled icon buttons can follow in separate PRs. ## Verification `oxlint --type-aware` and `oxfmt` pass on all changed files. The label is i18n-wrapped via the existing `useLingui` macro already imported in each component. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23325?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. --> |
||
|
|
4f9fd6f674 |
feat(applications): restore the application custom settings tab (#23256)
## Summary Restores the application **custom settings tab** feature that was removed in #22156. This reverts that removal so applications can again expose a custom settings tab via a front component. ## Changes - Restore the `SettingsApplicationCustomTab` component and its tab entry/rendering in `SettingsApplicationDetails`. - `ApplicationManifestMigrationService` syncs `settingsCustomTabFrontComponent` from application manifests again (`syncDefaultRoleAndSettingsCustomTab`), resolving the front component from `settingsCustomTabFrontComponentUniversalIdentifier`. - Remove the deprecation annotations added by #22156: - `ApplicationDTO.settingsCustomTabFrontComponentId` (drop GraphQL `@deprecated`) - `ApplicationManifest.settingsCustomTabFrontComponentUniversalIdentifier` - the `settingsCustomTabFrontComponentId` column comment on `ApplicationEntity` - Regenerate the corresponding GraphQL schema/types to drop the `@deprecated` reason. The DB column was never dropped, so no schema migration is required. --- _Generated by [Claude Code](https://claude.ai/code/session_01A6aoLa5kZjba9C3uwo6nay)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23256?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. --> --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com> |
||
|
|
a94f2443b3 |
i18n - translations (#23328)
Created by Github action --------- Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
3fb29db28a |
Feat/email settings v2 (#23180)
Settings pages changes - Add `displayName` - Unsubscribers Page <img width="1496" height="844" alt="Screenshot 2026-07-22 at 8 52 15 PM" src="https://github.com/user-attachments/assets/69bc1993-4547-4a64-83a6-b47fef1a4e40" /> <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23180?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. --> --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com> |
||
|
|
8326fd186f |
i18n - docs translations (#23324)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
1ee08ff92b |
docs: set correct credit cost for workflow steps and app logic functions (#23297)
The Credits page listed the app logic function row as "A small fraction of a credit / Thousands per credit", which understates the rate. A workflow step and a logic function run each cost a flat 100 micro-credits (`workflow-executor.workspace-service.ts:353`, `logic-function-executor.service.ts:536`), i.e. $0.0001, or 10,000 runs per credit. Since the two rows carried the same rate stated twice, they're merged into one. Also adds a note that Call Recorder and Last contact don't consume credits for their logic function runs. They're in `MARKETPLACE_BILLING_EXEMPT_UNIVERSAL_IDENTIFIERS`, which is 2 of the 3 apps currently in `MARKETPLACE_VETTED_APPLICATIONS`. The note is explicit that the exemption covers only the per-run charge, since those apps still bill metered work (recorded call minutes) through `chargeCredits`. ## Test plan Docs-only change. Rates cross-checked against `workflow-executor.workspace-service.ts` and `logic-function-executor.service.ts`; the exempt list against `marketplace-billing-exempt-applications.constant.ts` and `marketplace-vetted-applications.constant.ts`. Co-authored-by: Martin <martin@twenty.com> |
||
|
|
24067ec87a |
chore: remove twenty-companion dead code (#23310)
## What Removes `packages/twenty-companion` (package name `twenty-desktop`), the Electron "Twenty Desktop" proof of concept that landed with the Recall.ai call-recording work in #18281. ## Why it's dead code - **Not in the nx graph** — no `project.json`, so no target ever runs against it. - **Not in CI** — no workflow references it. #21327 said as much when bumping its Electron: "there's no CI job that builds/tests twenty-companion, so this isn't exercised by CI". - **No code references** — nothing imports it, and the only path references were the root `workspaces` array, `yarn.lock`, and `.vscode/twenty.code-workspace`. It talks to Twenty over the public REST API from a separate process, so there is no coupling to remove. - **Self-declared POC** — its README opens with "This application is a Proof of Concept (POC) and must NOT be used in production. [...] Security, stability, and performance have not been validated for production use." - **No feature work since it landed** (March 2026). Every commit touching it since has been a dependency or tooling sweep: React 19 migration, ESLint→OxLint, npm→yarn workspaces, and four CVE bumps. - **Docs already stale** — its README points at `packages/twenty-apps/internal/call-recording`, which no longer exists. The shipped app lives at `packages/twenty-apps/public/call-recorder` and does not reference the desktop companion. Meanwhile it pulled a full Electron + electron-forge toolchain into every root install, and kept generating Dependabot noise against a tree nothing builds. ## Changes - Delete `packages/twenty-companion`. - Drop its entry from root `workspaces` and from `.vscode/twenty.code-workspace`. - Drop four root `resolutions` that existed only to evict CVEs from the Electron tree, along with their entries in the `//resolutions` rationale doc: - `@electron/rebuild/tar`, `@electron/node-gyp/tar` - `@electron-forge/plugin-webpack/webpack-dev-server` - `make-fetch-happen` — its only sub-`^15` consumer was the Electron `node-gyp` fork; the remaining consumers (`@sigstore/sign`, `npm-registry-fetch`, `tuf-js`) already declare `^15.x` - Regenerate `yarn.lock`. ## Lockfile impact 469 descriptors removed, **zero version changes for any surviving descriptor** (verified with a descriptor-level diff of old vs new resolutions). Two descriptors show up as new — `make-fetch-happen@npm:^15.0.1` and `@npm:^15.0.4` — only because the global resolution was previously rewriting them; both still resolve to `15.0.6`. Re-running resolution produces a byte-identical lockfile. ## Test plan - [x] Repo-wide grep confirms no remaining references to `twenty-companion` / `twenty-desktop` / the Electron toolchain. - [x] `yarn install --mode=update-lockfile` is stable and idempotent under hardened mode. - [x] Descriptor-level lockfile diff shows no resolution changes outside the removed tree. - [ ] CI green (nothing targets the removed package, so the risk surface is the lockfile). --- _Generated by [Claude Code](https://claude.ai/code/session_019NttPZiJWSJz56RW8pZ5jN)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23310?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. --> |
||
|
|
0b44864f5f |
i18n - translations (#23315)
Created by Github action --------- Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
472c7c1edc |
fix(workspace): open edit panel for PAGE_LAYOUT sidebar items (#23293)
Fixes #22649. Custom page-layout links in the sidebar (like "Star History") couldn't be removed. Clicking them in edit mode did nothing. **Root cause** `handleNavigationMenuItemClick` in `WorkspaceSection.tsx` switches on `item.type`. `FOLDER` and `LINK` have explicit cases that call `openNavigationMenuItemInSidePanel`. `PAGE_LAYOUT` fell through to `default`, which calls `openViewOrRecordEditPanelAndNavigate`. That function only opens the side panel when `objectMetadataItem` is defined - PAGE_LAYOUT items don't have one - so the panel never opened. **Fix** Add a `PAGE_LAYOUT` case that calls `openNavigationMenuItemInSidePanel` directly, using the item's own label and icon. Same pattern as `LINK`. **How to test** 1. Create a custom page link in the sidebar (Settings > Workspace > Add menu item > Page layout). 2. Click the wrench icon to enter edit mode. 3. Click the custom page item - the edit side panel should now open. 4. Verify you can remove it from the sidebar. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23293?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. --> --------- Co-authored-by: prastoin <paul@twenty.com> |
||
|
|
4eb5ad9e32 |
i18n - translations (#23313)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23313?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. --> --------- Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
93066ae800 |
fix(a11y): add aria-label to navigation drawer collapse button (WCAG … (#23287)
…4.1.2) Fixes #23131 Added `aria-label` to the navigation drawer collapse/expand button (LightIconButton with IconLayoutSidebarLeftCollapse/RightCollapse), which previously had no accessible name for screen readers. Verified with axe DevTools scan on localhost — the button-name violation for this element no longer appears. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23287?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. --> --------- Co-authored-by: Thomas Trompette <thomas.trompette@sfr.fr> |
||
|
|
763d31a859 |
chore: sync AI model catalog from models.dev (#23298)
Automated daily sync of `ai-providers.json` from [models.dev](https://models.dev). This PR updates pricing, context windows, and model availability based on the latest data. New models meeting inclusion criteria (tool calling, pricing data, context limits) are added automatically. Deprecated models are detected based on cost-efficiency within the same model family. **Please review before merging** — verify no critical models were incorrectly deprecated. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23298?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. --> Co-authored-by: FelixMalfait <6399865+FelixMalfait@users.noreply.github.com> |
||
|
|
c102a22375 |
fix: lift axios/tar/brace-expansion/body-parser in server fixture lockfiles (Dependabot) (#23291)
## Summary Follow-up to #23267: lifts **axios, tar, brace-expansion, body-parser** in the two **twenty-server fixture projects** that were deliberately excluded from the apps sweep because of checksum coupling: - `application-package/constants/seed-dependencies`: axios 1.16.1 -> 1.18.1, body-parser 1.20.5 -> 1.20.6, brace-expansion 2.1.1 -> 2.1.2 and 5.0.6 -> 5.0.7, tar 7.5.16 -> 7.5.20 - `logic-function/.../common-layer-dependencies`: brace-expansion 2.1.1 -> 2.1.2 All moves fit the declared ranges (recursive `yarn up`), so both diffs are lockfile-only. ## Checksum coupling `seed-dependencies` is read at runtime by `getDefaultApplicationPackageFields` and its content is pinned by stored constants (first 32 hex chars of SHA512; package.json hashes the re-serialized JSON). The lockfile change therefore regenerates **`DEFAULT_YARN_LOCK_CHECKSUM`** in `get-default-application-package-fields.util.ts`. `package.json` is byte-untouched, so `DEFAULT_PACKAGE_JSON_CHECKSUM` stays. Verified in order: the hash formula reproduces both *current* constants before regenerating; the new constant matches the new lockfile content; `yarn install --immutable` passes in both fixture projects. `common-layer-dependencies` has no checksum coupling (copied + installed at Lambda layer build time). ## Deliberately not covered **sharp** stays at 0.34.5 in seed-dependencies: every path is minor-locked at `^0.34.5` (including twenty-sdk latest); pending the twenty-sdk range decision. ## Alerts Clears the axios (10), tar (4), brace-expansion (3) and body-parser (1) Dependabot alerts on these two manifests. |
||
|
|
ab3d921218 |
i18n - docs translations (#23292)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23292?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. --> Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
4851489ebc |
fix(ai-chat) fix AI chat tool-output spill leaks: spill learn_tools, cap navigation tools, truncate on spill failure (#23286)
## Context
AI chat spills tool outputs larger than `MAX_INLINE_TOOL_OUTPUT_BYTES`
(16 kB) to a file and lets
the model page them back with `search_output` / `extract_json_paths`.
Three paths bypass this and
let unbounded payloads into conversation history:
1. **`learn_tools` is never spilled.** Tool schemas go inline whatever
their size.
2. **Navigation tools have no inline cap.** They are exempt from
spilling by design (they page
spilled files), but nothing bounds their own output.
3. **Spill failure falls back to full inline.** On any spill error the
service returns the complete
payload with only a warning appended.
## What changed
- **`learn_tools` now spills.** `createLearnToolsTool` takes `{
excludeTools?, spillLargeOutput? }`
(same shape as `createExecuteToolTool`); chat execution enables it. Only
the bulky `tools`
schemas are spilled; `message` / `notFound` / `suggestions` stay inline
and the response carries
a `spilledTools` envelope (fileId, preview, hint) pageable via
`extract_json_paths`. MCP is
unchanged.
- **Navigation tools get a hard inline cap.** Still never spilled, but
output above 16 kB is
head+tail truncated with a marker telling the model to narrow the query
or page with `offset`.
- **Spill failure truncates instead of inlining.** The fallback returns
head+tail within the 16 kB
budget with the original byte size in the marker, keeping the warning.
- New `truncateHeadTail` util: byte-budgeted, marker-aware, UTF-8
codepoint-safe.
## Test plan
- `tool-output-spill.service.spec.ts`: spill envelope unchanged,
under-budget passthrough,
navigation cap for both tools (budget respected, marker mentions
`offset`, no file written),
truncated fallback on spill failure with warnings preserved.
- `learn-tools.tool.spec.ts`: no spill without the option, inline under
budget,
`message`/`notFound`/`suggestions` intact when spilled, spill-failure
warnings surfaced.
- `truncate-head-tail.util.spec.ts`: budget, head+tail+marker,
multibyte-safe cuts.
- 63 tests across 6 suites; `lint:diff-with-main` and `typecheck` green.
## Post-deploy
Watch the `AiChatToolOutputTokens` histogram (p95 should collapse to ~4k
tokens) and the
`AiChatInputTokens` / `AiChatCacheReadTokens` ratio on GPT-5-class
models.
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/23286?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. -->
|
||
|
|
3a1067ec6d |
fix(server): index page-layout FKs to fix workspace cleanup timeout (#23289)
The cleanSuspendedWorkspacesJob cron timed out every run (Sentry monitor
"a timeout check-in was detected"): hard-deleting soft-deleted
workspaces hung on `DELETE FROM core.pageLayout`, hit the 10s query
timeout, rolled back, so those workspaces were never destroyed and got
retried hourly.
Root cause: the FKs in the pageLayout -> pageLayoutTab ->
pageLayoutWidget tree had no usable index on the referencing column. The
existing indexes lead with workspaceId and are partial ("deletedAt" IS
NULL), so ON DELETE CASCADE / SET NULL fell back to full sequential
scans of the shared core tables per deleted row; on layout-heavy
workspaces this exceeded 10s.
- Add non-partial FK-column indexes on pageLayoutTab(pageLayoutId) and
pageLayoutWidget(pageLayoutTabId)
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/23289?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. -->
|
||
|
|
45f92b6763 |
feat(billing): make logic function executions free for exempt apps (#23255)
## Problem Workspaces get 5 free credits/month to run logic functions, AI and workflows. When a user imports their mailbox with the onboarding-suggested **Call Recorder** and **Last contact** apps, each imported message/calendar event fires those apps' database-event-triggered logic functions, and each execution bills a flat 100 micro-credits. A single import can fire tens of thousands of executions and drain the entire monthly allowance before the user has done anything else. The trigger pipeline has no notion of "this came from sync", and logic function executions are metered per record (one job per imported record), so the burn is unavoidable today. ## Approach Keep a static list of billing-exempt app identifiers (`MARKETPLACE_BILLING_EXEMPT_UNIVERSAL_IDENTIFIERS` — Call Recorder and Last contact) and check it in the logic-function executor's billing step via a small `isBillingExemptApplication(universalIdentifier)` utility. When the running app is exempt, the per-invocation meter records `creditsUsedMicro: 0` and skips the credit decrement. Scope is deliberately narrow: only the automatic per-invocation meter is exempted. Anything the function itself charges via `chargeCredits` (the separate `/app/billing/charge` endpoint) and any AI token usage keep billing and keep their enforcement, so a free app can still charge for real paid work (e.g. Call Recorder's per-recording charge, People Data Labs enrichment) and AI usage still throws on credit exhaustion. There is no DB column, migration, cache, admin UI, or per-registration state — the exemption is derived entirely from the app's `universalIdentifier` against the in-memory list, so it applies uniformly to fresh and existing installations. ## Changes - `isBillingExemptApplication` utility over the exempt-apps constant, with a unit test. - Logic-function executor consults the utility to decide `creditsUsedMicro` (0 for exempt apps, 100 otherwise) and only decrements credits for non-exempt invocations. ## Notes / follow-ups - This fixes the billing drain but not the execution burst: an import still fires the real isolate executions for zero user-visible benefit over the apps' existing batch backfill. Suppressing database-event triggers during historical import is a complementary follow-up worth doing for infra cost and rate-limit reasons. ## Test plan - [x] `nx typecheck twenty-server` / `nx typecheck twenty-front` - [x] Server unit tests (`isBillingExemptApplication`) pass - [ ] Manual: install Call Recorder / Last contact, import a mailbox, confirm credits are not consumed by their logic function executions while AI usage and in-app charges still bill |
||
|
|
d6c186a71e |
Fix system objects bypassing role object permission overrides (#23280)
## Bug Fixes #23062 (security). A workspace member whose role denies all object access could still read/mutate system-object records (messages, calendar events, and related system objects). System objects bypassed explicit role-level object permissions. ## Root cause In `workspace-roles-permissions-cache.service.ts`, the per-object permission helper resolved values as: ```ts (isSystem ? true : (overrideValue ?? defaultValue)) ``` For every non-workflow, non-workspace-member system object this forced `read`/`update`/`softDelete`/`destroy` to `true`, so an explicit deny override on the role was never consulted. ## Fix Flip the precedence so an explicit role override wins, and the `isSystem` default only applies when the role provides no override: ```ts overrideValue ?? (isSystem ? true : defaultValue) ``` Because the override fields are `boolean | undefined`, `??` correctly honors an explicit `false` while still falling back to the system default (`true`) when the role has no override row for that object. Workflow objects (settings-gated via the `WORKFLOWS` flag) and workspace-member objects (settings-gated, always readable) are handled in separate branches and are unchanged, so their intended defaults do not regress. ## Testing - `nx lint:diff-with-main twenty-server` passes. - Typecheck: no new errors from this change (pre-existing unrelated failures in `twenty-shared` date-filter utils only). - Manually verified on a local instance that a deny-all role no longer has read access to system objects. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23280?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. --> |
||
|
|
bc7922da7d |
fix: move add-agent-foreign-key-to-role-target instance command to 2.25 (#23285)
## What Moves the `add-agent-foreign-key-to-role-target` fast instance command from `2.24.0` to `2.25.0`. Introduced in #23206, the command was registered under version `2.24.0`. Since `TWENTY_CURRENT_VERSION` is now `2.25.0`, `2.24.0` is an already-released version, so its instance commands do not re-run on upgrade and the foreign-key migration would never execute. This is the same issue #23271 fixed for the message-list-members backfill workspace command. ## Changes - Moved the command file from `upgrade-version-command/2-24/` to `2-25/` (renamed the file prefix). - Updated the decorator from `@RegisteredInstanceCommand('2.24.0', ...)` to `('2.25.0', ...)`. - Updated the import in `instance-commands.constant.ts` to the new relative path and reordered both the import and the array entry to sit after the 2-24 commands. The timestamp (`1784820332810`) and command logic (`up`/`down`) are unchanged. --- _Generated by [Claude Code](https://claude.ai/code/session_01KpCb9BK1eoM1WWXJU6g95e)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23285?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. --> |
||
|
|
0d876eb714 |
fix: lift axios/tar/brace-expansion/body-parser across app lockfiles (Dependabot) (#23267)
## Summary Sweeps the **twenty-apps lockfiles** for this week's advisory wave: recursive `yarn up` for **axios, tar, brace-expansion, body-parser** in each of the 12 apps with open Dependabot alerts (hello-world, postcard, self-hosting, twenty-partners, call-recorder, people-data-labs, twenty-discord, twenty-exa, twenty-fireflies, twenty-last-contact, twenty-linear, twenty-slack). All moves fit the declared ranges (apps carry these transitively via `twenty-sdk`, whose `axios ^1.16.0` and deep tar/brace chains are carets), so the diff is **lockfile-only** across all 12 manifests - no resolutions, no `package.json` changes. axios -> 1.18.x, tar -> 7.5.20 (critical GHSA-23hp-3jrh-7fpw chain), brace-expansion -> 1.1.16 / 2.1.2 / 5.0.7, body-parser -> 1.20.6 / 2.3.0. The second commit narrows scope to apps only: the twenty-server fixture projects (seed-dependencies, common-layer-dependencies) move to a dedicated PR because seed-dependencies' yarn.lock is checksum-coupled to `DEFAULT_YARN_LOCK_CHECKSUM` in `get-default-application-package-fields.util.ts`; it also drops accidentally committed `.yarn/install-state.gz` artifacts. ## Deliberately not covered - **sharp**: every path is minor-locked at `^0.34.5` (including twenty-sdk latest) - separate PR bumping twenty-sdk's range. - **react-router / react-router-dom**: no fixed release on the 6.x line (fix is the v7 major); tracked separately. ## Verification - Vulnerable-version scan across all 12 lockfiles: no axios <1.18, tar <7.5.19, brace-expansion below 1.1.16/2.1.2/5.0.7, or body-parser below 1.20.6/2.3.0 remains. - `yarn install --immutable` passes in each app. - All fix versions clear the 3-day npm age gate. |