e15b9efc2d053e24e5423d10000b1efbcc191fec
13805 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e15b9efc2d |
Deprecate workspace metadataVersion and stop consuming it in the frontend (#23189)
## Context Follow-up to #23164. Now that the metadata GraphQL response cache and the workspace SDL cache are keyed on flat-map hashes, `workspace.metadataVersion` no longer drives any cache invalidation. This PR is the next stage of retiring it: the frontend stops consuming the field entirely, and the public GraphQL field is marked deprecated so external API consumers get a migration signal. ## What changed **Frontend stops consuming `metadataVersion`:** - `userQueryFragment.ts` no longer selects the field. - `currentWorkspaceState.ts` drops it from the workspace `Pick`. - `apollo.factory.ts` no longer attaches the `X-Schema-Version` request header. Dropping the header retires the "your workspace has been updated, please refresh the page" error rewrite on the server (it only fired when the header was present, and only on requests that had already failed validation). Metadata staleness detection is unaffected: the frontend has been running on collection hashes plus SSE since the minimal-metadata work, so that path stays intact. Stale clients now surface a raw validation error instead of the friendly message, which we consider an acceptable trade for deleting the mechanism. **Server marks the field deprecated:** - `workspace.entity.ts`: `@Field({ deprecationReason: 'No longer used for metadata cache invalidation, will be removed' })`. **Regenerated (CI-enforced surfaces):** `twenty-front/src/generated-metadata`, and `twenty-client-sdk`'s generated schema, which now carries `@deprecated(reason: ...)`. The `admin` codegen config produced no changes. `packages/twenty-sdk/generated` is intentionally untouched: no in-repo command produces it, CI does not drift-check it, and its committed snapshot lags the live schema, so regenerating it here would pull unrelated schema drift into this PR; it will pick up the directive on its next routine refresh. ## Deployment notes - No ordering constraint with #23164: removing a field selection and a request header is backward compatible against any server, and old frontend bundles keep working during the rollout because the field still exists and the server-side header check is still in place. Same release is fine. - The follow-up server cleanup (removing the `X-Schema-Version` check in `use-graphql-error-handler.hook.ts`, the per-request `metadataVersion` reads and seed in `middleware.service.ts`/`jwt-auth.guard.ts`, and the REST heal block) must wait until the release containing this PR has shipped, since a deployed frontend still selecting the field would break `GetCurrentUser` if the field were removed first. After that cleanup, the only remaining `metadataVersion` consumers are the five pinned upgrade commands (2.8 through 2.20), which hold the column and `WorkspaceMetadataVersionService` until that upgrade window closes; the physical column drop then follows the two-phase pattern used for `gridPosition`. ## Validation - Server and frontend typecheck, lint, and format pass; the apollo factory test suite passes unchanged (it fixtures the field but never asserted the header). - Live introspection against a server running this branch returns `isDeprecated: true` with the reason on `Workspace.metadataVersion`. - CI's pending-codegen check covers the regenerated surfaces (`data`/`metadata`/`admin` configs and `twenty-client-sdk:generate-metadata-client`). <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23189?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. --> |
||
|
|
d1c70ab0bf |
Fix dashboard record table widget aggregate persistence (#23008)
## Summary - Update dashboard record-table widget aggregate changes to write into the widget draft while page layout edit mode is active. - Include `aggregateOperation` when saving record-table widget view fields through `upsertViewWidget`. - Persist aggregate operations server-side for widget view-field create, update, and clear flows. - Add frontend utility tests and backend integration coverage for widget aggregate create/update/clear behavior. Fixes #22934. ## Why Dashboard record-table widgets use their own draft view state while a page layout is being edited. The aggregate footer path was resolving fields through the normal current-view flow and then trying to persist immediately, which can miss widget draft fields and fail before the save flow runs. This change keeps aggregate edits in the widget draft during page layout editing, then saves the aggregate operation with the rest of the widget view configuration. ## Validation - `npx nx lint twenty-front` - `npx nx typecheck twenty-front` - `npx nx test twenty-front --configuration=ci` - `npx nx build twenty-front` - `npx nx build twenty-server` - `npx nx lint twenty-server --configuration=ci` - `npx nx typecheck twenty-server` - `npx nx test twenty-server --configuration=ci` - `npx nx jest --config ./jest-integration.config.ts --logHeapUsage --runTestsByPath test/integration/metadata/suites/view/upsert-view-widget.integration-spec.ts` - `git diff --check` Disclosure: I used AI-assisted coding tools while preparing this PR. I reviewed the changes myself, tested them, and take responsibility for the implementation and any follow-up revisions needed. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23008?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. --> |
||
|
|
32041ce2e0 |
fix(website): restore condensed marketplace list card (#23161)
## Summary The condensed marketplace list-card design (sans-serif partner name, description excerpt, a single scope line, and a "View profile →" text CTA) was the intended glowup card, shipped by #22471/#22402. During the #23016 resync, the `PartnerCard.tsx` merge conflict was resolved in favor of main's rich card, so the condensed design never actually landed on `main`. This PR restores the condensed design, ported onto main's current data layer and helpers — none of #23016's profile/case-study/matching work is touched. ## What changed - `PartnerCard.tsx` — rewritten to render the condensed layout (sans-serif `PartnerName`, `CardIntro` description excerpt, `CardFoot` with a single `ScopeLine` and a mono `CardCta` "View profile →" text link, no chip rows, no money row, no LinkedIn icon). Reuses main's data layer and helpers directly: `richTextExcerpt`, `resolvePartnerScopeCards`, `PartnerAvatar`, `titleCaseFallback`, and the shared `CardFrame` shell (same entrance-animation/hover idiom already used by `MarketplaceMatchCard`). No new helper files were needed — everything the condensed design requires already exists on `main`. - `PartnerChipRow.tsx` — deleted (zero remaining importers after the port; grep confirmed only self-reference). - `PartnerMoneyRow.tsx` — deleted (zero remaining importers after the port; grep confirmed only self-reference). Kept untouched: `fetch-live-marketplace-partners.ts`, `marketplace-partner.ts`, `get-marketplace-partners.ts`, `marketplace-partners-source.ts`, all `PartnerProfile*`/case-study/matching files, `MarketplaceGrid.tsx` (already has the `repeat(N, minmax(0, 1fr))` grid columns on main — no change needed), and the shared label helpers `PARTNER_SCOPE_LABELS`/`SERVED_GEO_LABELS`/`SPOKEN_LANGUAGE_LABELS` (still used by `FilterBar.tsx`/`PartnerReachFacts.tsx`). Website-only change — no partners-app version bump, no `.po` catalog changes. ## Test plan - [x] `npx nx typecheck twenty-website` — clean - [x] `npx oxlint -c .oxlintrc.json .` (twenty-website) — 0 warnings, 0 errors - [x] `npx oxfmt --check .` (twenty-website) — clean - [x] `npx jest partners-marketplace` — 12 suites / 54 tests passing - [x] Verified locally against the running dev server (`/partners/list`): 16 partner cards render with the condensed layout (sans-serif name, excerpt, single scope line, "View profile →"), no chip rows/money rows/LinkedIn icons, real partner data renders correctly (e.g. 01GROWTH, Inc) <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23161?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. --> |
||
|
|
e2ea82170c |
i18n - translations (#23191)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23191?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> |
||
|
|
a3f4acadb1 |
Add workspace and server level stop commands for applications (#23183)
## Context When an installed application misbehaves (e.g. a logic function loop DDoSing the server or the database), we currently have no targeted way to shut it down in production: the only kill switch is `LOGIC_FUNCTION_TYPE=DISABLED`, which disables logic functions for the whole instance. This PR adds an emergency stop mechanism at two levels: - **Workspace level**: stop one installed application in its workspace. - **Server level**: stop every application installed from an `applicationRegistration`, across all workspaces. ## How it works **New nullable `stoppedAt` columns** on `core.application` and `core.applicationRegistration` (fast instance command `2.24.0`, with `up`/`down` and `@WasIntroducedInUpgrade` decorators on the entities). **Enforcement in a single choke point**: `LogicFunctionExecutorService.execute()` is the funnel behind every execution path (public route triggers, server route triggers, cron triggers, database event triggers, workflow actions, agent tool calls, manual GraphQL execution, install hooks). A new `assertApplicationNotStopped` guard runs right after the flat entities are resolved and throws `LOGIC_FUNCTION_DISABLED` (already mapped to a 403 on route triggers and handled by the GraphQL exception handler) when: - `flatApplication.stoppedAt` is set (workspace-level stop, read from the cached flat application maps: zero extra runtime cost), or - the linked registration is stopped (one indexed PK lookup, same pattern as the existing per-execution server-variable query). **Propagation**: the workspace-level stop invalidates and recomputes `flatApplicationMaps` for the workspace, so all server instances pick the flag up within the local cache TTL (100ms). The registration-level flag is read live, so it is effective immediately. ## Ops commands ```bash # Workspace level yarn command:prod application:stop -a <application-id> yarn command:prod application:start -a <application-id> # Server level (all applications of the registration, all workspaces) yarn command:prod application-registration:stop -r <application-registration-id> yarn command:prod application-registration:start -r <application-registration-id> ``` Each command logs what was stopped/started and, for registrations, how many installed applications are affected. ## Notes - Stopped executions fail fast at the guard, so queued trigger jobs (cron/db-event) burn a negligible amount of work while stopped. - The two flags are independent: lifting a registration-level stop does not clear workspace-level stops that were set individually, and vice versa. - Unit tests added for `ApplicationStopService`. --- _Generated by [Claude Code](https://claude.ai/code/session_01CjEnKUACn89aSgK1wEMH2d)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23183?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. --> |
||
|
|
a65da48591 |
feat(server): per-worker queue filtering via env vars (#23181)
## Context Follow-up to #23134. Goal: stop a heavy/long-running queue from saturating every worker pod and blocking the whole job pipeline, by letting each BullMQ worker decide **which queues it consumes** from env vars. > Note: an earlier revision of this PR also added a dedicated `application-queue` (concurrency 1). Per review, that was dropped — application install/upgrade/backfill jobs stay on `workspaceQueue`. This PR now contains **only** the worker queue-filtering mechanism. ## What changed - The queue-worker explorer (`MessageQueueExplorer`, which only runs in the `queue-worker` process) now reads two env vars before creating workers: - `WORKER_ENABLED_QUEUES` — comma-separated allowlist of queues this worker processes (empty = all). - `WORKER_EXCLUDED_QUEUES` — comma-separated denylist, applied after the allowlist. - Workers are only created for queues that pass the filter; filtered-out queues are logged and skipped. Unknown queue names are logged as warnings. - Both vars are read directly from `process.env` (not the DB-backed config-variable system), since they're worker-bootstrap settings. - Pure decision logic + env parsing extracted to `shouldCreateWorkerForQueue` / `parseQueueListFromEnv` utils with unit tests. Queue **clients** are still registered in every process, so jobs can be enqueued from anywhere — only the **consumer** side is gated. ## Usage Isolate `workspace-queue` (where the application jobs run) onto dedicated pods: - General worker pods: `WORKER_EXCLUDED_QUEUES=workspace-queue` - Dedicated worker pods: `WORKER_ENABLED_QUEUES=workspace-queue` ## Tests - `should-create-worker-for-queue.util.spec.ts`: allowlist / denylist / precedence + env parsing. - `typecheck` + `oxlint --type-aware` + `oxfmt --check` clean on the diff. ## Companion - twentyhq/twenty-infra#805 wires `WORKER_ENABLED_QUEUES` / `WORKER_EXCLUDED_QUEUES` to the worker pods. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_014XeN6wVSbMWnFu8jeLaSXk --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
3ae09cd581 |
i18n - translations (#23187)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
987995636f |
Sync metadata store after view child entity mutations server response (#23150)
## Summary - View persist hooks (`usePerformView(Sort|Field|Filter|Group|FieldGroup|FilterGroup|)APIPersist`) now write successful mutation results back to the metadata store (`addToDraft` / `updateInDraft` / `removeFromDraft` + `applyChanges`), following the existing pattern from `usePerformViewAPIUpdate`. - Previously the store only updated via SSE, so until that landed, save flows diffed against stale view data and re-sent creates with the same id — failing server-side with a duplicate key error. Fixes TWENTY-SERVER-HQM |
||
|
|
f52643d170 |
i18n - docs translations (#23186)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
0108a34765 |
Rekey metadata caches when flat map hashes change (#23164)
## Context
The `/metadata` GraphQL response cache (`ObjectMetadataItems`,
`FindAllViews`) and the workspace SDL cache were keyed on
`workspace.metadataVersion`, an integer bumped on every object/field
migration. That mechanism is legacy (the migration runner literally
calls it `getLegacyCacheInvalidationPromises`): the data plane already
moved to `WorkspaceCacheService`, which versions each flat entity map
with its own hash minted on invalidation.
Version keying had two concrete costs: the version bump was the only
proactive invalidation for `ObjectMetadataItems`, and keeping
`FindAllViews` fresh required `flushGraphQLOperation`, a full Redis
keyspace SCAN on every relevant migration. It is also the main blocker
for deprecating `metadataVersion` entirely.
This PR re-keys both caches on the flat-map hashes instead.
## What changed
**Response cache** (`use-cached-metadata.ts`): the key is now
`{operation}:{workspaceId}:{combinedDependencyHash}[:{userWorkspaceId}]:{locale}:{queryHash}`.
Each cached operation declares which flat maps its resolvers read
(`metadata-graphql-operations-to-cache.constant.ts`) plus a scope:
`ObjectMetadataItems` stays workspace-shared, `FindAllViews` is per-user
because unlisted-view visibility depends on the caller. When any
declared map changes, its hash rotates and the key rotates with it; no
flush needed. The key is resolved once per request and reused in
`onResponse`, so a rotation mid-request can never cache a response under
a fresher key than the data it was built from. If hash resolution fails,
the request is served uncached (Sentry-captured).
This also fixes three pre-existing key soundness gaps: `FindAllViews`
ignored locale although view names are translated server-side, the query
hash ignored GraphQL variables (`$viewTypes`), and mid-request version
rotation could re-key between request and response.
**SDL cache** (`workspace-graphql-schema-sdl.service.ts`): keyed on the
combined hash of the four maps the schema is generated from, taken from
the same `getOrRecomputeWithHashes` call that returns the data, so key
and content cannot skew. The `metadataVersion` read/seed block there is
gone; the Redis seed moved to `middleware.service.ts` so the
`X-Schema-Version` "refresh the page" check keeps working after the
Redis key's TTL expires.
**`WorkspaceCacheService`**: the internal pipeline now threads `{data,
hashes}` through every stage (local hit, hash validation, Redis fetch,
recompute) and the memoizer stores the pair, so returned hashes are
always consistent with returned data. New public
`getOrRecomputeWithHashes` and `getOrRecomputeCombinedHash`
(hashes-first: one MGET of the small `:hash` keys, full pipeline only
for missing ones, so cold pods never pull map payloads just to build a
key).
**Atomic pair writes** (`cache-storage.service.ts`): `mset` on the Redis
driver now delegates to the store's own `mset` (a MULTI of `SET ... PX`,
or native `MSET`), grouped by TTL. Previously it was a `Promise.all` of
independent SETs, so two concurrent recomputes could interleave and
leave one recompute's `:data` next to the other's `:hash`; with
hash-keyed caches that torn pair could persist a stale response under a
live key. `CoreEntityCacheService` writes through the same method and is
fixed for free.
**Cleanup**: `flush()` lost its `metadataVersion` parameter (always
pattern-flush per key on workspace deletion),
`METADATA_VERSIONED_WORKSPACE_CACHE_KEY` became
`HASH_KEYED_WORKSPACE_CACHE_KEYS` with the `MetadataVersion` key
relocated to `WORKSPACE_CACHE_KEYS` and the dead `ORMEntitySchemas`
entry removed.
## Deliberately unchanged
- `incrementMetadataVersion` and all its callers stay: the version still
feeds the `X-Schema-Version` check and the pinned upgrade commands.
Deprecating the column is a later stage.
- The runner's `FindAllViews` pattern-flush is kept for exactly one
release: view-only migrations never bump `metadataVersion`, so old pods
in a rolling deploy have no other invalidation signal for their
version-keyed entries. It gets deleted next release, which removes the
SCAN entirely.
- Old-shape cache entries are not migrated; they expire via the 7-day
TTL.
## Known limitations (follow-ups, not regressions)
- The plugin reads dependency hashes Redis-fresh while resolvers can
serve up to 10s-old memoized data, so a request landing right after a
migration can cache a pre-rotation response under the new key. Same
shape existed under `metadataVersion`; closing it needs request-scoped
snapshot plumbing.
- Concurrent recomputes are last-writer-wins (lost update). Fencing with
a conditional write is a follow-up.
## Validation
- Unit: response-cache plugin behavior (scope, key stash, serve-uncached
on failure, prototype-name guard), atomic `mset` batching, existing
`WorkspaceCacheService` spec passing unchanged.
- Integration: a new drift-guard spec runs the real
`ObjectMetadataItems`/`FindAllViews` operations with full frontend
selection sets against the in-process app, spies on
`WorkspaceCacheService`, and fails if resolvers read a flat map missing
from the declared dependency lists, so the constant cannot silently
drift.
- Manual against a live server: creating a field rotates the field-map
hash and the very next `ObjectMetadataItems` response contains it (hash
rotation is now its only invalidation path); warm hits are ~5ms; SDL
entries appear under hash-shaped keys via introspection.
## Suggested reading order
1. `workspace-cache.service.ts`, `workspace-cache-key.type.ts`,
`combine-cache-hashes.util.ts` (the `{data, hashes}` pipeline)
2. `use-cached-metadata.ts`,
`metadata-graphql-operations-to-cache.constant.ts`,
`metadata.module-factory.ts` (response cache)
3. `workspace-graphql-schema-sdl.service.ts`,
`workspace-cache-storage.service.ts` (SDL cache and renames)
4. `middleware.service.ts` (metadata version seed relocation)
5. `cache-storage.service.ts` (atomic writes)
6. Tests
|
||
|
|
f67e9c6b05 |
fix(ai-chat): disable Responses API storage for Azure models to stop "Item with id ... not found" stream failures (#23182)
## Problem AI chat and workflow-agent turns on Azure-routed reasoning models (e.g. `azure-foundry-us/gpt-5.5`) intermittently die mid-answer with: > Failed to get response — `400 Item with id 'rs_...' not found` ## Root cause The OpenAI Responses API is stateful: each output item gets a server-side id (`rs_...` reasoning, `fc_...` function call). With `store: true` (the API default), the Vercel AI SDK replays prior assistant reasoning as `item_reference` entries that the provider must resolve from its own storage. For reasoning models the chain-of-thought is never returned in plaintext — it lives only as a stored referenced item, or as `encrypted_content` requested via `include: ['reasoning.encrypted_content']` (which the SDK only auto-adds when `store: false`). PR #20888 (June 22) switched to the safe `store: false` mode, but only for `AI_SDK_OPENAI`. `AI_SDK_AZURE` fell through to `default` and got no options, so every Azure request ran in stored-reference mode. Azure's item storage resolves those references unreliably (a server-side race — the July 21 failure could not find the exact `rs_...` id Azure itself had streamed seconds earlier in the same turn), and the failure is fatal to the stream. Retries replay the same persisted references, so they can fail again. ## Fix Add an `AI_SDK_AZURE` case in `getCallLevelProviderOptions` that passes `azure: { store: false }`. Both AI chat and workflow agents go through this helper, so both paths are covered. The provider-options key for `@ai-sdk/azure` is `azure` (its responses model is registered as `azure.responses`); I verified the `@ai-sdk/openai@3.0.54` copy nested inside `@ai-sdk/azure` has the same guards as the root `3.0.71`. With `store: false` the SDK: - auto-requests `include: ['reasoning.encrypted_content']` for reasoning models (the `gpt-5.5` deployment name passes reasoning detection), - replays reasoning as self-contained encrypted items instead of server-side references, - drops any stale unencrypted reasoning parts instead of referencing them. ### Transition safety Existing threads whose persisted reasoning has `reasoningEncryptedContent: null` simply have those parts skipped on replay. I checked prod logs for the related pairing-validation error ("was provided without its required...") and found zero occurrences since OpenAI-direct made this same switch a month ago, so the transition is safe. ## Testing - Added two Azure cases to `provider-options.util.spec.ts` — all 9 pass. - oxlint (type-aware) and oxfmt clean on the changed files. ## Post-deploy validation Inverse of the evidence: new Azure reasoning parts should persist with non-null `reasoningEncryptedContent`, and the Loki query below should go quiet. fixes : https://discord.com/channels/1130383047699738754/1526873169124659230 |
||
|
|
c54ad3c0b9 |
feat(ai-instrumentation): fix AI histogram buckets & add tool-call duration instrumentation (#23173)
## What & why
Two related observability fixes for AI chat / agent / MCP metrics.
### 1. Widen histogram buckets (`widden-ai-histo`)
Latency percentiles for AI chat pinned at exactly **10s** on the
dashboard — not because anything times out, but because the histogram
buckets top out at 10,000.
`recordHistogram` created histograms without explicit bucket boundaries,
so the OTel SDK applied its defaults: `[0, 5, 10, 25, 50, 75, 100, 250,
500, 750, 1000, 2500, 5000, 7500, 10000]`. These were designed for small
generic values; interpreted as **ms**, the last finite bucket is exactly
10s. Every observation above 10s falls into the `+Inf` overflow bucket,
and quantile estimation clamps to the highest finite bound — so any
percentile that lands in the overflow draws a flat line at 10s.
Reality (gpt-5.5, last 30 days): 64% of turns exceed 10s (so even p50
pins at 10s), mean turn latency ~38s, max ~29min. The same 10k cap
affects the token-unit `tool-output-tokens` histograms.
**Changes:**
- `recordHistogram` now accepts an optional `bucketBoundaries`, applied
per-instrument via `advice.explicitBucketBoundaries` (supported in
`@opentelemetry/api@1.9.1`). Colocated with the metric, no
instrument-setup changes.
- Added bucket-boundary constants:
- `AI_LATENCY_MS_BUCKET_BOUNDARIES` — `[250, 500, 1000, 2500, 5000,
10000, 20000, 30000, 60000, 120000, 300000, 600000]` (250ms → 10min)
- `TOOL_OUTPUT_TOKENS_BUCKET_BOUNDARIES` — `[100, 250, 500, 1000, 2500,
5000, 10000, 25000, 50000, 100000, 250000, 500000]` (100 → 500k tokens)
- Wired boundaries into `ai-chat/turn-latency-ms`,
`ai-chat/step-latency-ms`, `ai-chat/ttft-ms` (latency) and
`ai-chat/tool-output-tokens`, `workflow-agent/tool-output-tokens`,
`mcp/tool-output-tokens` (tokens).
### 2. Instrument tool-call duration (`add-tool-duration-monitoring`)
Previously we tracked tool success/failure counts and output tokens, but
not how long each tool call took. Added a `*/tool-execution-duration-ms`
histogram for each execution path.
**Changes:**
- New metric keys: `ai-chat/tool-execution-duration-ms`,
`workflow-agent/tool-execution-duration-ms`,
`mcp/tool-execution-duration-ms`.
- New constant `TOOL_EXECUTION_DURATION_MS_BUCKET_BOUNDARIES` — `[25,
50, 100, 250, 500, 1000, 2500, 5000, 10000, 30000, 60000, 120000]` (25ms
→ 2min).
- **AI chat / agent node**: use the AI SDK's
`experimental_onToolCallFinish` callback (`ai@6.0.97`), which reports an
exact `durationMs` measured around the tool's `execute()`, plus
`toolCall.toolName` and `success`. Attributes `{ model, tool }`.
- **MCP**: measured directly around `tool.execute` with
`performance.now()`, recorded on both success and failure paths.
Attributes `{ tool }`.
## Notes
- Backward-compatible: metrics without `bucketBoundaries` (e.g.
`job/latency-ms`, `sdk-client-generation/duration-ms`) keep OTel
defaults.
- **Historical data stays clamped** — only writes after deploy use the
new buckets, so percentile panels will show a step change at deploy
time. For truth on existing data, use a mean panel (Sum/Count is exact)
or Sentry trace span durations.
- Provider-executed tools (e.g. native web search) run inside the
provider, so they don't emit a local duration — same limitation as the
existing tool counters.
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/23173?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. -->
|
||
|
|
0326e32b9b |
i18n - translations (#23184)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23184?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> |
||
|
|
04d1c2035c |
feat(connections): run a logic function on connection provider connect (#23167)
## What Adds an optional `onConnectLogicFunctionUniversalIdentifier` field to the connection provider manifest. When set, the referenced logic function is dispatched right after an OAuth connection is successfully established for that provider. This gives apps a first-class "on connect" hook — e.g. the Slack app can resolve the workspace's `team_id` via `auth.test` and claim the `team_id -> workspaceId` mapping in the SERVER key-value store immediately on connect, instead of racing against later events. Follow-up to the app key-value store PR (#23089). ## How - **twenty-shared**: add `onConnectLogicFunctionUniversalIdentifier` to `ConnectionProviderManifest`. - **twenty-sdk**: expose the field in `defineConnectionProvider` and validate it is a UUID `universalIdentifier`. - **twenty-server**: - add a nullable `onConnectLogicFunctionUniversalIdentifier` column to `ConnectionProviderEntity` (+ fast instance command / migration). - map the field through the <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23167?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. --> |
||
|
|
50ec77daf8 |
i18n - docs translations (#23179)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23179?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> |
||
|
|
eddb56d26e |
Fix uneven padding in the onboarding trust badge (#23174)
The "+10k" trust badge on the onboarding import contacts step had 2px left padding vs 10px right, so the PwC logo hugged the left edge. The badge now has equal padding on both sides so its content is centered. <img width="347" height="168" alt="image" src="https://github.com/user-attachments/assets/05e18541-827e-4ace-99ce-7e8c4201cd1b" /> |
||
|
|
de3889e04a |
fix: use aria-current instead of aria-selected on navigation drawer items (#23160)
## Fixes Closes #23129 ## Problem Navigation drawer items render an `<a>` (or React Router `Link`) with `aria-selected="true"`. `aria-selected` is only valid on roles such as `option`, `tab`, `row`, or `gridcell`, not on links, so axe flags WCAG 4.1.2 (aria-allowed-attr): "ARIA attribute is not allowed: aria-selected=true". ## Fix Use `aria-current="page"` instead. It is the WAI-ARIA recommended way to mark the current item in a navigation, it is a global attribute valid on any role (link, button, or div), and it correctly communicates the active page to screen readers. `packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerItem.tsx`: - aria-selected={active} + aria-current={active ? 'page' : undefined} <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23160?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. --> |
||
|
|
9043ab3091 |
Upgrade to 1.0.9 PDL app (#23172)
<!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23172?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. --> |
||
|
|
e0debf87a7 |
fix(call-recorder): listen proper updated fileds event (#23135)
## Context Around meeting-end peaks (~6pm), Recall/Svix delivers event bursts for every recorded call across the 700+ workspaces the app is installed on. Each delivery was processed synchronously in the API request path, and internal failures surfaced to Svix as non-2xx, so it redelivered — a self-feeding storm of 500s and latency that only stopped when the webhook endpoint was disabled. ## What changed With #23134, server-route dispatch defaults to **queued** server-side: the API acks Svix with a 202 right after signature verification, `process-recall-webhook` runs on the worker queue, and failed runs retry there (resolver `retryLimit`, default 3). The resolver needs no change at all — `recall-webhook.ts` is back to main, and no SDK update is required. Remaining app changes: - `schedule-recall-bot-on-call-recording-update` declares `updatedFields` (the pending-transition fields) on its `callRecording.updated` trigger, so the server drops the app's own scheduling-progress and artifact writes **before** spawning a full execution instead of executing and returning "skipped". The in-handler check stays as a fallback. - Version bumped to 1.5.0. - Code comments introduced by earlier revisions of this PR removed per review. ## Tests - New test pins the trigger's `updatedFields` declaration. - `yarn test:unit` (507 tests), `yarn lint`, `yarn typecheck` all green. ## Notes - The `call-recorder (dockerhub-latest)` CI leg fails because main already requires `twenty >= 2.23.0` while the latest published image is 2.22.0 — pre-existing, clears when 2.23.0 images publish. - The 250s `import-call-recording-artifacts` route still runs in an API request slot behind the fire-and-forget own-route POST; moving it fully off the request path is a follow-up. |
||
|
|
f59bda1dbf |
feat(server): queued-only server-route dispatch (#23134)
## Context
Follow-up to the incident where 500s and latency spiked around 6pm until
the Recall webhook was disabled. Server routes
(`/webhooks/server/:resolverUid`) ran the resolver **and** the target
logic function synchronously inside the API request, so any handler
throw became a 500 and any slowdown past Svix's delivery timeout marked
the delivery failed — Svix redelivered, feeding load back into the API
in a self-sustaining storm.
## What changed
- Every server-route request acks with **202 `{ queued: true }`** as
soon as the resolver returns; the target runs on `logicFunctionQueue`.
Signature verification stays synchronous in the resolver and still
rejects with a non-2xx. External senders never observe target latency or
failures.
- The resolver contract is unchanged from main: `{ workspaceId,
targetLogicFunctionUniversalIdentifier, payload? }`.
- The target lookup still happens synchronously before enqueueing,
scoped to the resolver's application registration, so unknown targets
404 as before.
- Endpoints whose caller must read the response body (challenge
handshakes, Slack commands) should use `httpRouteTriggerSettings`
routes.
Final diff is 4 files: the server-route service, its spec, the
integration spec, and the docs page. Trigger jobs, fan-out, message
queue, shared types, and SDK are all untouched.
## Tests
- `server-route-trigger.service.spec.ts`: 202 ack + enqueue,
unknown-target 404 without enqueue, resolver auth/contract/error
mapping.
- Integration: `server-route-trigger-authorization.integration-spec.ts`
asserts the 202 queued ack (run locally against a seeded DB, green).
- `typecheck` + `lint:diff-with-main` + `oxfmt` clean.
## Notes
- **Breaking for existing server-route resolvers**: responses are always
202; the target's return value no longer reaches the caller. Existing
resolvers returning response bodies must move those endpoints to
`httpRouteTriggerSettings`.
- A queued target's handler failure is recorded in execution logs but
not retried (same as other queue-executed functions today); retry
semantics are deliberately out of scope here.
- Follow-up candidates: retry-on-failure semantics for queued
executions, `addBulk` for single-round-trip fan-out, declarative
signature verification to take resolver code out of the request path,
moving the call-recorder 250s artifacts import off the API request path.
- Companion PR #23135 (call-recorder): no app change needed for dispatch
— queued dispatch applies by default.
|
||
|
|
271f9f833b |
i18n - translations (#23171)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23171?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> |
||
|
|
0c964aee10 |
Fix blank pages and add error recovery in onboarding (#23069)
Onboarding showed a blank page in two places: before the plan/payment step, and briefly after the welcome animation. `ChooseYourPlan` returned `null` while its `ListPlans` query loaded, leaving the step empty during the crossfade. It now renders the step loader, and the plans query is warmed from an earlier onboarding step so the content is usually already there. The post-completion redirect lands transiently on `/`, whose route element was `<></>`, so the welcome animation could reveal an empty page. It now renders a skeleton, and the `null` Suspense fallbacks on payment-success and book-call are replaced too. A failed `ListPlans` query was worse than a blank frame: `PLAN_REQUIRED` redirects every route back to itself, so the user was locked out of the product with no way to retry. That step and the billing settings page now show a retryable error state. One related fix found on the way: `BlankLayout` had no error boundary, so a render-time throw anywhere in sign-in or onboarding took down the whole app. `DefaultLayout` already had one. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23069?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. --> |
||
|
|
7e79b19f73 |
fix: bump vitest family 4.1.8 -> 4.1.10 (Dependabot) (#23166)
## Summary Bumps the **vitest family 4.1.8 -> 4.1.10** to clear the **critical** Dependabot alert [1800](https://github.com/twentyhq/twenty/security/dependabot/1800): GHSA-p63j-vcc4-9vmv, @vitest/browser Browser Mode provider commands bypass the file-access permission gate (no CVE yet; fixed 4.1.10). `@vitest/browser` is exact-pinned by `@vitest/browser-playwright` (vitest family version-locks), so the fix is a recursive `yarn up` on the caret parents (`vitest` and `@vitest/browser-playwright`, both declared `^4.1.0` by twenty-ui, twenty-front-component-renderer, twenty-client-sdk). The whole family (browser, expect, mocker, pretty-format, etc.) moves together to 4.1.10. No resolution, no `package.json` change. ## Verification - `yarn install --immutable` passes. - Diff is `yarn.lock` only; `@vitest/browser` resolves to 4.1.10, no 4.1.8 remains. - 4.1.10 published 2026-07-06, clears the 3-day npm age gate. - Dev-scope tooling (vitest test runner), patch-level bump within 4.1.x. |
||
|
|
16e7db8577 |
PDL react 19 (#23169)
Started to face in version 1.07 on staging 2.23.0 ``` Failed to load front component: Cannot read properties of undefined (reading 'ReactCurrentBatchConfig') ``` |
||
|
|
ec859faa6a |
i18n - translations (#23170)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23170?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> |
||
|
|
f5ab8a236c |
fix: add aria-label to record table selection checkboxes (#23147)
## Fixes Closes #23130 ## Problem On record tables, the select-all header checkbox and each row's selection checkbox render as `role="checkbox"` with no accessible name, failing WCAG 4.1.2 (aria-toggle-field-name). Screen-reader users cannot tell what the checkbox selects. ## Fix The shared `Checkbox` component already accepts and forwards `aria-label`, but the record-table callers weren't passing one. Added translated labels: - Row checkbox: `aria-label={t\`Select row\`}` in `RecordTableCellCheckbox.tsx` - Header checkbox: `aria-label={t\`Select all rows\`}` in `RecordTableHeaderCheckboxColumn.tsx` Both use `const { t } = useLingui()` from `@lingui/react/macro`, following the existing i18n convention in this module. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23147?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. --> |
||
|
|
b4aa323889 |
Record Board & Record Calendar Drag Drop dnd-kit rewrite. (#23071)
Closes: #23070 Record Board: https://github.com/user-attachments/assets/26df63e2-fbbd-4339-88b3-eed73af283b4 Record Calendar: https://github.com/user-attachments/assets/cb209545-695e-44a1-8dfb-a92b3c8650a6 ### Technical Inputs - Each column is a `Droppable`. This differs from other implementations (Record table header and Record board header), where only the gap between headers is `Droppable`. - Individual cards are `Sortable`. - Uses `DragOverlay` to display a cloned version of the dragged card along with a `+N` chip when dragging multiple cards. - Updated `DragDropColumnDropTarget` to handle `vertical` and `horizontal` orientation of drop target. - File name changes: - `DragDropColumnDropTarget` → `DragDropItemDropTarget` - `DragDropColumnDroppableSlot` → `DragDropItemDroppableSlot` - `DragDropColumnSortableCell` → `DragDropItemSortableCell` - `DragDropColumnSortableHandle` → `DragDropItemSortableHandle` - `DragDropColumnDndContext` → `DragDropItemDndContext` - `DragDropColumnData` → `DragDropItemData` <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23071?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> |
||
|
|
3223e3c58f |
fix - fix light overscroll flash in dark mode (#23153)
Before https://github.com/user-attachments/assets/7dc35369-0c1e-4ca8-9d30-12738c7e7d5b After https://github.com/user-attachments/assets/b8dd49c2-9ad3-49d0-a107-6ddfdf1ba531 <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23153?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. --> |
||
|
|
0489d39f2b |
fix(messaging): stop group and bulk emails from creating contacts (#23137)
Group addresses were only checked against the message sender, so anything arriving as reply-to or cc slipped through and became a contact. Now checked per participant at contact creation. The group word list can't keep up with real senders (posts-recap@, showinfo@, follow-suggestions@), so bulk-mail headers back it up: `List-Unsubscribe, List-Id, Precedence, Auto-Submitted` (Industry standard headers) <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23137?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. --> |
||
|
|
8d943e1f68 |
Bump to alpha 2 all published apps (#23165)
Related https://github.com/twentyhq/twenty/pull/23155 <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23165?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. --> |
||
|
|
3e97b355b4 |
fix: bump tar 7.5.16 -> 7.5.20 (Dependabot) (#23159)
## Summary Bumps **tar 7.5.16 -> 7.5.20** on the root `yarn.lock` to clear **4 Dependabot alerts** from this week's node-tar advisory chain, including the critical one. | Severity | Advisory | Vulnerable | Fixed | Alert | |---|---|---|---|---| | critical | GHSA-23hp-3jrh-7fpw | <= 7.5.18 | 7.5.19 | [1772](https://github.com/twentyhq/twenty/security/dependabot/1772) | | high | GHSA-8x88-c5mf-7j5w | <= 7.5.17 | 7.5.18 | [1771](https://github.com/twentyhq/twenty/security/dependabot/1771) | | medium | GHSA-w8wr-v893-vjvp | <= 7.5.17 | 7.5.18 | [1773](https://github.com/twentyhq/twenty/security/dependabot/1773) | | medium | GHSA-gvwx-54wh-qm9j | <= 7.5.16 | 7.5.17 | [1770](https://github.com/twentyhq/twenty/security/dependabot/1770) | The root lockfile has a single tar entry with all-caret consumer ranges (`^7.4.0` ... `^7.5.16`), so a recursive `yarn up -R tar` lifts it to the latest patch (7.5.20, published 2026-07-12, clears the 3-day age gate). The existing scoped tar resolutions (`npm:^7.5.16` for the @electron/rebuild toolchain and @mintlify/previewing) are caret ranges and permit it unchanged, so no `package.json` edit. Note: the remaining 52 tar alerts live in the twenty-apps and seed-dependencies mini-lockfiles and will be handled separately. ## Verification - `yarn install --immutable` passes. - Diff is `yarn.lock` only; single tar entry now 7.5.20, nothing below remains. |
||
|
|
41c32a04e4 |
i18n - docs translations (#23162)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
25f28ee299 |
Fix TWENTY-SERVER-60Y: register permissions exception filter globally (#23104)
## Context Sentry [TWENTY-SERVER-60Y](https://twenty-v7.sentry.io/issues/6633503406) ("Permission Denied: Entity performing the request does not have permission") has ~12.9k occurrences / 151 users. It groups by the shared throw site `settings-permission.guard.ts:57`, so it's a **catch-all bucket** for settings-permission denials across many resolvers, not a single operation. Sampled events include `findOneApplication` (app runtimes reading their own `applicationVariables`), `uploadFilesFieldFileByUniversalIdentifier`, `UpdatePageLayoutWithTabsAndWidgets`, `CreateFileUpload`, etc. ## Root cause Settings-permission denials are only converted to a client-appropriate `FORBIDDEN` when a resolver manually attaches `PermissionsGraphqlApiExceptionFilter`. **28** GraphQL resolvers do; **~35** guarded resolvers do not. On those, the guard-thrown `PermissionsException` (a `CustomException`, not a `BaseGraphQLError`) falls through to the Yoga error hook, is serialized as `INTERNAL_SERVER_ERROR`, and `shouldCaptureException` reports it to Sentry as a 500-class error. REST is not affected: every `SettingsPermissionGuard` controller already carries `PermissionsRestApiExceptionFilter` (15/15). ## Fix Register `PermissionsGraphqlApiExceptionFilter` globally via `APP_FILTER` in the core and metadata engine modules, mirroring the existing global GraphQL filters `BillingGraphqlApiExceptionFilter` and `FlatEntityMapsGraphqlApiExceptionFilter`. The filter is guarded on the GraphQL context (`host.getType()`), so REST keeps its existing per-controller filter untouched and no request-scoped dependency is pulled into a global provider. Every settings-guarded resolver now returns `FORBIDDEN` for denials, which is both the correct client error code and excluded from Sentry. Existing per-resolver `@UseFilters(PermissionsGraphqlApiExceptionFilter)` entries remain valid (handler-scoped takes precedence, identical result); collapsing them into the global registration is a possible follow-up. ## Test Added a case to `granular-settings-permissions.integration-spec.ts`: a member without the `APPLICATIONS` flag calling `findOneApplication{applicationVariables{key value}}` (the exact Sentry query, and a resolver with **no** resolver-scoped filter) must receive `FORBIDDEN`, exercising the global filter. Verified against the local test DB: - With the global filter: passes (`FORBIDDEN`). - Without it: fails with `INTERNAL_SERVER_ERROR`, reproducing the leak. - The existing roles / workspace-members / api-keys denial tests (per-resolver filters) still pass, confirming no precedence conflict and no boot issue from dual `APP_FILTER` registration. Fixes TWENTY-SERVER-60Y |
||
|
|
623aac5a56 |
Show agent names on real estate dashboard charts (#23146)
Follow-up to the real estate demo app: the Agency Overview dashboard's "Listings by agent" and "Showings by agent" bar charts grouped by the agent relation, which rendered the agent's UUID on the axis. Adding `primaryAxisGroupBySubFieldName: 'name.firstName'` groups by the agent's first name instead, so the charts show agent names (Emma, Lucas, Chloe, Louis). <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23146?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. --> |
||
|
|
3ee8b72aa3 |
twenty-sdk env var to disable prov check (#23155)
<!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23155?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. --> |
||
|
|
9d4a564af4 |
i18n - translations (#23157)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23157?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> |
||
|
|
4c4a154d31 |
key-value storage for applications (#23089)
## What
Key-value storage for applications, as proposed in
twentyhq/core-team-issues#2391 — built on the existing `keyValuePair`
entity:
- Nullable `applicationId` relation on `keyValuePair` + a new
`APPLICATION_VARIABLE` type (fast instance command included)
- GraphQL CRUD on the metadata schema (`appKeyValue`, `setAppKeyValue`,
`deleteAppKeyValue`), requiring an `APPLICATION_ACCESS` token —
`applicationId` always comes from the token, never from arguments, so
apps can't touch each other's entries
- `kv.get` / `kv.set` / `kv.delete` helpers in
`twenty-sdk/logic-function`
## Scopes
- **`INSTALL`** (default): entries are private to one workspace install;
arbitrary JSON values
- **`GLOBAL`**: entries are shared across every install of the app, with
claim semantics — the value is always the claiming `workspaceId` and
only that workspace can overwrite or delete the key (guarded writes,
race-safe via insert-if-absent)
Since `applicationId` identifies an install (one row per workspace),
GLOBAL entries are stored under the registration owner workspace's
install so all installs of the same app share one namespace.
The GLOBAL scope is what enables cross-workspace webhook routing: e.g.
the Slack app's `serverRoute` resolver (running in the owner workspace)
can resolve `kv.get('slack:team:' + team_id, { scope: 'GLOBAL' })` to
find the workspace that connected that Slack team — without a workspace
being able to hijack another's mapping.
## Follow-ups
- Wire the Slack assistant PR (#22984) to write the claim at connect
time and read it in the events resolver
- `kv.*` access from front components
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/23089?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. -->
|
||
|
|
8b0cd00357 |
fix: bump websocket-driver 0.7.4 -> 0.7.5 (Dependabot) (#23142)
## Summary Bumps **websocket-driver 0.7.4 -> 0.7.5** to clear **2 Dependabot alerts** on the root `yarn.lock`, including the critical one from this week's advisory wave. | Severity | Advisory | CVE | Alert | |---|---|---|---| | critical | GHSA-xv26-6w52-cph6 | CVE-2026-54466 | [1577](https://github.com/twentyhq/twenty/security/dependabot/1577) - message corruption via abuse of protocol length headers | | medium | GHSA-mp7j-qc5w-4988 | CVE-2026-54490 | [1576](https://github.com/twentyhq/twenty/security/dependabot/1576) - resource limit bypass via message compression | websocket-driver is transitive via **faye-websocket** (`>=0.5.1`) and **sockjs** (`^0.7.4`); both ranges already permit 0.7.5, so a recursive `yarn up -R websocket-driver` lifts it with no resolution and no `package.json` change. ## Verification - `yarn install --immutable` passes. - Diff is `yarn.lock` only; websocket-driver resolves to 0.7.5, no 0.7.4 remains. - 0.7.5 published 2026-06-04, clears the 3-day npm age gate. |
||
|
|
10a313dc7f |
chore: bump version to 2.24.0 (#23152)
## Summary - Moves current version to previous versions array - Sets TWENTY_CURRENT_VERSION to the new version - Updates TWENTY_NEXT_VERSIONS with the next minor version - Bumps twenty-client-sdk, twenty-sdk, and create-twenty-app to the same version ## Checklist - [ ] Verify version constants are correct - [ ] Verify npm package versions match <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23152?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 Action Deploy <github-action-deploy@twenty.com> |
||
|
|
893db7558e |
Fix instance fast migration, fallback index creation (#23149)
<!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23149?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. --> |
||
|
|
7fa3c5c77b |
chore: sync AI model catalog from models.dev (#23143)
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. Co-authored-by: FelixMalfait <6399865+FelixMalfait@users.noreply.github.com> |
||
|
|
984944e9bc |
Fix workspace cache flush versioned metadata (#23121)
## Problem
`WorkspaceCacheStorageService.flushVersionedMetadata` never deleted
several of the keys it was supposed to flush:
- When called without a `metadataVersion` (the dev seeder path), it
built keys ending in `:*` and passed them to `cacheStorageService.del`.
`del` is an exact-key delete with no glob support, so this branch
deleted nothing at all.
- `setGraphQLTypeDefs` and `setGraphQLUsedScalarNames` can write
applicationId-suffixed keys
(`{key}:{workspaceId}:{metadataVersion}:{applicationId}`), which the
flush loop never matched. On workspace deletion these SDL and scalar
entries survived in Redis until the 1-week TTL expired.
- The `MetadataVersion` key is stored without a version suffix
(`metadata:workspace-metadata-version:{workspaceId}`), but the flush
loop appended `:{metadataVersion}` to every key, so it never matched
either.
## Fix
`flushVersionedMetadata` now targets the key shapes that are actually
written:
- The `MetadataVersion` key is deleted by its exact, unversioned shape.
- With a known `metadataVersion`, each versioned key gets an exact `del`
on `{key}:{workspaceId}:{metadataVersion}` plus a `flushByPattern` on
`{key}:{workspaceId}:{metadataVersion}:*` to catch
applicationId-suffixed entries. The pattern requires the trailing colon
so flushing version 1 cannot match version 12.
- Without a `metadataVersion`, each versioned key is flushed with
`flushByPattern` on `{key}:{workspaceId}:*`.
`flushByPattern` is Redis-only, which is safe here: the cache module
factory hardcodes the Redis store, and `flushGraphQLOperation` in the
same service already relies on it.
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/23121?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. -->
|
||
|
|
c503d4c4aa |
i18n - docs translations (#23136)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
5add5ea695 |
fix(billing) - expand billing sub uniqueness constraint (#23123)
fixes : https://discord.com/channels/1130383047699738754/1528956737363771497 <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23123?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. --> |
||
|
|
af6f3c0c06 |
i18n - translations (#23126)
Created by Github action --------- Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
c59df4cddd |
Upgrade remaining official apps to 2.23 alpha (#23124)
The sdk does not provide system field anymore at all ( including relation ) if you don't upgrade you'll get a deterministic universal identifier collision from previously provision and now side effect resulting ones <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23124?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. --> |
||
|
|
9742c21a99 |
i18n - docs translations (#23125)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23125?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> |
||
|
|
9a374c4b6d |
Add real estate demo app (#23111)
## What A new app under `packages/twenty-apps/internal/real-estate` that seeds a real-estate demo workspace: buyers, sellers, agents, property listings, showings, and an opportunity pipeline, with role-based access. ## Data model - **Property** (custom object): address, price (currency), status (coming soon / active / under offer / sold), type, beds/baths/surface, photos, `listingAgent` and `sellerContact` relations to Person. - **Showing** (custom object): scheduledAt, status, feedback, interest rating, and `property` / `buyer` / `agent` / `opportunity` relations. - **Person** (standard, extended): `personType` (Buyer / Seller / Agent), budget min/max, pre-approved, desired area. - **Opportunity** (standard, extended): `buyerStage` pipeline (completing profile → showing → offer made → closing → won → lost), and `buyer` / `seller` / `property` / `showings` relations. ## Views - **Buyer Pipeline** — kanban on Opportunity grouped by buyer stage, one card per buyer, scoped to real-estate deals. - **Available (by price)** — properties sorted by price desc, excluding sold. - **Agents** / **Buyers** — filtered Person views. - Record-page layouts for Opportunity and Showing so the relations render on the detail pages. ## Roles - **Broker** — full access (default). - **Agent** — Property / Showing / Person / Note / Task, no Opportunity access. - **Seller** — read-only on their listing and its showings. ## Seeding A synchronous post-install logic function seeds 4 agents, 12 sellers, 12 buyers, 30 properties across 5 cities, 24 showings, and 12 opportunities (one per buyer), all wired through the relations. --------- Co-authored-by: prastoin <paul@twenty.com> |
||
|
|
28adcbffb9 |
Remove dead code from legacy metadataVersion cache mechanism (#23122)
- Drop unused setORMEntitySchema/getORMEntitySchema from WorkspaceCacheStorageService - Drop MetadataObjectMetadataMaps cache key, only referenced by the flush loop - Drop never-populated workspaceMetadataVersion field from workspace auth context type and builders - Drop unthrown TwentyORMExceptionCode.METADATA_VERSION_MISMATCH - Drop unused WorkspaceMetadataVersionModule imports in field-metadata and object-metadata modules <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23122?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. --> |
||
|
|
cf5e6b7bad |
i18n - website translations (#23096)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23096?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> |