# Introduction
Followup of
https://github.com/twentyhq/twenty/pull/17001#pullrequestreview-3638508738
close https://github.com/twentyhq/core-team-issues/issues/1910
We've completely decom the `sync-metadata` in production. We're now then
removing its implementation in favor of the v2.
## TODO:
- [x] Remove sync-metadata implem and commands
- [x] Remove workspace decorators
- [x] Type each deprecated field to deprecated on their workspaceEntity
- [x] Remove the `workspace-sync-metadata` folder entirely
- [x] remove workspace migration
- [x] workspace migration removal migration
- [x] remove the `v2` references from workspace manager file names
- [x] remove the `v2` references from workspace manager modules
- [ ] Double check impact on translation file path updates
## Note
- Removed the gate logic
- Remains some service v2 naming, serverless needs to be migrated on v2
fully
- Removed workspaceMigration service app health consumption, making it
always returning up ( no more down ) cc @FelixMalfait ( quite obsolete
health check now, will require complete refactor once we introduce inter
app dependency etc )
## Context
The feature has not been maintained for more than a year and was never
officially launched.
This PR removes its code due to the upcoming refactoring of the
sync-metadata that will break its logic if not handled properly and it
would be too costly for the time being.
TODO:
- remove isRemote from object/field metadata
- Fix groupBy field matching for composite fields nested within relation
fields
- Add `nestedSubFieldName` comparison when the nested field is a
composite type (CURRENCY, FULL_NAME, ADDRESS, etc.)
For huge workflows, after 20 steps, we stop the job and re-trigger a new
one.
But some of those workflows actually never stop.
See https://github.com/twentyhq/private-issues/issues/401
Here is a first issue. We resuming the workflow, we were providing the
result instead of the output
- Add widget validation
- Remove 'None' option for primary axis group by
- Fix error message parsing by passing the operation type in
`useMetadataErrorHandler`
Fixes [#16819](https://github.com/twentyhq/twenty/issues/16819)
Added a post-query hook (NoteDeleteOnePostQueryHook) that automatically
soft-deletes all associated noteTarget records when a note is deleted.
## Key changes:
Created `note-delete-one.post-query.hook.ts`
Uses `GlobalWorkspaceOrmManager` to access workspace entities correctly
Executes in the proper workspace context to ensure data isolation
Soft-deletes noteTarget records to maintain referential consistency
## Testing
1. Company notes
Create a Company
Create and attach a Note to the Company
Navigate to Company → Notes page (should display the note)
Delete the Note
Navigate to Company → Notes page (should no longer crash)
Restore the Note (should appear again)
Delete and destroy the Note permanently
Navigate to Company → Notes page (should still work, just empty)
2. Opportunity notes
Create an Opportunity
Create and attach a Note to the Opportunity
Navigate to Opportunity → Notes page (should display the note)
Delete the Note
Navigate to Opportunity → Notes page (should no longer crash)
Restore the Note (should appear again)
Delete and destroy the Note permanently
Navigate to Opportunity → Notes page (should still work, just empty)
### Expected behavior:
Company Notes page remains functional at all stages
No console errors
Deleted notes properly excluded from the view
Other notes on the same Company remain accessible
Additional Notes
This fix ensures data consistency by maintaining the relationship
between notes and their targets throughout the deletion lifecycle.
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
# Introduction
Followup https://github.com/twentyhq/twenty/pull/16863
Important note: This is not an upgrade command and will have to be
manually run
In this pull request we're introducing a coding that will allow this
[migration](https://github.com/twentyhq/twenty/blob/clean-orphan-metadata/packages/twenty-server/src/database/typeorm/core/migrations/utils/1767002571103-addWorkspaceForeignKeys.util.ts#L3)
to pass, it enforces the `workspaceId` foreignKey on all metadata
entities. Allowing workspace deletion cascading of all its related
entities and avoiding orphan metadata entities to reoccur in the future
Also introduced a small migration that will set the workspaceId col type
to `uuid`, as it has been historically `varchar`
This migration is a requirement for the above command to work
successfully
## Note
Chunking by relations fields the orphan field deletion as would take way
too much time within a transac,
## Test
Tested on a prod extract locally ( both dry and not dry )
## Description
SELECT fields have a defined option order that users expect to see
reflected in charts.
This PR allows sorting by that position and also enables custom manual
ordering.
## Video QA
### Reordering on primary axis
https://github.com/user-attachments/assets/994f515e-19cb-4a5e-b745-e8c77e92ae0b
### Reordering on secondary axis
https://github.com/user-attachments/assets/444c16f2-1920-4dc4-8b42-312d520ab43b
Note: The colors in the graph will match the colors of the select
options, but this will be done in another PR
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> Introduces new sort modes and UI for chart groupings, with full FE/BE
support and updated GraphQL schema.
>
> - Extend `GraphOrderBy` with `FIELD_POSITION_ASC/DESC` and `MANUAL`;
add corresponding fields in configs: `primaryAxisManualSortOrder`,
`secondaryAxisManualSortOrder`, and `manualSortOrder` (pie)
> - New UI: dropdown options filtered by field type, icons, and a
draggable submenu (`ChartManualSortSubMenuContent`) to reorder select
options; integrates with widget edit flow
> - Sorting logic added/refactored: `sortChartData`,
`sortByManualOrder`, `sortBySelectOptionPosition`,
`sortLineChartSeries`, plus updates to bar/line/pie transformers to
honor new modes and manual orders
> - Default behaviors: select fields default to `FIELD_POSITION_ASC`;
query variable builders skip `orderBy` when using manual/position sorts
> - Update GraphQL generated types/fragments/queries and backend
DTOs/schemas to persist new fields; add tests for sorting utilities and
snapshots; add sorting icons in `twenty-ui`
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
78c9b56c0f. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
## Summary
This PR enables serverless functions to be exposed as AI tools, allowing
them to be used by AI agents.
### Changes
- Added new `SERVERLESS_FUNCTION` tool category
- Added `toolDescription`, `toolInputSchema`, and `toolOutputSchema`
fields to serverless functions
- Created database migration for the new schema columns
- Added tool index query and resolver for fetching available tools
- Added Settings AI page tabs (Skills, Tools, Settings) with new tools
table
- Added utility to convert tool schema to JSON schema format
- Updated frontend to display tools in the settings page
### Implementation Details
- Serverless functions can now define tool metadata (description,
input/output schemas)
- These functions are automatically registered in the tool registry
- The tool index endpoint allows querying available tools with their
schemas
- Settings page now has a dedicated Tools tab showing all available
tools
# Introduction
In this PR we're:
- Refactoring the workspace migration action type introducing grain over
metadata and operation type ( for example operation `create` and
metadata `field` )
- Thanks to above point we can now factorize the runner optimistic
rendering out of each runner actions-handler file using the existing
into the generic one ( -3200 lines of code here )
- Still thanks to action type refactor we're able to dynamically compose
the response error type only send data when there's here. No more static
counter and static summary error message. This way we won't have to re
run snapshot every time we add a new entity to the engine ( huge
snapshot diff here )
## Noticeable points:
- We introduce an index update action to avoid any complex typing for
not having one or a tuple of actions instead. Now the drop and insert
logic is directly inferred from the update action handler instead of
being two action ( delete index and create index )
## TODO
- [x] Define base actions types
- [x] Migrate all actions to action type and metadata name pattern (
base actions )
- [x] Refactor flat entity validation type to embed metadata name
- [x] Refactor optimistic rendering within runner
- [x] Refactor legacy cache invalidation switch
- [x] Refactor response error format ( dynamic counter again + no empty
entries )
- [x] Try factorizing and removing redundant nor unused type declaration
in metadata actions type intermediary files
- [x] Adapt front to new response error format
## Remarks
- ~~Should create an issue for generic replace flat entity in related
flat entity maps~~ overkill
- Should create an issue for oneToMany foreignKey being nullable not
always cascade delete optimistic rendering edge case to either docs or
fix it in delete flat entity and related entity ( re-code the pg
cascading behavior )
- We could also factorize the builder to only implement validators and
not the intermediary file
# Introduction
closes https://github.com/twentyhq/core-team-issues/issues/1792
Refactoring the cache to invalidate to be more precise and prevent any
corrupted cache occurrences
## Next
The load dependency cache from the workspace migration, that's not
optimal it should be smart enough to infer it from the actions
themselves. For the moment their definition isn't granular enough and
inferring such info would be very dirty
# Introduction
As we introduced a new grain on relation extraction thanks to low level
`SyncableEntity` and `WorkspaceRelatedEntity` we're able to strictly
typesafe extract metadata entity
The new constant centralizes both many to one and one to many constants
metadata entity constants in a more strictly typesafe way. Remains only
the flatEntityForeignKey aggregator which has to be chosen manually
across all available targeted flat entity ids properties
## Summary
This PR introduces a Skills system for AI agents, inspired by the [Agent
Skills specification](https://agentskills.io/specification).
## Changes
### Backend
- **SkillEntity**: New database entity with migration for storing skills
- **V2 Sync Mechanism**: Implemented FlatSkill, builders, validators,
and action handlers following the v2 flat entity pattern
- **Standard Skills**: Pre-defined skills (workflow-building,
data-manipulation, dashboard-building, metadata-building, research,
code-interpreter, xlsx, pdf, docx, pptx)
- **GraphQL API**: CRUD operations for skills with proper guards and
permissions
- **Workspace Cache**: Integrated skills into the workspace cache system
### Frontend
- **Skills Table**: Searchable table in AI settings showing all skills
- **Skill Form**: Create/edit page with Label (primary), Description,
and Content (markdown editor)
- **API Name**: Following existing patterns, name is derived from label
with advanced settings toggle for custom API names
- **Standard vs Custom**: Standard skills are read-only, custom skills
can be edited/deleted
## Key Design Decisions
- Skills are stored in the database (Salesforce-like approach) rather
than files
- Name is derived from Label by default (isLabelSyncedWithName pattern)
- Skills reference functions/files via @ mentions in markdown content
rather than explicit relations
- Standard skills are synced from code, custom skills are created via UI
## Screenshots
Skills table and form UI follow existing settings patterns.
## Testing
- [x] Lint passes
- [x] Typecheck passes
- [ ] CI tests
Fixes https://github.com/twentyhq/private-issues/issues/395
When calling `removeUserFromWorkspaceAndPotentiallyDeleteWorkspace` from
`user.service`,
```
await workspaceMemberRepository.delete({
userId: userWorkspace.userId,
});
```
related database event is not emitted.
Same issue with update has been fixed
[here](https://github.com/twentyhq/twenty/pull/13287/changes)
The database event is not emitted because `await
eventSelectQueryBuilder.getOne()` in `workspace-delete-query-builder`
returns `null`. This happens because the `selectQueryBuilder` has no
entity—the database request is sent and the raw result is not `null`,
but the entity is `null`, causing the final result to be null.
It can be fixed the same way update has been fixed, updating the
`workspace-entity-manager`
- Pros : consistant with update but we should not forget to fix
softDelete and restore
- Cons : `workspace-entity-manager` is a copy of typeORM logic +
permission injection. Should it be more ?
Alternatively (as featured in this PR), it can be fixed by updating
computeEventSelectQueryBuilder, inspired by TypeORM's logic in
typeorm/query-builder/QueryBuilder.js at line 67.
- Pros : it fit with typeORM logic + It fixes all repository operations
## Introduction
On the side hanlded PR with a // agents on another repo
Made several iterations to fix behavior and direction
Find below auto-generated PR description
closes https://github.com/twentyhq/core-team-issues/issues/2037
Created generic tooling for entity circular dep checking, will be useful
for permissions validation too @Weiko
## Migrate `viewFilterGroup` entity to v2 flat architecture
### Summary
Migrates the `viewFilterGroup` entity from v1 to the v2 flat entity
architecture, following the established patterns for other v2 entities
like `viewFilter`, `view`, and `viewField`.
### Changes
**Types & Constants**
- Added `FlatViewFilterGroup` and `FlatViewFilterGroupMaps` types
- Added editable properties constant for `viewFilterGroup`
- Registered `viewFilterGroup` in `ALL_METADATA_NAME`,
`ALL_METADATA_RELATION_PROPERTIES`,
`ALL_METADATA_MANY_TO_ONE_RELATIONS`, and related constants
**Cache Service**
- Created `WorkspaceFlatViewFilterGroupMapCacheService` with proper
relation loading for `viewFilters` and `childViewFilterGroups`
- Updated `WorkspaceFlatViewMapCacheService` to load `viewFilterGroups`
relation
**Builder & Validator**
- Created `WorkspaceMigrationV2ViewFilterGroupActionsBuilderService`
- Created `FlatViewFilterGroupValidatorService` with creation, update,
and deletion validation
- Integrated validation into the orchestrator service (runs before
`viewFilter` validation)
**Action Handlers**
- Created create, update, and delete action handlers for
`viewFilterGroup`
**Service Migration**
- Rewrote `ViewFilterGroupService` to use v2 migration pattern with
`WorkspaceMigrationValidateBuildAndRunService`
- Created utility functions for transforming DTOs to flat entities
**Database Migration**
- Added migration to make `parentViewFilterGroupId` foreign key
deferrable (handles self-referential parent/child insertions)
**ViewFilter Integration**
- Added `viewFilterGroupId` validation in
`FlatViewFilterValidatorService`
- Updated `viewFilter` many-to-one relations to include
`viewFilterGroup`
**Tests**
- Added integration tests for successful creation, update, deletion, and
destruction
- Added failing test cases for non-existent entities and invalid
references
- Added failing test for `viewFilter` creation with non-existent
`viewFilterGroupId`
### Breaking Changes
None - existing API contracts are preserved.
And remove IS_GLOBAL_WORKSPACE_DATASOURCE_ENABLED
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Removes deprecated feature flag and enables RLS predicates in dev**
>
> - Deletes `IS_GLOBAL_WORKSPACE_DATASOURCE_ENABLED` from server enum
and frontend generated GraphQL types
> - Updates dev seeder to enable
`IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED` instead of the removed flag
> - Adjusts tests and mocked `GlobalWorkspaceDataSource` defaults to
drop the removed flag
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
7f4d5a401a. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
## Context
This PR adds the core structure for RLS implementation:
- RLS data model
- RLS service layer
- RLS WorkspaceMigration and Syncable Entity + cache + Validations
- RLS resolver layer
- ORM layer with RLS Predicate to ORM WHERE clause conversion with
workspaceMember record transposition
Tests are missing though
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> Establishes core row-level permissions infrastructure and enforcement
across the stack.
>
> - Backend: new `rowLevelPermissionPredicate` and
`rowLevelPermissionPredicateGroup` entities, TypeORM migration, feature
flag `IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED`, flat-entity
maps/cache wiring, services and GraphQL resolvers for CRUD, and
inclusion of `workspaceMember` in auth context
> - ORM: applies row-level permission predicates to SELECT, DELETE, and
SOFT DELETE query builders; propagates context through
GlobalWorkspaceOrmManager/EntityManager
> - GraphQL: generated schema/types/queries/mutations for
creating/updating/deleting/fetching predicates and groups
> - Frontend: settings page adds a gated "Record-level" section
(placeholder) and metadata error handler labels for new entities
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
fe955cc458. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Fixes https://github.com/twentyhq/twenty/issues/16809
# Introduction
Front was expected timeline activities field to be relation and not
morph relation
Also fixed the new v2 workspace creation that had a bug in the
createStandardRelation util ( it's not in production yet so everything's
fine )
## Summary
The `lint:changed` command was not using the correct ESLint config for
`twenty-server`, causing it to use the root `eslint.config.mjs` instead
of the package-specific one. This PR fixes the issue and renames the
command to `lint:diff-with-main` for clarity.
## Problem
- `twenty-front` correctly specified `--config
packages/twenty-front/eslint.config.mjs`
- `twenty-server` just called `npx eslint` without specifying a config
- This meant `twenty-server` was missing important rules like:
- `@typescript-eslint/no-explicit-any: 'error'`
- `@stylistic/*` rules (linebreak-style, padding, etc.)
- `import/order` with NestJS patterns
- Custom workspace rules (`@nx/workspace-inject-workspace-repository`,
etc.)
## Solution
1. **Renamed** `lint:changed` to `lint:diff-with-main` to be explicit
about what the command does
2. **Centralized** the configuration in `nx.json` targetDefaults:
- Uses `{projectRoot}` interpolation for paths (resolved by Nx at
runtime)
- Each package automatically uses its own ESLint config
- Packages can override specific options (e.g., file extension pattern)
3. **Simplified** `project.json` files by inheriting from defaults
## Usage
```bash
# Lint only files changed vs main branch
npx nx lint:diff-with-main twenty-front
npx nx lint:diff-with-main twenty-server
# Auto-fix files changed vs main
npx nx lint:diff-with-main twenty-front --configuration=fix
```
## Changes
- **nx.json**: Added `lint:diff-with-main` target default with
configurable pattern
- **twenty-front/project.json**: Simplified to inherit from defaults
- **twenty-server/project.json**: Overrides pattern to include `.json`
files
- **CLAUDE.md** and **.cursor/rules**: Updated documentation