diff --git a/docs/generated/postgres-schema/README.md b/docs/generated/postgres-schema/README.md index 8f6c07493fb..7b21b239c83 100644 --- a/docs/generated/postgres-schema/README.md +++ b/docs/generated/postgres-schema/README.md @@ -36,7 +36,7 @@ Auto-generated from the PostgreSQL migrations in @n8n/db. Do not edit by hand. | [public.agent_thread_grants](public.agent_thread_grants.md) | 4 | | BASE TABLE | | [public.agent_version_skill_dependency](public.agent_version_skill_dependency.md) | 4 | | BASE TABLE | | [public.agent_workflow_dependency](public.agent_workflow_dependency.md) | 3 | | BASE TABLE | -| [public.agents](public.agents.md) | 14 | | BASE TABLE | +| [public.agents](public.agents.md) | 15 | | BASE TABLE | | [public.agents_memory_entries](public.agents_memory_entries.md) | 13 | | BASE TABLE | | [public.agents_memory_entry_candidates](public.agents_memory_entry_candidates.md) | 14 | | BASE TABLE | | [public.agents_memory_entry_locks](public.agents_memory_entry_locks.md) | 6 | | BASE TABLE | @@ -240,7 +240,7 @@ erDiagram "public.agent_version_skill_dependency" }o--|| "public.skill_version" : "FOREIGN KEY (#quot;skillVersionId#quot;) REFERENCES skill_version(id)" "public.agent_workflow_dependency" }o--|| "public.workflow_entity" : "FOREIGN KEY (#quot;workflowId#quot;) REFERENCES workflow_entity(id) ON DELETE CASCADE" "public.agent_workflow_dependency" }o--|| "public.agents" : "FOREIGN KEY (#quot;agentId#quot;) REFERENCES agents(id) ON DELETE CASCADE" -"public.agents" }o--|| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE" +"public.agents" }o--o| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE" "public.agents" }o--o| "public.agent_history" : "FOREIGN KEY (#quot;activeVersionId#quot;) REFERENCES agent_history(#quot;versionId#quot;) ON DELETE SET NULL" "public.agents_memory_entries" }o--|| "public.agents" : "FOREIGN KEY (#quot;agentId#quot;) REFERENCES agents(id) ON DELETE CASCADE" "public.agents_memory_entries" }o--|| "public.agents_resources" : "FOREIGN KEY (#quot;resourceId#quot;) REFERENCES agents_resources(id) ON DELETE CASCADE" @@ -758,6 +758,7 @@ erDiagram varchar_255_ projectId FK integer revision json schema + varchar_16_ scope timestamp_3__with_time_zone setupCompletedAt json skills json tools diff --git a/docs/generated/postgres-schema/public.agent_background_job.md b/docs/generated/postgres-schema/public.agent_background_job.md index ef567001ba6..86a106e09be 100644 --- a/docs/generated/postgres-schema/public.agent_background_job.md +++ b/docs/generated/postgres-schema/public.agent_background_job.md @@ -95,6 +95,7 @@ erDiagram varchar_255_ projectId FK integer revision json schema + varchar_16_ scope timestamp_3__with_time_zone setupCompletedAt json skills json tools diff --git a/docs/generated/postgres-schema/public.agent_channel_status.md b/docs/generated/postgres-schema/public.agent_channel_status.md index a4ca4c92a92..ed2d659cc90 100644 --- a/docs/generated/postgres-schema/public.agent_channel_status.md +++ b/docs/generated/postgres-schema/public.agent_channel_status.md @@ -70,6 +70,7 @@ erDiagram varchar_255_ projectId FK integer revision json schema + varchar_16_ scope timestamp_3__with_time_zone setupCompletedAt json skills json tools diff --git a/docs/generated/postgres-schema/public.agent_chat_attachments.md b/docs/generated/postgres-schema/public.agent_chat_attachments.md index fb9814667dc..c20b0b3340b 100644 --- a/docs/generated/postgres-schema/public.agent_chat_attachments.md +++ b/docs/generated/postgres-schema/public.agent_chat_attachments.md @@ -75,6 +75,7 @@ erDiagram varchar_255_ projectId FK integer revision json schema + varchar_16_ scope timestamp_3__with_time_zone setupCompletedAt json skills json tools diff --git a/docs/generated/postgres-schema/public.agent_chat_subscriptions.md b/docs/generated/postgres-schema/public.agent_chat_subscriptions.md index fa4bf668e2b..d5aedff03ef 100644 --- a/docs/generated/postgres-schema/public.agent_chat_subscriptions.md +++ b/docs/generated/postgres-schema/public.agent_chat_subscriptions.md @@ -56,6 +56,7 @@ erDiagram varchar_255_ projectId FK integer revision json schema + varchar_16_ scope timestamp_3__with_time_zone setupCompletedAt json skills json tools diff --git a/docs/generated/postgres-schema/public.agent_checkpoints.md b/docs/generated/postgres-schema/public.agent_checkpoints.md index ee4865a8032..3b71a13d0c0 100644 --- a/docs/generated/postgres-schema/public.agent_checkpoints.md +++ b/docs/generated/postgres-schema/public.agent_checkpoints.md @@ -58,6 +58,7 @@ erDiagram varchar_255_ projectId FK integer revision json schema + varchar_16_ scope timestamp_3__with_time_zone setupCompletedAt json skills json tools diff --git a/docs/generated/postgres-schema/public.agent_credential_dependency.md b/docs/generated/postgres-schema/public.agent_credential_dependency.md index 243130ebf69..ab82c4e1ac0 100644 --- a/docs/generated/postgres-schema/public.agent_credential_dependency.md +++ b/docs/generated/postgres-schema/public.agent_credential_dependency.md @@ -49,6 +49,7 @@ erDiagram varchar_255_ projectId FK integer revision json schema + varchar_16_ scope timestamp_3__with_time_zone setupCompletedAt json skills json tools diff --git a/docs/generated/postgres-schema/public.agent_eval_dataset.md b/docs/generated/postgres-schema/public.agent_eval_dataset.md index 272c34d8d52..cfaaeefcfdd 100644 --- a/docs/generated/postgres-schema/public.agent_eval_dataset.md +++ b/docs/generated/postgres-schema/public.agent_eval_dataset.md @@ -69,6 +69,7 @@ erDiagram varchar_255_ projectId FK integer revision json schema + varchar_16_ scope timestamp_3__with_time_zone setupCompletedAt json skills json tools diff --git a/docs/generated/postgres-schema/public.agent_execution_threads.md b/docs/generated/postgres-schema/public.agent_execution_threads.md index 07f0bc8ef83..f63f8e89a50 100644 --- a/docs/generated/postgres-schema/public.agent_execution_threads.md +++ b/docs/generated/postgres-schema/public.agent_execution_threads.md @@ -102,6 +102,7 @@ erDiagram varchar_255_ projectId FK integer revision json schema + varchar_16_ scope timestamp_3__with_time_zone setupCompletedAt json skills json tools diff --git a/docs/generated/postgres-schema/public.agent_files.md b/docs/generated/postgres-schema/public.agent_files.md index 382622ed0d4..25ea458e566 100644 --- a/docs/generated/postgres-schema/public.agent_files.md +++ b/docs/generated/postgres-schema/public.agent_files.md @@ -70,6 +70,7 @@ erDiagram varchar_255_ projectId FK integer revision json schema + varchar_16_ scope timestamp_3__with_time_zone setupCompletedAt json skills json tools diff --git a/docs/generated/postgres-schema/public.agent_history.md b/docs/generated/postgres-schema/public.agent_history.md index f88d49c9688..efc11c1b7ba 100644 --- a/docs/generated/postgres-schema/public.agent_history.md +++ b/docs/generated/postgres-schema/public.agent_history.md @@ -67,6 +67,7 @@ erDiagram varchar_255_ projectId FK integer revision json schema + varchar_16_ scope timestamp_3__with_time_zone setupCompletedAt json skills json tools diff --git a/docs/generated/postgres-schema/public.agent_skill_dependency.md b/docs/generated/postgres-schema/public.agent_skill_dependency.md index b799959e324..74fe035f6a3 100644 --- a/docs/generated/postgres-schema/public.agent_skill_dependency.md +++ b/docs/generated/postgres-schema/public.agent_skill_dependency.md @@ -53,6 +53,7 @@ erDiagram varchar_255_ projectId FK integer revision json schema + varchar_16_ scope timestamp_3__with_time_zone setupCompletedAt json skills json tools diff --git a/docs/generated/postgres-schema/public.agent_task_definition.md b/docs/generated/postgres-schema/public.agent_task_definition.md index 6be5911a4c3..49a97c48ce4 100644 --- a/docs/generated/postgres-schema/public.agent_task_definition.md +++ b/docs/generated/postgres-schema/public.agent_task_definition.md @@ -61,6 +61,7 @@ erDiagram varchar_255_ projectId FK integer revision json schema + varchar_16_ scope timestamp_3__with_time_zone setupCompletedAt json skills json tools diff --git a/docs/generated/postgres-schema/public.agent_task_run_lock.md b/docs/generated/postgres-schema/public.agent_task_run_lock.md index caece49a764..6901d998856 100644 --- a/docs/generated/postgres-schema/public.agent_task_run_lock.md +++ b/docs/generated/postgres-schema/public.agent_task_run_lock.md @@ -55,6 +55,7 @@ erDiagram varchar_255_ projectId FK integer revision json schema + varchar_16_ scope timestamp_3__with_time_zone setupCompletedAt json skills json tools diff --git a/docs/generated/postgres-schema/public.agent_workflow_dependency.md b/docs/generated/postgres-schema/public.agent_workflow_dependency.md index 3dc1145a36e..89f365b5bc3 100644 --- a/docs/generated/postgres-schema/public.agent_workflow_dependency.md +++ b/docs/generated/postgres-schema/public.agent_workflow_dependency.md @@ -49,6 +49,7 @@ erDiagram varchar_255_ projectId FK integer revision json schema + varchar_16_ scope timestamp_3__with_time_zone setupCompletedAt json skills json tools diff --git a/docs/generated/postgres-schema/public.agents.md b/docs/generated/postgres-schema/public.agents.md index 63aa85de828..3716bbeb0ca 100644 --- a/docs/generated/postgres-schema/public.agents.md +++ b/docs/generated/postgres-schema/public.agents.md @@ -10,9 +10,10 @@ | id | varchar(36) | | false | [public.agent_background_job](public.agent_background_job.md) [public.agent_channel_status](public.agent_channel_status.md) [public.agent_chat_attachments](public.agent_chat_attachments.md) [public.agent_chat_subscriptions](public.agent_chat_subscriptions.md) [public.agent_checkpoints](public.agent_checkpoints.md) [public.agent_credential_dependency](public.agent_credential_dependency.md) [public.agent_eval_dataset](public.agent_eval_dataset.md) [public.agent_execution_threads](public.agent_execution_threads.md) [public.agent_files](public.agent_files.md) [public.agent_history](public.agent_history.md) [public.agent_skill_dependency](public.agent_skill_dependency.md) [public.agent_task_definition](public.agent_task_definition.md) [public.agent_task_run_lock](public.agent_task_run_lock.md) [public.agent_workflow_dependency](public.agent_workflow_dependency.md) [public.agents_memory_entries](public.agents_memory_entries.md) [public.agents_memory_entry_candidates](public.agents_memory_entry_candidates.md) [public.agents_memory_entry_locks](public.agents_memory_entry_locks.md) [public.agents_memory_entry_sources](public.agents_memory_entry_sources.md) [public.agents_observation_cursors](public.agents_observation_cursors.md) [public.agents_observation_locks](public.agents_observation_locks.md) [public.agents_observations](public.agents_observations.md) [public.skill](public.skill.md) | | | | integrations | json | '[]'::json | false | | | | | name | varchar(128) | | false | | | | -| projectId | varchar(255) | | false | | [public.project](public.project.md) | | +| projectId | varchar(255) | | true | | [public.project](public.project.md) | | | revision | integer | 0 | false | | | | | schema | json | | true | | | | +| scope | varchar(16) | 'project'::character varying | false | | | project: belongs to projectId; instance: code-defined, belongs to no project (projectId is null) | | setupCompletedAt | timestamp(3) with time zone | | true | | | When this agent first reached a complete, publishable setup | | skills | json | '{}'::json | false | | | | | tools | json | '{}'::json | false | | | | @@ -23,6 +24,8 @@ | Name | Type | Definition | | ---- | ---- | ---------- | +| CHK_agents_scope | CHECK | CHECK (((scope)::text = ANY ((ARRAY['project'::character varying, 'instance'::character varying])::text[]))) | +| CHK_agents_scope_project | CHECK | CHECK ((((scope)::text = 'instance'::text) = ("projectId" IS NULL))) | | FK_940597dfe9753375309ce6aeea0 | FOREIGN KEY | FOREIGN KEY ("activeVersionId") REFERENCES agent_history("versionId") ON DELETE SET NULL | | FK_a30d560207c4071d98aa03c179c | FOREIGN KEY | FOREIGN KEY ("projectId") REFERENCES project(id) ON DELETE CASCADE | | PK_9c653f28ae19c5884d5baf6a1d9 | PRIMARY KEY | PRIMARY KEY (id) | @@ -31,8 +34,8 @@ | agents_id_not_null | n | NOT NULL id | | agents_integrations_not_null | n | NOT NULL integrations | | agents_name_not_null | n | NOT NULL name | -| agents_projectId_not_null | n | NOT NULL "projectId" | | agents_revision_not_null | n | NOT NULL revision | +| agents_scope_not_null | n | NOT NULL scope | | agents_skills_not_null | n | NOT NULL skills | | agents_tools_not_null | n | NOT NULL tools | | agents_updatedAt_not_null | n | NOT NULL "updatedAt" | @@ -73,7 +76,7 @@ erDiagram "public.agents_observation_locks" }o--|| "public.agents" : "FOREIGN KEY (#quot;agentId#quot;) REFERENCES agents(id) ON DELETE CASCADE" "public.agents_observations" }o--|| "public.agents" : "FOREIGN KEY (#quot;agentId#quot;) REFERENCES agents(id) ON DELETE CASCADE" "public.skill" }o--o| "public.agents" : "FOREIGN KEY (#quot;agentId#quot;) REFERENCES agents(id) ON DELETE CASCADE" -"public.agents" }o--|| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE" +"public.agents" }o--o| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE" "public.agents" { varchar_36_ activeVersionId FK @@ -85,6 +88,7 @@ erDiagram varchar_255_ projectId FK integer revision json schema + varchar_16_ scope timestamp_3__with_time_zone setupCompletedAt json skills json tools diff --git a/docs/generated/postgres-schema/public.agents_memory_entries.md b/docs/generated/postgres-schema/public.agents_memory_entries.md index 2e4e740d314..c8a2716a406 100644 --- a/docs/generated/postgres-schema/public.agents_memory_entries.md +++ b/docs/generated/postgres-schema/public.agents_memory_entries.md @@ -82,6 +82,7 @@ erDiagram varchar_255_ projectId FK integer revision json schema + varchar_16_ scope timestamp_3__with_time_zone setupCompletedAt json skills json tools diff --git a/docs/generated/postgres-schema/public.agents_memory_entry_candidates.md b/docs/generated/postgres-schema/public.agents_memory_entry_candidates.md index 256ab1bf2fb..91a01fb6a6e 100644 --- a/docs/generated/postgres-schema/public.agents_memory_entry_candidates.md +++ b/docs/generated/postgres-schema/public.agents_memory_entry_candidates.md @@ -91,6 +91,7 @@ erDiagram varchar_255_ projectId FK integer revision json schema + varchar_16_ scope timestamp_3__with_time_zone setupCompletedAt json skills json tools diff --git a/docs/generated/postgres-schema/public.agents_memory_entry_locks.md b/docs/generated/postgres-schema/public.agents_memory_entry_locks.md index 19bd1c192aa..9c71ff2a020 100644 --- a/docs/generated/postgres-schema/public.agents_memory_entry_locks.md +++ b/docs/generated/postgres-schema/public.agents_memory_entry_locks.md @@ -58,6 +58,7 @@ erDiagram varchar_255_ projectId FK integer revision json schema + varchar_16_ scope timestamp_3__with_time_zone setupCompletedAt json skills json tools diff --git a/docs/generated/postgres-schema/public.agents_memory_entry_sources.md b/docs/generated/postgres-schema/public.agents_memory_entry_sources.md index 951027ae410..f84f4c27118 100644 --- a/docs/generated/postgres-schema/public.agents_memory_entry_sources.md +++ b/docs/generated/postgres-schema/public.agents_memory_entry_sources.md @@ -80,6 +80,7 @@ erDiagram varchar_255_ projectId FK integer revision json schema + varchar_16_ scope timestamp_3__with_time_zone setupCompletedAt json skills json tools diff --git a/docs/generated/postgres-schema/public.agents_observation_cursors.md b/docs/generated/postgres-schema/public.agents_observation_cursors.md index 3fb08a0e2dd..f8907721346 100644 --- a/docs/generated/postgres-schema/public.agents_observation_cursors.md +++ b/docs/generated/postgres-schema/public.agents_observation_cursors.md @@ -58,6 +58,7 @@ erDiagram varchar_255_ projectId FK integer revision json schema + varchar_16_ scope timestamp_3__with_time_zone setupCompletedAt json skills json tools diff --git a/docs/generated/postgres-schema/public.agents_observation_locks.md b/docs/generated/postgres-schema/public.agents_observation_locks.md index 997e2d78046..8834ab68de5 100644 --- a/docs/generated/postgres-schema/public.agents_observation_locks.md +++ b/docs/generated/postgres-schema/public.agents_observation_locks.md @@ -62,6 +62,7 @@ erDiagram varchar_255_ projectId FK integer revision json schema + varchar_16_ scope timestamp_3__with_time_zone setupCompletedAt json skills json tools diff --git a/docs/generated/postgres-schema/public.agents_observations.md b/docs/generated/postgres-schema/public.agents_observations.md index cb6acde34ba..a8fda278622 100644 --- a/docs/generated/postgres-schema/public.agents_observations.md +++ b/docs/generated/postgres-schema/public.agents_observations.md @@ -81,6 +81,7 @@ erDiagram varchar_255_ projectId FK integer revision json schema + varchar_16_ scope timestamp_3__with_time_zone setupCompletedAt json skills json tools diff --git a/docs/generated/postgres-schema/public.project.md b/docs/generated/postgres-schema/public.project.md index 04d30b4ff68..7f114875982 100644 --- a/docs/generated/postgres-schema/public.project.md +++ b/docs/generated/postgres-schema/public.project.md @@ -42,7 +42,7 @@ erDiagram "public.activity_event" }o--|| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE" "public.agent_chat_attachments" }o--|| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE" "public.agent_execution_threads" }o--|| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE" -"public.agents" }o--|| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE" +"public.agents" }o--o| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE" "public.ai_preference" }o--o| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE" "public.data_table" }o--|| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE" "public.folder" }o--|| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE" @@ -148,6 +148,7 @@ erDiagram varchar_255_ projectId FK integer revision json schema + varchar_16_ scope timestamp_3__with_time_zone setupCompletedAt json skills json tools diff --git a/docs/generated/postgres-schema/public.skill.md b/docs/generated/postgres-schema/public.skill.md index 9b424ab5239..d644698182f 100644 --- a/docs/generated/postgres-schema/public.skill.md +++ b/docs/generated/postgres-schema/public.skill.md @@ -65,6 +65,7 @@ erDiagram varchar_255_ projectId FK integer revision json schema + varchar_16_ scope timestamp_3__with_time_zone setupCompletedAt json skills json tools diff --git a/docs/generated/sqlite-schema/README.md b/docs/generated/sqlite-schema/README.md index 6e946dc580d..19db9d7643f 100644 --- a/docs/generated/sqlite-schema/README.md +++ b/docs/generated/sqlite-schema/README.md @@ -36,7 +36,7 @@ Auto-generated from the SQLite migrations in @n8n/db. Do not edit by hand. | [agent_thread_grants](agent_thread_grants.md) | 4 | | table | | [agent_version_skill_dependency](agent_version_skill_dependency.md) | 4 | | table | | [agent_workflow_dependency](agent_workflow_dependency.md) | 3 | | table | -| [agents](agents.md) | 14 | | table | +| [agents](agents.md) | 15 | | table | | [agents_memory_entries](agents_memory_entries.md) | 13 | | table | | [agents_memory_entry_candidates](agents_memory_entry_candidates.md) | 14 | | table | | [agents_memory_entry_locks](agents_memory_entry_locks.md) | 6 | | table | @@ -222,7 +222,7 @@ erDiagram "agent_version_skill_dependency" |o--|| "agent_history" : "FOREIGN KEY (agentVersionId) REFERENCES agent_history (versionId) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE" "agent_workflow_dependency" |o--|| "workflow_entity" : "FOREIGN KEY (workflowId) REFERENCES workflow_entity (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE" "agent_workflow_dependency" |o--|| "agents" : "FOREIGN KEY (agentId) REFERENCES agents (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE" -"agents" }o--|| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE" +"agents" }o--o| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE" "agents" }o--o| "agent_history" : "FOREIGN KEY (activeVersionId) REFERENCES agent_history (versionId) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE" "agents_memory_entries" }o--o| "agents_memory_entries" : "FOREIGN KEY (supersededBy) REFERENCES agents_memory_entries (id) ON UPDATE NO ACTION ON DELETE NO ACTION MATCH NONE" "agents_memory_entries" }o--|| "agents_resources" : "FOREIGN KEY (resourceId) REFERENCES agents_resources (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE" @@ -744,6 +744,7 @@ erDiagram varchar_255_ projectId FK INTEGER revision TEXT schema + varchar_16_ scope datetime_3_ setupCompletedAt TEXT skills TEXT tools diff --git a/docs/generated/sqlite-schema/agent_background_job.md b/docs/generated/sqlite-schema/agent_background_job.md index f7e22cd9e97..ecb211cb620 100644 --- a/docs/generated/sqlite-schema/agent_background_job.md +++ b/docs/generated/sqlite-schema/agent_background_job.md @@ -97,6 +97,7 @@ erDiagram varchar_255_ projectId FK INTEGER revision TEXT schema + varchar_16_ scope datetime_3_ setupCompletedAt TEXT skills TEXT tools diff --git a/docs/generated/sqlite-schema/agent_channel_status.md b/docs/generated/sqlite-schema/agent_channel_status.md index b41e3f3ab51..2477a1c83f6 100644 --- a/docs/generated/sqlite-schema/agent_channel_status.md +++ b/docs/generated/sqlite-schema/agent_channel_status.md @@ -77,6 +77,7 @@ erDiagram varchar_255_ projectId FK INTEGER revision TEXT schema + varchar_16_ scope datetime_3_ setupCompletedAt TEXT skills TEXT tools diff --git a/docs/generated/sqlite-schema/agent_chat_attachments.md b/docs/generated/sqlite-schema/agent_chat_attachments.md index beae5589f66..295a3024ceb 100644 --- a/docs/generated/sqlite-schema/agent_chat_attachments.md +++ b/docs/generated/sqlite-schema/agent_chat_attachments.md @@ -77,6 +77,7 @@ erDiagram varchar_255_ projectId FK INTEGER revision TEXT schema + varchar_16_ scope datetime_3_ setupCompletedAt TEXT skills TEXT tools diff --git a/docs/generated/sqlite-schema/agent_chat_subscriptions.md b/docs/generated/sqlite-schema/agent_chat_subscriptions.md index 9a2c0b2539a..84ed7ad5bc6 100644 --- a/docs/generated/sqlite-schema/agent_chat_subscriptions.md +++ b/docs/generated/sqlite-schema/agent_chat_subscriptions.md @@ -65,6 +65,7 @@ erDiagram varchar_255_ projectId FK INTEGER revision TEXT schema + varchar_16_ scope datetime_3_ setupCompletedAt TEXT skills TEXT tools diff --git a/docs/generated/sqlite-schema/agent_checkpoints.md b/docs/generated/sqlite-schema/agent_checkpoints.md index 3af00512ccc..9b964420d53 100644 --- a/docs/generated/sqlite-schema/agent_checkpoints.md +++ b/docs/generated/sqlite-schema/agent_checkpoints.md @@ -66,6 +66,7 @@ erDiagram varchar_255_ projectId FK INTEGER revision TEXT schema + varchar_16_ scope datetime_3_ setupCompletedAt TEXT skills TEXT tools diff --git a/docs/generated/sqlite-schema/agent_credential_dependency.md b/docs/generated/sqlite-schema/agent_credential_dependency.md index 0d7b62f90bd..5d65426e1cf 100644 --- a/docs/generated/sqlite-schema/agent_credential_dependency.md +++ b/docs/generated/sqlite-schema/agent_credential_dependency.md @@ -59,6 +59,7 @@ erDiagram varchar_255_ projectId FK INTEGER revision TEXT schema + varchar_16_ scope datetime_3_ setupCompletedAt TEXT skills TEXT tools diff --git a/docs/generated/sqlite-schema/agent_eval_dataset.md b/docs/generated/sqlite-schema/agent_eval_dataset.md index ecc23c2cc1d..1fca253a5c5 100644 --- a/docs/generated/sqlite-schema/agent_eval_dataset.md +++ b/docs/generated/sqlite-schema/agent_eval_dataset.md @@ -74,6 +74,7 @@ erDiagram varchar_255_ projectId FK INTEGER revision TEXT schema + varchar_16_ scope datetime_3_ setupCompletedAt TEXT skills TEXT tools diff --git a/docs/generated/sqlite-schema/agent_execution_threads.md b/docs/generated/sqlite-schema/agent_execution_threads.md index db855a5098d..3174f435166 100644 --- a/docs/generated/sqlite-schema/agent_execution_threads.md +++ b/docs/generated/sqlite-schema/agent_execution_threads.md @@ -102,6 +102,7 @@ erDiagram varchar_255_ projectId FK INTEGER revision TEXT schema + varchar_16_ scope datetime_3_ setupCompletedAt TEXT skills TEXT tools diff --git a/docs/generated/sqlite-schema/agent_files.md b/docs/generated/sqlite-schema/agent_files.md index 30c1e4dd3ba..2aff17b7b96 100644 --- a/docs/generated/sqlite-schema/agent_files.md +++ b/docs/generated/sqlite-schema/agent_files.md @@ -73,6 +73,7 @@ erDiagram varchar_255_ projectId FK INTEGER revision TEXT schema + varchar_16_ scope datetime_3_ setupCompletedAt TEXT skills TEXT tools diff --git a/docs/generated/sqlite-schema/agent_history.md b/docs/generated/sqlite-schema/agent_history.md index b157fab3042..f8e84c9c323 100644 --- a/docs/generated/sqlite-schema/agent_history.md +++ b/docs/generated/sqlite-schema/agent_history.md @@ -74,6 +74,7 @@ erDiagram varchar_255_ projectId FK INTEGER revision TEXT schema + varchar_16_ scope datetime_3_ setupCompletedAt TEXT skills TEXT tools diff --git a/docs/generated/sqlite-schema/agent_skill_dependency.md b/docs/generated/sqlite-schema/agent_skill_dependency.md index 114d3a3998e..c341daa1073 100644 --- a/docs/generated/sqlite-schema/agent_skill_dependency.md +++ b/docs/generated/sqlite-schema/agent_skill_dependency.md @@ -63,6 +63,7 @@ erDiagram varchar_255_ projectId FK INTEGER revision TEXT schema + varchar_16_ scope datetime_3_ setupCompletedAt TEXT skills TEXT tools diff --git a/docs/generated/sqlite-schema/agent_task_definition.md b/docs/generated/sqlite-schema/agent_task_definition.md index 48bac946715..bbfe9b09fe3 100644 --- a/docs/generated/sqlite-schema/agent_task_definition.md +++ b/docs/generated/sqlite-schema/agent_task_definition.md @@ -66,6 +66,7 @@ erDiagram varchar_255_ projectId FK INTEGER revision TEXT schema + varchar_16_ scope datetime_3_ setupCompletedAt TEXT skills TEXT tools diff --git a/docs/generated/sqlite-schema/agent_task_run_lock.md b/docs/generated/sqlite-schema/agent_task_run_lock.md index 4645d32fa85..c194d594998 100644 --- a/docs/generated/sqlite-schema/agent_task_run_lock.md +++ b/docs/generated/sqlite-schema/agent_task_run_lock.md @@ -62,6 +62,7 @@ erDiagram varchar_255_ projectId FK INTEGER revision TEXT schema + varchar_16_ scope datetime_3_ setupCompletedAt TEXT skills TEXT tools diff --git a/docs/generated/sqlite-schema/agent_workflow_dependency.md b/docs/generated/sqlite-schema/agent_workflow_dependency.md index 4725ec273ca..5b74a67a461 100644 --- a/docs/generated/sqlite-schema/agent_workflow_dependency.md +++ b/docs/generated/sqlite-schema/agent_workflow_dependency.md @@ -59,6 +59,7 @@ erDiagram varchar_255_ projectId FK INTEGER revision TEXT schema + varchar_16_ scope datetime_3_ setupCompletedAt TEXT skills TEXT tools diff --git a/docs/generated/sqlite-schema/agents.md b/docs/generated/sqlite-schema/agents.md index b7feda944a9..3c29aeabe19 100644 --- a/docs/generated/sqlite-schema/agents.md +++ b/docs/generated/sqlite-schema/agents.md @@ -6,7 +6,7 @@ Table Definition ```sql -CREATE TABLE "agents" ("id" varchar(36) PRIMARY KEY NOT NULL, "name" varchar(128) NOT NULL, "projectId" varchar(255) NOT NULL, "integrations" text NOT NULL DEFAULT ('[]'), "schema" text, "tools" text NOT NULL DEFAULT ('{}'), "skills" text NOT NULL DEFAULT ('{}'), "versionId" varchar(36), "createdAt" datetime(3) NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), "updatedAt" datetime(3) NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), "activeVersionId" varchar(36), "availableInMCP" boolean NOT NULL DEFAULT (false), "setupCompletedAt" datetime(3), "revision" integer NOT NULL DEFAULT 0, CONSTRAINT "FK_940597dfe9753375309ce6aeea0" FOREIGN KEY ("activeVersionId") REFERENCES "agent_history" ("versionId") ON DELETE SET NULL ON UPDATE NO ACTION, CONSTRAINT "FK_a30d560207c4071d98aa03c179c" FOREIGN KEY ("projectId") REFERENCES "project" ("id") ON DELETE CASCADE ON UPDATE NO ACTION) +CREATE TABLE "agents" ("id" varchar(36) PRIMARY KEY NOT NULL, "name" varchar(128) NOT NULL, "projectId" varchar(255), "integrations" text NOT NULL DEFAULT ('[]'), "schema" text, "tools" text NOT NULL DEFAULT ('{}'), "skills" text NOT NULL DEFAULT ('{}'), "versionId" varchar(36), "createdAt" datetime(3) NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), "updatedAt" datetime(3) NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), "activeVersionId" varchar(36), "availableInMCP" boolean NOT NULL DEFAULT (false), "setupCompletedAt" datetime(3), "revision" integer NOT NULL DEFAULT (0), "scope" varchar(16) NOT NULL DEFAULT ('project'), CONSTRAINT "CHK_agents_scope" CHECK (("scope" IN ('project', 'instance'))), CONSTRAINT "CHK_agents_scope_project" CHECK (("scope" = 'instance') = ("projectId" IS NULL)), CONSTRAINT "FK_940597dfe9753375309ce6aeea0" FOREIGN KEY ("activeVersionId") REFERENCES "agent_history" ("versionId") ON DELETE SET NULL ON UPDATE NO ACTION, CONSTRAINT "FK_a30d560207c4071d98aa03c179c" FOREIGN KEY ("projectId") REFERENCES "project" ("id") ON DELETE CASCADE ON UPDATE NO ACTION) ``` @@ -21,9 +21,10 @@ CREATE TABLE "agents" ("id" varchar(36) PRIMARY KEY NOT NULL, "name" varchar(128 | id | varchar(36) | | false | [agent_background_job](agent_background_job.md) [agent_channel_status](agent_channel_status.md) [agent_chat_attachments](agent_chat_attachments.md) [agent_chat_subscriptions](agent_chat_subscriptions.md) [agent_checkpoints](agent_checkpoints.md) [agent_credential_dependency](agent_credential_dependency.md) [agent_eval_dataset](agent_eval_dataset.md) [agent_execution_threads](agent_execution_threads.md) [agent_files](agent_files.md) [agent_history](agent_history.md) [agent_skill_dependency](agent_skill_dependency.md) [agent_task_definition](agent_task_definition.md) [agent_task_run_lock](agent_task_run_lock.md) [agent_workflow_dependency](agent_workflow_dependency.md) [agents_memory_entries](agents_memory_entries.md) [agents_memory_entry_candidates](agents_memory_entry_candidates.md) [agents_memory_entry_locks](agents_memory_entry_locks.md) [agents_memory_entry_sources](agents_memory_entry_sources.md) [agents_observation_cursors](agents_observation_cursors.md) [agents_observation_locks](agents_observation_locks.md) [agents_observations](agents_observations.md) [skill](skill.md) | | | | integrations | TEXT | '[]' | false | | | | | name | varchar(128) | | false | | | | -| projectId | varchar(255) | | false | | [project](project.md) | | +| projectId | varchar(255) | | true | | [project](project.md) | | | revision | INTEGER | 0 | false | | | | | schema | TEXT | | true | | | | +| scope | varchar(16) | 'project' | false | | | | | setupCompletedAt | datetime(3) | | true | | | | | skills | TEXT | '{}' | false | | | | | tools | TEXT | '{}' | false | | | | @@ -34,6 +35,8 @@ CREATE TABLE "agents" ("id" varchar(36) PRIMARY KEY NOT NULL, "name" varchar(128 | Name | Type | Definition | | ---- | ---- | ---------- | +| - | CHECK | CHECK (("scope" IN ('project', 'instance'))) | +| - | CHECK | CHECK (("scope" = 'instance') = ("projectId" IS NULL)) | | - (Foreign key ID: 0) | FOREIGN KEY | FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE | | - (Foreign key ID: 1) | FOREIGN KEY | FOREIGN KEY (activeVersionId) REFERENCES agent_history (versionId) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE | | id | PRIMARY KEY | PRIMARY KEY (id) | @@ -75,7 +78,7 @@ erDiagram "agents_observation_locks" |o--|| "agents" : "FOREIGN KEY (agentId) REFERENCES agents (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE" "agents_observations" }o--|| "agents" : "FOREIGN KEY (agentId) REFERENCES agents (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE" "skill" }o--o| "agents" : "FOREIGN KEY (agentId) REFERENCES agents (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE" -"agents" }o--|| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE" +"agents" }o--o| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE" "agents" { varchar_36_ activeVersionId FK @@ -87,6 +90,7 @@ erDiagram varchar_255_ projectId FK INTEGER revision TEXT schema + varchar_16_ scope datetime_3_ setupCompletedAt TEXT skills TEXT tools diff --git a/docs/generated/sqlite-schema/agents_memory_entries.md b/docs/generated/sqlite-schema/agents_memory_entries.md index 7879d5a3d33..5094019bcdb 100644 --- a/docs/generated/sqlite-schema/agents_memory_entries.md +++ b/docs/generated/sqlite-schema/agents_memory_entries.md @@ -85,6 +85,7 @@ erDiagram varchar_255_ projectId FK INTEGER revision TEXT schema + varchar_16_ scope datetime_3_ setupCompletedAt TEXT skills TEXT tools diff --git a/docs/generated/sqlite-schema/agents_memory_entry_candidates.md b/docs/generated/sqlite-schema/agents_memory_entry_candidates.md index 7694fb712b9..8879e4773b2 100644 --- a/docs/generated/sqlite-schema/agents_memory_entry_candidates.md +++ b/docs/generated/sqlite-schema/agents_memory_entry_candidates.md @@ -90,6 +90,7 @@ erDiagram varchar_255_ projectId FK INTEGER revision TEXT schema + varchar_16_ scope datetime_3_ setupCompletedAt TEXT skills TEXT tools diff --git a/docs/generated/sqlite-schema/agents_memory_entry_locks.md b/docs/generated/sqlite-schema/agents_memory_entry_locks.md index be2dd9e55e3..fdc863f4813 100644 --- a/docs/generated/sqlite-schema/agents_memory_entry_locks.md +++ b/docs/generated/sqlite-schema/agents_memory_entry_locks.md @@ -65,6 +65,7 @@ erDiagram varchar_255_ projectId FK INTEGER revision TEXT schema + varchar_16_ scope datetime_3_ setupCompletedAt TEXT skills TEXT tools diff --git a/docs/generated/sqlite-schema/agents_memory_entry_sources.md b/docs/generated/sqlite-schema/agents_memory_entry_sources.md index 485afc513a9..d76d2acd15e 100644 --- a/docs/generated/sqlite-schema/agents_memory_entry_sources.md +++ b/docs/generated/sqlite-schema/agents_memory_entry_sources.md @@ -84,6 +84,7 @@ erDiagram varchar_255_ projectId FK INTEGER revision TEXT schema + varchar_16_ scope datetime_3_ setupCompletedAt TEXT skills TEXT tools diff --git a/docs/generated/sqlite-schema/agents_observation_cursors.md b/docs/generated/sqlite-schema/agents_observation_cursors.md index ee80c6bd989..ce4df78da7d 100644 --- a/docs/generated/sqlite-schema/agents_observation_cursors.md +++ b/docs/generated/sqlite-schema/agents_observation_cursors.md @@ -65,6 +65,7 @@ erDiagram varchar_255_ projectId FK INTEGER revision TEXT schema + varchar_16_ scope datetime_3_ setupCompletedAt TEXT skills TEXT tools diff --git a/docs/generated/sqlite-schema/agents_observation_locks.md b/docs/generated/sqlite-schema/agents_observation_locks.md index 6a9d479c37e..bb9549de9f6 100644 --- a/docs/generated/sqlite-schema/agents_observation_locks.md +++ b/docs/generated/sqlite-schema/agents_observation_locks.md @@ -69,6 +69,7 @@ erDiagram varchar_255_ projectId FK INTEGER revision TEXT schema + varchar_16_ scope datetime_3_ setupCompletedAt TEXT skills TEXT tools diff --git a/docs/generated/sqlite-schema/agents_observations.md b/docs/generated/sqlite-schema/agents_observations.md index c72bd8bd06e..2c63954a9aa 100644 --- a/docs/generated/sqlite-schema/agents_observations.md +++ b/docs/generated/sqlite-schema/agents_observations.md @@ -84,6 +84,7 @@ erDiagram varchar_255_ projectId FK INTEGER revision TEXT schema + varchar_16_ scope datetime_3_ setupCompletedAt TEXT skills TEXT tools diff --git a/docs/generated/sqlite-schema/project.md b/docs/generated/sqlite-schema/project.md index 46f39c7fb0b..6dd22d0d03f 100644 --- a/docs/generated/sqlite-schema/project.md +++ b/docs/generated/sqlite-schema/project.md @@ -48,7 +48,7 @@ erDiagram "activity_event" }o--|| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE" "agent_chat_attachments" }o--|| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE" "agent_execution_threads" }o--|| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE" -"agents" }o--|| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE" +"agents" }o--o| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE" "ai_preference" }o--o| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE" "data_table" }o--|| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE" "folder" }o--|| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE" @@ -154,6 +154,7 @@ erDiagram varchar_255_ projectId FK INTEGER revision TEXT schema + varchar_16_ scope datetime_3_ setupCompletedAt TEXT skills TEXT tools diff --git a/docs/generated/sqlite-schema/skill.md b/docs/generated/sqlite-schema/skill.md index 8be4273cb95..d26335ea399 100644 --- a/docs/generated/sqlite-schema/skill.md +++ b/docs/generated/sqlite-schema/skill.md @@ -73,6 +73,7 @@ erDiagram varchar_255_ projectId FK INTEGER revision TEXT schema + varchar_16_ scope datetime_3_ setupCompletedAt TEXT skills TEXT tools diff --git a/packages/@n8n/db/src/migrations/common/1792051765225-AddInstanceScopeToAgents.ts b/packages/@n8n/db/src/migrations/common/1792051765225-AddInstanceScopeToAgents.ts new file mode 100644 index 00000000000..f00bb8dc974 --- /dev/null +++ b/packages/@n8n/db/src/migrations/common/1792051765225-AddInstanceScopeToAgents.ts @@ -0,0 +1,74 @@ +import { TableCheck } from '@n8n/typeorm'; + +import type { MigrationContext, ReversibleMigration } from '../migration-types'; + +const table = 'agents'; +const scopeCheck = 'agents_scope_project'; + +/** + * Adds instance-scoped agents. An instance agent belongs to no project, so + * its `projectId` is null. Its runtime comes from code, and each of its + * threads carries its own working project. A CHECK constraint makes sure that + * `projectId` is null exactly for instance agents. + */ +export class AddInstanceScopeToAgents1792051765225 implements ReversibleMigration { + async up(context: MigrationContext) { + const { + schemaBuilder: { addColumns, column, dropNotNull }, + } = context; + await addColumns( + table, + [ + column('scope') + .varchar(16) + .notNull.default("'project'") + .withEnumCheck(['project', 'instance']) + .comment( + 'project: belongs to projectId; instance: code-defined, belongs to no project (projectId is null)', + ), + ], + { recreatesOnSqlite: true }, + ); + await dropNotNull(table, 'projectId', { recreatesOnSqlite: true }); + await this.addScopeCheck(context); + } + + async down(context: MigrationContext) { + const { + schemaBuilder: { addNotNull, dropColumns, dropEnumCheck }, + } = context; + // The old schema cannot store an agent without a project. + // `DeleteInstanceAgentsOnRevert` already deleted these agents and their + // dependent rows with foreign keys on. This delete is a fallback. + await this.deleteInstanceAgents(context); + await this.dropScopeCheck(context); + await addNotNull(table, 'projectId', { recreatesOnSqlite: true }); + await dropEnumCheck(table, 'scope', { recreatesOnSqlite: true }); + await dropColumns(table, ['scope'], { recreatesOnSqlite: true }); + } + + private async addScopeCheck({ escape, queryRunner, tablePrefix }: MigrationContext) { + const scope = escape.columnName('scope'); + const projectId = escape.columnName('projectId'); + await queryRunner.createCheckConstraint( + `${tablePrefix}${table}`, + new TableCheck({ + name: `CHK_${tablePrefix}${scopeCheck}`, + expression: `(${scope} = 'instance') = (${projectId} IS NULL)`, + }), + ); + } + + private async dropScopeCheck({ queryRunner, tablePrefix }: MigrationContext) { + await queryRunner.dropCheckConstraint( + `${tablePrefix}${table}`, + `CHK_${tablePrefix}${scopeCheck}`, + ); + } + + private async deleteInstanceAgents({ escape, runQuery }: MigrationContext) { + await runQuery( + `DELETE FROM ${escape.tableName(table)} WHERE ${escape.columnName('projectId')} IS NULL`, + ); + } +} diff --git a/packages/@n8n/db/src/migrations/common/1792139680416-DeleteInstanceAgentsOnRevert.ts b/packages/@n8n/db/src/migrations/common/1792139680416-DeleteInstanceAgentsOnRevert.ts new file mode 100644 index 00000000000..06ecbbfd640 --- /dev/null +++ b/packages/@n8n/db/src/migrations/common/1792139680416-DeleteInstanceAgentsOnRevert.ts @@ -0,0 +1,19 @@ +import type { MigrationContext, ReversibleMigration } from '../migration-types'; + +/** + * Runs before `AddInstanceScopeToAgents` on revert. That migration recreates + * `agents` with foreign keys off on SQLite, so deleting instance agents there + * does not cascade. This migration keeps foreign keys on and deletes them + * first, so the cascades also delete the rows that depend on them. + */ +export class DeleteInstanceAgentsOnRevert1792139680416 implements ReversibleMigration { + async up() { + // Nothing to change on upgrade. This migration only does work on revert. + } + + async down({ escape, runQuery }: MigrationContext) { + await runQuery( + `DELETE FROM ${escape.tableName('agents')} WHERE ${escape.columnName('scope')} = 'instance'`, + ); + } +} diff --git a/packages/@n8n/db/src/migrations/sqlite/1792051765225-AddInstanceScopeToAgents.ts b/packages/@n8n/db/src/migrations/sqlite/1792051765225-AddInstanceScopeToAgents.ts new file mode 100644 index 00000000000..64811b8367f --- /dev/null +++ b/packages/@n8n/db/src/migrations/sqlite/1792051765225-AddInstanceScopeToAgents.ts @@ -0,0 +1,11 @@ +import { AddInstanceScopeToAgents1792051765225 as BaseMigration } from '../common/1792051765225-AddInstanceScopeToAgents'; + +/** + * `agents` has incoming cascading foreign keys. Recreating it must not delete + * their rows, so foreign keys are off for this migration. On revert, + * `DeleteInstanceAgentsOnRevert` deletes instance agents and their dependent + * rows first, with foreign keys on. + */ +export class AddInstanceScopeToAgents1792051765225 extends BaseMigration { + withFKsDisabled = true as const; +} diff --git a/packages/cli/test/migration/1792051765225-add-instance-scope-to-agents.test.ts b/packages/cli/test/migration/1792051765225-add-instance-scope-to-agents.test.ts new file mode 100644 index 00000000000..b9cd0353b46 --- /dev/null +++ b/packages/cli/test/migration/1792051765225-add-instance-scope-to-agents.test.ts @@ -0,0 +1,202 @@ +import { + createTestMigrationContext, + initDbUpToMigration, + runSingleMigration, + undoLastSingleMigration, + type TestMigrationContext, +} from '@n8n/backend-test-utils'; +import { DbConnection } from '@n8n/db'; +import { Container } from '@n8n/di'; +import { DataSource } from '@n8n/typeorm'; +import { randomUUID } from 'node:crypto'; + +const migrationName = 'AddInstanceScopeToAgents1792051765225'; + +describe('AddInstanceScopeToAgents migration', () => { + let dataSource: DataSource; + + beforeAll(async () => { + await Container.get(DbConnection).init(); + dataSource = Container.get(DataSource); + }); + + beforeEach(async () => { + await withContext(async ({ queryRunner }) => await queryRunner.clearDatabase()); + await initDbUpToMigration(migrationName); + }); + + afterAll(async () => { + await Container.get(DbConnection).close(); + }); + + async function withContext(run: (context: TestMigrationContext) => Promise): Promise { + const context = createTestMigrationContext(dataSource); + try { + return await run(context); + } finally { + await context.queryRunner.release(); + } + } + + function columns(context: TestMigrationContext, names: string[]) { + return names.map((name) => context.escape.columnName(name)).join(', '); + } + + async function insertProject(context: TestMigrationContext) { + const id = randomUUID(); + await context.runQuery( + `INSERT INTO ${context.escape.tableName('project')} (${columns(context, ['id', 'name', 'type', 'createdAt', 'updatedAt'])}) + VALUES (:id, 'Agents project', 'team', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`, + { id }, + ); + return id; + } + + async function insertAgent( + context: TestMigrationContext, + projectId: string | null, + scope?: string, + ) { + const id = randomUUID(); + const names = ['id', 'name', 'projectId', 'createdAt', 'updatedAt']; + const values = [':id', "'Agent'", ':projectId', 'CURRENT_TIMESTAMP', 'CURRENT_TIMESTAMP']; + if (scope !== undefined) { + names.push('scope'); + values.push(':scope'); + } + await context.runQuery( + `INSERT INTO ${context.escape.tableName('agents')} (${columns(context, names)}) VALUES (${values.join(', ')})`, + { id, projectId, scope }, + ); + return id; + } + + /** A thread with one execution: rows that depend on the agent at two levels. */ + async function insertThreadWithExecution( + context: TestMigrationContext, + agentId: string, + projectId: string, + ) { + const threadId = randomUUID(); + await context.runQuery( + `INSERT INTO ${context.escape.tableName('agent_execution_threads')} + (${columns(context, ['id', 'agentId', 'agentName', 'projectId', 'createdAt', 'updatedAt'])}) + VALUES (:threadId, :agentId, 'Agent', :projectId, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`, + { threadId, agentId, projectId }, + ); + await context.runQuery( + `INSERT INTO ${context.escape.tableName('agent_execution')} + (${columns(context, ['id', 'threadId', 'status', 'createdAt', 'updatedAt'])}) + VALUES (:id, :threadId, 'success', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`, + { id: randomUUID(), threadId }, + ); + return threadId; + } + + async function readIds(context: TestMigrationContext, table: string) { + const rows = await context.runQuery>( + `SELECT ${context.escape.columnName('id')} AS id FROM ${context.escape.tableName(table)}`, + ); + return rows.map((row) => row.id).sort(); + } + + async function readAgent(context: TestMigrationContext, id: string) { + const [row] = await context.runQuery>( + `SELECT ${columns(context, ['scope', 'projectId'])} FROM ${context.escape.tableName('agents')} WHERE ${context.escape.columnName('id')} = :id`, + { id }, + ); + return row; + } + + it('marks existing agents as project agents and keeps their dependent rows', async () => { + const { agentId, threadId } = await withContext(async (context) => { + const projectId = await insertProject(context); + const agentId = await insertAgent(context, projectId); + const threadId = await insertThreadWithExecution(context, agentId, projectId); + return { agentId, threadId }; + }); + + await runSingleMigration(migrationName); + + await withContext(async (context) => { + expect(await readAgent(context, agentId)).toMatchObject({ scope: 'project' }); + expect(await readIds(context, 'agent_execution_threads')).toEqual([threadId]); + expect(await readIds(context, 'agent_execution')).toHaveLength(1); + }); + }); + + it('stores an instance agent without a project and rejects an unknown scope', async () => { + await runSingleMigration(migrationName); + + await withContext(async (context) => { + const agentId = await insertAgent(context, null, 'instance'); + expect(await readAgent(context, agentId)).toEqual({ scope: 'instance', projectId: null }); + + const projectId = await insertProject(context); + await expect(insertAgent(context, projectId, 'global')).rejects.toThrow(); + }); + }); + + it('rejects a scope that does not match the project', async () => { + await runSingleMigration(migrationName); + + await withContext(async (context) => { + const projectId = await insertProject(context); + // A project agent needs a project. + await expect(insertAgent(context, null)).rejects.toThrow(); + await expect(insertAgent(context, null, 'project')).rejects.toThrow(); + // An instance agent belongs to no project. + await expect(insertAgent(context, projectId, 'instance')).rejects.toThrow(); + expect(await readIds(context, 'agents')).toEqual([]); + }); + }); + + it('rejects an agent without a project before the migration', async () => { + await withContext(async (context) => { + await expect(insertAgent(context, null)).rejects.toThrow(); + }); + }); + + it('deletes instance agents on revert, and keeps project agents with their rows', async () => { + await runSingleMigration(migrationName); + const kept = await withContext(async (context) => { + const projectId = await insertProject(context); + const projectAgentId = await insertAgent(context, projectId); + const projectThreadId = await insertThreadWithExecution(context, projectAgentId, projectId); + // DeleteInstanceAgentsOnRevert deletes the rows that depend on instance agents first. + await insertAgent(context, null, 'instance'); + return { projectAgentId, projectThreadId }; + }); + + await undoLastSingleMigration(); + + await withContext(async (context) => { + expect(await readIds(context, 'agents')).toEqual([kept.projectAgentId]); + expect(await readIds(context, 'agent_execution_threads')).toEqual([kept.projectThreadId]); + expect(await readIds(context, 'agent_execution')).toHaveLength(1); + if (context.isSqlite) { + expect(await context.runQuery('PRAGMA foreign_key_check')).toEqual([]); + } + await expect(insertAgent(context, null)).rejects.toThrow(); + // The foreign keys still hold after the table was recreated. + await expect( + insertThreadWithExecution(context, randomUUID(), await insertProject(context)), + ).rejects.toThrow(); + }); + }); + + it('can apply again after a revert', async () => { + const agentId = await withContext(async (context) => { + return await insertAgent(context, await insertProject(context)); + }); + + await runSingleMigration(migrationName); + await undoLastSingleMigration(); + await runSingleMigration(migrationName); + + await withContext(async (context) => { + expect(await readAgent(context, agentId)).toMatchObject({ scope: 'project' }); + await expect(insertAgent(context, null, 'instance')).resolves.toBeDefined(); + }); + }); +}); diff --git a/packages/cli/test/migration/1792139680416-delete-instance-agents-on-revert.test.ts b/packages/cli/test/migration/1792139680416-delete-instance-agents-on-revert.test.ts new file mode 100644 index 00000000000..ece01255810 --- /dev/null +++ b/packages/cli/test/migration/1792139680416-delete-instance-agents-on-revert.test.ts @@ -0,0 +1,138 @@ +import { + createTestMigrationContext, + initDbUpToMigration, + runSingleMigration, + undoLastSingleMigration, + type TestMigrationContext, +} from '@n8n/backend-test-utils'; +import { DbConnection } from '@n8n/db'; +import { Container } from '@n8n/di'; +import { DataSource } from '@n8n/typeorm'; +import { randomUUID } from 'node:crypto'; + +const migrationName = 'DeleteInstanceAgentsOnRevert1792139680416'; + +describe('DeleteInstanceAgentsOnRevert migration', () => { + let dataSource: DataSource; + + beforeAll(async () => { + await Container.get(DbConnection).init(); + dataSource = Container.get(DataSource); + }); + + beforeEach(async () => { + await withContext(async ({ queryRunner }) => await queryRunner.clearDatabase()); + await initDbUpToMigration(migrationName); + }); + + afterAll(async () => { + await Container.get(DbConnection).close(); + }); + + async function withContext(run: (context: TestMigrationContext) => Promise): Promise { + const context = createTestMigrationContext(dataSource); + try { + return await run(context); + } finally { + await context.queryRunner.release(); + } + } + + function columns(context: TestMigrationContext, names: string[]) { + return names.map((name) => context.escape.columnName(name)).join(', '); + } + + async function insertProject(context: TestMigrationContext) { + const id = randomUUID(); + await context.runQuery( + `INSERT INTO ${context.escape.tableName('project')} (${columns(context, ['id', 'name', 'type', 'createdAt', 'updatedAt'])}) + VALUES (:id, 'Agents project', 'team', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`, + { id }, + ); + return id; + } + + async function insertAgent(context: TestMigrationContext, projectId: string | null) { + const id = randomUUID(); + const scope = projectId === null ? 'instance' : 'project'; + await context.runQuery( + `INSERT INTO ${context.escape.tableName('agents')} (${columns(context, ['id', 'name', 'projectId', 'scope', 'createdAt', 'updatedAt'])}) + VALUES (:id, 'Agent', :projectId, :scope, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`, + { id, projectId, scope }, + ); + return id; + } + + /** A thread with one execution: rows that depend on the agent at two levels. */ + async function insertThreadWithExecution( + context: TestMigrationContext, + agentId: string, + projectId: string, + ) { + const threadId = randomUUID(); + const executionId = randomUUID(); + await context.runQuery( + `INSERT INTO ${context.escape.tableName('agent_execution_threads')} + (${columns(context, ['id', 'agentId', 'agentName', 'projectId', 'createdAt', 'updatedAt'])}) + VALUES (:threadId, :agentId, 'Agent', :projectId, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`, + { threadId, agentId, projectId }, + ); + await context.runQuery( + `INSERT INTO ${context.escape.tableName('agent_execution')} + (${columns(context, ['id', 'threadId', 'status', 'createdAt', 'updatedAt'])}) + VALUES (:executionId, :threadId, 'success', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`, + { executionId, threadId }, + ); + return { threadId, executionId }; + } + + async function readIds(context: TestMigrationContext, table: string) { + const rows = await context.runQuery>( + `SELECT ${context.escape.columnName('id')} AS id FROM ${context.escape.tableName(table)}`, + ); + return rows.map((row) => row.id).sort(); + } + + it('deletes instance agents and their dependent rows on revert', async () => { + await runSingleMigration(migrationName); + const kept = await withContext(async (context) => { + const projectId = await insertProject(context); + const projectAgentId = await insertAgent(context, projectId); + const projectRows = await insertThreadWithExecution(context, projectAgentId, projectId); + const instanceAgentId = await insertAgent(context, null); + await insertThreadWithExecution(context, instanceAgentId, projectId); + return { projectAgentId, ...projectRows }; + }); + + await undoLastSingleMigration(); + + await withContext(async (context) => { + expect(await readIds(context, 'agents')).toEqual([kept.projectAgentId]); + expect(await readIds(context, 'agent_execution_threads')).toEqual([kept.threadId]); + expect(await readIds(context, 'agent_execution')).toEqual([kept.executionId]); + if (context.isSqlite) { + expect(await context.runQuery('PRAGMA foreign_key_check')).toEqual([]); + } + }); + }); + + it('leaves no orphan rows after both migrations are reverted', async () => { + await runSingleMigration(migrationName); + await withContext(async (context) => { + const projectId = await insertProject(context); + await insertThreadWithExecution(context, await insertAgent(context, null), projectId); + }); + + await undoLastSingleMigration(); + await undoLastSingleMigration(); + + await withContext(async (context) => { + expect(await readIds(context, 'agents')).toEqual([]); + expect(await readIds(context, 'agent_execution_threads')).toEqual([]); + expect(await readIds(context, 'agent_execution')).toEqual([]); + if (context.isSqlite) { + expect(await context.runQuery('PRAGMA foreign_key_check')).toEqual([]); + } + }); + }); +});