@tickernelz/paperclip-pro-db 2026.1011.3 → 2026.1012.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. package/dist/backup-lib.d.ts +2 -0
  2. package/dist/backup-lib.d.ts.map +1 -1
  3. package/dist/backup-lib.js +52 -15
  4. package/dist/backup-lib.js.map +1 -1
  5. package/dist/backup-lib.test.js +64 -2
  6. package/dist/backup-lib.test.js.map +1 -1
  7. package/dist/client.d.ts.map +1 -1
  8. package/dist/client.js +81 -42
  9. package/dist/client.js.map +1 -1
  10. package/dist/client.test.js +33 -0
  11. package/dist/client.test.js.map +1 -1
  12. package/dist/connections-v3-schema-core-migration.test.js +3 -0
  13. package/dist/connections-v3-schema-core-migration.test.js.map +1 -1
  14. package/dist/heartbeat-run-issue-privacy-migration.test.d.ts +2 -0
  15. package/dist/heartbeat-run-issue-privacy-migration.test.d.ts.map +1 -0
  16. package/dist/heartbeat-run-issue-privacy-migration.test.js +265 -0
  17. package/dist/heartbeat-run-issue-privacy-migration.test.js.map +1 -0
  18. package/dist/index.d.ts +1 -0
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +1 -0
  21. package/dist/index.js.map +1 -1
  22. package/dist/migrations/0315_chunky_chamber.sql +3 -0
  23. package/dist/migrations/0316_lumpy_maria_hill.sql +157 -0
  24. package/dist/migrations/0317_colossal_otto_octavius.sql +24 -0
  25. package/dist/migrations/0318_supreme_garia.sql +1 -0
  26. package/dist/migrations/0319_curvy_shadow_king.sql +27 -0
  27. package/dist/migrations/0320_chubby_vin_gonzales.sql +7 -0
  28. package/dist/migrations/0321_familiar_titania.sql +4 -0
  29. package/dist/migrations/0322_cool_naoko.sql +3 -0
  30. package/dist/migrations/0323_whole_steel_serpent.sql +20 -0
  31. package/dist/migrations/0324_loving_the_hood.sql +77 -0
  32. package/dist/migrations/0325_agent_commentary.sql +33 -0
  33. package/dist/migrations/0326_mcp_file_transfers.sql +48 -0
  34. package/dist/migrations/0327_easy_eternity.sql +232 -0
  35. package/dist/migrations/0328_private_task_access.sql +278 -0
  36. package/dist/migrations/0329_private_task_draft_assets.sql +28 -0
  37. package/dist/migrations/0330_rapid_emma_frost.sql +44 -0
  38. package/dist/migrations/0331_premium_slayback.sql +81 -0
  39. package/dist/migrations/0332_messy_famine.sql +189 -0
  40. package/dist/migrations/0333_strong_blacklash.sql +23 -0
  41. package/dist/migrations/0334_heavy_captain_midlands.sql +15 -0
  42. package/dist/migrations/0335_daily_onslaught.sql +16 -0
  43. package/dist/migrations/0336_acoustic_human_fly.sql +1 -0
  44. package/dist/migrations/0337_reflective_kree.sql +98 -0
  45. package/dist/migrations/0338_petite_shockwave.sql +4 -0
  46. package/dist/migrations/0339_long_franklin_storm.sql +153 -0
  47. package/dist/migrations/0340_aberrant_machine_man.sql +6 -0
  48. package/dist/migrations/0341_blushing_screwball.sql +17 -0
  49. package/dist/migrations/0342_sad_proteus.sql +23 -0
  50. package/dist/migrations/0343_chunky_mentallo.sql +52 -0
  51. package/dist/migrations/0344_free_wolf_cub.sql +18 -0
  52. package/dist/migrations/0345_spooky_white_tiger.sql +7 -0
  53. package/dist/migrations/0346_panoramic_spyke.sql +74 -0
  54. package/dist/migrations/0347_little_night_nurse.sql +1 -0
  55. package/dist/migrations/meta/_journal.json +232 -1
  56. package/dist/private-task-draft-assets-migration.test.d.ts +2 -0
  57. package/dist/private-task-draft-assets-migration.test.d.ts.map +1 -0
  58. package/dist/private-task-draft-assets-migration.test.js +35 -0
  59. package/dist/private-task-draft-assets-migration.test.js.map +1 -0
  60. package/dist/question-response-history-migration.test.d.ts +2 -0
  61. package/dist/question-response-history-migration.test.d.ts.map +1 -0
  62. package/dist/question-response-history-migration.test.js +53 -0
  63. package/dist/question-response-history-migration.test.js.map +1 -0
  64. package/dist/schema/accounting.d.ts +1282 -0
  65. package/dist/schema/accounting.d.ts.map +1 -0
  66. package/dist/schema/accounting.js +120 -0
  67. package/dist/schema/accounting.js.map +1 -0
  68. package/dist/schema/adapter_auth_sessions.d.ts +2 -2
  69. package/dist/schema/agent_commentary.d.ts +181 -0
  70. package/dist/schema/agent_commentary.d.ts.map +1 -0
  71. package/dist/schema/agent_commentary.js +24 -0
  72. package/dist/schema/agent_commentary.js.map +1 -0
  73. package/dist/schema/agent_identity_keys.d.ts +131 -0
  74. package/dist/schema/agent_identity_keys.d.ts.map +1 -0
  75. package/dist/schema/agent_identity_keys.js +20 -0
  76. package/dist/schema/agent_identity_keys.js.map +1 -0
  77. package/dist/schema/agent_runtime_state.d.ts +2 -2
  78. package/dist/schema/agent_runtime_state.js +2 -2
  79. package/dist/schema/agent_runtime_state.js.map +1 -1
  80. package/dist/schema/agent_wakeup_requests.d.ts.map +1 -1
  81. package/dist/schema/agent_wakeup_requests.js +6 -0
  82. package/dist/schema/agent_wakeup_requests.js.map +1 -1
  83. package/dist/schema/agents.d.ts +132 -2
  84. package/dist/schema/agents.d.ts.map +1 -1
  85. package/dist/schema/agents.js +10 -2
  86. package/dist/schema/agents.js.map +1 -1
  87. package/dist/schema/ai_connection_defaults.d.ts +2 -2
  88. package/dist/schema/ai_connection_defaults.js +1 -1
  89. package/dist/schema/ai_connection_defaults.js.map +1 -1
  90. package/dist/schema/ai_connection_routing.d.ts +8 -8
  91. package/dist/schema/ai_provider_defaults.d.ts +2 -2
  92. package/dist/schema/ai_provider_defaults.js +1 -1
  93. package/dist/schema/ai_provider_defaults.js.map +1 -1
  94. package/dist/schema/ai_subscriptions.d.ts +619 -0
  95. package/dist/schema/ai_subscriptions.d.ts.map +1 -0
  96. package/dist/schema/ai_subscriptions.js +61 -0
  97. package/dist/schema/ai_subscriptions.js.map +1 -0
  98. package/dist/schema/budget_incidents.d.ts +2 -2
  99. package/dist/schema/budget_incidents.js +3 -3
  100. package/dist/schema/budget_incidents.js.map +1 -1
  101. package/dist/schema/budget_policies.d.ts +68 -0
  102. package/dist/schema/budget_policies.d.ts.map +1 -1
  103. package/dist/schema/budget_policies.js +7 -1
  104. package/dist/schema/budget_policies.js.map +1 -1
  105. package/dist/schema/chat_channels.d.ts +21 -4
  106. package/dist/schema/chat_channels.d.ts.map +1 -1
  107. package/dist/schema/chat_channels.js +3 -2
  108. package/dist/schema/chat_channels.js.map +1 -1
  109. package/dist/schema/chat_github.d.ts +108 -0
  110. package/dist/schema/chat_github.d.ts.map +1 -1
  111. package/dist/schema/chat_github.js +7 -0
  112. package/dist/schema/chat_github.js.map +1 -1
  113. package/dist/schema/chat_slack_registrations.d.ts +287 -0
  114. package/dist/schema/chat_slack_registrations.d.ts.map +1 -0
  115. package/dist/schema/chat_slack_registrations.js +26 -0
  116. package/dist/schema/chat_slack_registrations.js.map +1 -0
  117. package/dist/schema/chat_voice_callbacks.d.ts +128 -0
  118. package/dist/schema/chat_voice_callbacks.d.ts.map +1 -0
  119. package/dist/schema/chat_voice_callbacks.js +18 -0
  120. package/dist/schema/chat_voice_callbacks.js.map +1 -0
  121. package/dist/schema/chat_voice_inbound.d.ts +461 -0
  122. package/dist/schema/chat_voice_inbound.d.ts.map +1 -0
  123. package/dist/schema/chat_voice_inbound.js +45 -0
  124. package/dist/schema/chat_voice_inbound.js.map +1 -0
  125. package/dist/schema/chat_voice_reports.d.ts +184 -0
  126. package/dist/schema/chat_voice_reports.d.ts.map +1 -0
  127. package/dist/schema/chat_voice_reports.js +24 -0
  128. package/dist/schema/chat_voice_reports.js.map +1 -0
  129. package/dist/schema/chat_voice_sessions.d.ts +730 -0
  130. package/dist/schema/chat_voice_sessions.d.ts.map +1 -0
  131. package/dist/schema/chat_voice_sessions.js +85 -0
  132. package/dist/schema/chat_voice_sessions.js.map +1 -0
  133. package/dist/schema/companies.d.ts +19 -2
  134. package/dist/schema/companies.d.ts.map +1 -1
  135. package/dist/schema/companies.js +3 -2
  136. package/dist/schema/companies.js.map +1 -1
  137. package/dist/schema/cost_events.d.ts +141 -3
  138. package/dist/schema/cost_events.d.ts.map +1 -1
  139. package/dist/schema/cost_events.js +20 -3
  140. package/dist/schema/cost_events.js.map +1 -1
  141. package/dist/schema/decision_models.d.ts +579 -0
  142. package/dist/schema/decision_models.d.ts.map +1 -0
  143. package/dist/schema/decision_models.js +50 -0
  144. package/dist/schema/decision_models.js.map +1 -0
  145. package/dist/schema/dot_runner.d.ts +980 -0
  146. package/dist/schema/dot_runner.d.ts.map +1 -0
  147. package/dist/schema/dot_runner.js +82 -0
  148. package/dist/schema/dot_runner.js.map +1 -0
  149. package/dist/schema/fast_responses.d.ts +662 -0
  150. package/dist/schema/fast_responses.d.ts.map +1 -0
  151. package/dist/schema/fast_responses.js +65 -0
  152. package/dist/schema/fast_responses.js.map +1 -0
  153. package/dist/schema/finance_events.d.ts +36 -2
  154. package/dist/schema/finance_events.d.ts.map +1 -1
  155. package/dist/schema/finance_events.js +5 -2
  156. package/dist/schema/finance_events.js.map +1 -1
  157. package/dist/schema/heartbeat_runs.d.ts +138 -0
  158. package/dist/schema/heartbeat_runs.d.ts.map +1 -1
  159. package/dist/schema/heartbeat_runs.js +17 -1
  160. package/dist/schema/heartbeat_runs.js.map +1 -1
  161. package/dist/schema/index.d.ts +17 -0
  162. package/dist/schema/index.d.ts.map +1 -1
  163. package/dist/schema/index.js +17 -0
  164. package/dist/schema/index.js.map +1 -1
  165. package/dist/schema/issue_access_grants.d.ts +161 -0
  166. package/dist/schema/issue_access_grants.d.ts.map +1 -0
  167. package/dist/schema/issue_access_grants.js +21 -0
  168. package/dist/schema/issue_access_grants.js.map +1 -0
  169. package/dist/schema/issue_comments.d.ts +36 -0
  170. package/dist/schema/issue_comments.d.ts.map +1 -1
  171. package/dist/schema/issue_comments.js +2 -0
  172. package/dist/schema/issue_comments.js.map +1 -1
  173. package/dist/schema/issues.d.ts +51 -0
  174. package/dist/schema/issues.d.ts.map +1 -1
  175. package/dist/schema/issues.js +6 -0
  176. package/dist/schema/issues.js.map +1 -1
  177. package/dist/schema/plugin_lifecycle_acknowledgments.d.ts +59 -0
  178. package/dist/schema/plugin_lifecycle_acknowledgments.d.ts.map +1 -0
  179. package/dist/schema/plugin_lifecycle_acknowledgments.js +12 -0
  180. package/dist/schema/plugin_lifecycle_acknowledgments.js.map +1 -0
  181. package/dist/schema/project_access_members.d.ts +111 -0
  182. package/dist/schema/project_access_members.d.ts.map +1 -0
  183. package/dist/schema/project_access_members.js +18 -0
  184. package/dist/schema/project_access_members.js.map +1 -0
  185. package/dist/schema/projects.d.ts +51 -0
  186. package/dist/schema/projects.d.ts.map +1 -1
  187. package/dist/schema/projects.js +9 -1
  188. package/dist/schema/projects.js.map +1 -1
  189. package/dist/schema/public_mcp.d.ts +2100 -0
  190. package/dist/schema/public_mcp.d.ts.map +1 -0
  191. package/dist/schema/public_mcp.js +191 -0
  192. package/dist/schema/public_mcp.js.map +1 -0
  193. package/dist/schema/resource_lifecycle_events.d.ts +2 -2
  194. package/dist/schema/resource_lifecycle_events.d.ts.map +1 -1
  195. package/dist/schema/resource_lifecycle_events.js +2 -1
  196. package/dist/schema/resource_lifecycle_events.js.map +1 -1
  197. package/dist/schema/status_cards.d.ts +2 -2
  198. package/dist/schema/status_cards.js +2 -2
  199. package/dist/schema/status_cards.js.map +1 -1
  200. package/dist/schema/tool_access.d.ts +18 -0
  201. package/dist/schema/tool_access.d.ts.map +1 -1
  202. package/dist/schema/tool_access.js +3 -3
  203. package/dist/schema/tool_access.js.map +1 -1
  204. package/dist/schema/user_company_preferences.d.ts +127 -0
  205. package/dist/schema/user_company_preferences.d.ts.map +1 -0
  206. package/dist/schema/user_company_preferences.js +16 -0
  207. package/dist/schema/user_company_preferences.js.map +1 -0
  208. package/dist/speko-voice-migration.test.d.ts +2 -0
  209. package/dist/speko-voice-migration.test.d.ts.map +1 -0
  210. package/dist/speko-voice-migration.test.js +88 -0
  211. package/dist/speko-voice-migration.test.js.map +1 -0
  212. package/dist/work-signals.d.ts +20 -0
  213. package/dist/work-signals.d.ts.map +1 -0
  214. package/dist/work-signals.js +123 -0
  215. package/dist/work-signals.js.map +1 -0
  216. package/dist/work-signals.test.d.ts +2 -0
  217. package/dist/work-signals.test.d.ts.map +1 -0
  218. package/dist/work-signals.test.js +177 -0
  219. package/dist/work-signals.test.js.map +1 -0
  220. package/package.json +4 -4
  221. package/dist/seed.d.ts +0 -2
  222. package/dist/seed.d.ts.map +0 -1
  223. package/dist/seed.js +0 -91
  224. package/dist/seed.js.map +0 -1
@@ -0,0 +1,278 @@
1
+ -- Runs through the Paperclip migration executor outside a file-wide transaction.
2
+ -- Each idempotent keyset batch commits before advancing; history is recorded last.
3
+ CREATE TABLE IF NOT EXISTS "issue_access_grants" (
4
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
5
+ "issue_id" uuid NOT NULL,
6
+ "subject_type" text NOT NULL,
7
+ "subject_id" text NOT NULL,
8
+ "source" text NOT NULL,
9
+ "granted_by_user_id" text,
10
+ "granted_by_agent_id" uuid,
11
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
12
+ "revoked_at" timestamp with time zone,
13
+ CONSTRAINT "issue_access_grants_subject_type_check" CHECK ("issue_access_grants"."subject_type" in ('user', 'agent')),
14
+ CONSTRAINT "issue_access_grants_source_check" CHECK ("issue_access_grants"."source" in ('explicit', 'assignment', 'project'))
15
+ );
16
+ --> statement-breakpoint
17
+ CREATE TABLE IF NOT EXISTS "project_access_members" (
18
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
19
+ "company_id" uuid NOT NULL,
20
+ "project_id" uuid NOT NULL,
21
+ "subject_type" text NOT NULL,
22
+ "subject_id" text NOT NULL,
23
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
24
+ CONSTRAINT "project_access_members_subject_type_check" CHECK ("project_access_members"."subject_type" in ('user', 'agent'))
25
+ );
26
+ --> statement-breakpoint
27
+ ALTER TABLE "workspace_operations" DROP CONSTRAINT IF EXISTS "workspace_operations_heartbeat_run_id_heartbeat_runs_id_fk";
28
+ --> statement-breakpoint
29
+ ALTER TABLE "workspace_operations" DROP CONSTRAINT IF EXISTS "workspace_operations_issue_id_issues_id_fk";
30
+ --> statement-breakpoint
31
+ ALTER TABLE "heartbeat_runs" ADD COLUMN IF NOT EXISTS "scope_kind" text DEFAULT 'company' NOT NULL;--> statement-breakpoint
32
+ ALTER TABLE "heartbeat_runs" ADD COLUMN IF NOT EXISTS "issue_id" uuid;--> statement-breakpoint
33
+ ALTER TABLE "issues" ADD COLUMN IF NOT EXISTS "visibility" text DEFAULT 'open' NOT NULL;--> statement-breakpoint
34
+ ALTER TABLE "issues" ADD COLUMN IF NOT EXISTS "privacy_root_issue_id" uuid;--> statement-breakpoint
35
+ ALTER TABLE "issues" ADD COLUMN IF NOT EXISTS "privacy_parent_issue_id" uuid;--> statement-breakpoint
36
+ ALTER TABLE "projects" ADD COLUMN IF NOT EXISTS "visibility" text DEFAULT 'open' NOT NULL;--> statement-breakpoint
37
+ ALTER TABLE "projects" ADD COLUMN IF NOT EXISTS "personal_owner_user_id" text;--> statement-breakpoint
38
+ DO $$ BEGIN
39
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'issue_access_grants_issue_id_issues_id_fk' AND conrelid = 'public.issue_access_grants'::regclass) THEN
40
+ ALTER TABLE "issue_access_grants" ADD CONSTRAINT "issue_access_grants_issue_id_issues_id_fk" FOREIGN KEY ("issue_id") REFERENCES "public"."issues"("id") ON DELETE cascade ON UPDATE no action;
41
+ END IF;
42
+ END $$;--> statement-breakpoint
43
+ DO $$ BEGIN
44
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'issue_access_grants_granted_by_agent_id_agents_id_fk' AND conrelid = 'public.issue_access_grants'::regclass) THEN
45
+ ALTER TABLE "issue_access_grants" ADD CONSTRAINT "issue_access_grants_granted_by_agent_id_agents_id_fk" FOREIGN KEY ("granted_by_agent_id") REFERENCES "public"."agents"("id") ON DELETE set null ON UPDATE no action;
46
+ END IF;
47
+ END $$;--> statement-breakpoint
48
+ DO $$ BEGIN
49
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'project_access_members_company_id_companies_id_fk' AND conrelid = 'public.project_access_members'::regclass) THEN
50
+ ALTER TABLE "project_access_members" ADD CONSTRAINT "project_access_members_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action;
51
+ END IF;
52
+ END $$;--> statement-breakpoint
53
+ DO $$ BEGIN
54
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'project_access_members_project_id_projects_id_fk' AND conrelid = 'public.project_access_members'::regclass) THEN
55
+ ALTER TABLE "project_access_members" ADD CONSTRAINT "project_access_members_project_id_projects_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE cascade ON UPDATE no action;
56
+ END IF;
57
+ END $$;--> statement-breakpoint
58
+ CREATE INDEX IF NOT EXISTS "issue_access_grants_subject_active_issue_idx" ON "issue_access_grants" USING btree ("subject_type","subject_id","revoked_at","issue_id");--> statement-breakpoint
59
+ CREATE INDEX IF NOT EXISTS "issue_access_grants_issue_idx" ON "issue_access_grants" USING btree ("issue_id","created_at");--> statement-breakpoint
60
+ CREATE UNIQUE INDEX IF NOT EXISTS "project_access_members_project_subject_uq" ON "project_access_members" USING btree ("project_id","subject_type","subject_id");--> statement-breakpoint
61
+ CREATE INDEX IF NOT EXISTS "project_access_members_subject_lookup_idx" ON "project_access_members" USING btree ("company_id","subject_type","subject_id","project_id");--> statement-breakpoint
62
+ DO $$ BEGIN
63
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'heartbeat_runs_issue_id_issues_id_fk' AND conrelid = 'public.heartbeat_runs'::regclass) THEN
64
+ ALTER TABLE "heartbeat_runs" ADD CONSTRAINT "heartbeat_runs_issue_id_issues_id_fk" FOREIGN KEY ("issue_id") REFERENCES "public"."issues"("id") ON DELETE set null ON UPDATE no action;
65
+ END IF;
66
+ END $$;--> statement-breakpoint
67
+ DO $$ BEGIN
68
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'issues_privacy_root_issue_id_issues_id_fk' AND conrelid = 'public.issues'::regclass) THEN
69
+ ALTER TABLE "issues" ADD CONSTRAINT "issues_privacy_root_issue_id_issues_id_fk" FOREIGN KEY ("privacy_root_issue_id") REFERENCES "public"."issues"("id") ON DELETE no action ON UPDATE no action;
70
+ END IF;
71
+ END $$;--> statement-breakpoint
72
+ DO $$ BEGIN
73
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'issues_privacy_parent_issue_id_issues_id_fk' AND conrelid = 'public.issues'::regclass) THEN
74
+ ALTER TABLE "issues" ADD CONSTRAINT "issues_privacy_parent_issue_id_issues_id_fk" FOREIGN KEY ("privacy_parent_issue_id") REFERENCES "public"."issues"("id") ON DELETE set null ON UPDATE no action;
75
+ END IF;
76
+ END $$;--> statement-breakpoint
77
+ DO $$ BEGIN
78
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'workspace_operations_heartbeat_run_id_heartbeat_runs_id_fk' AND conrelid = 'public.workspace_operations'::regclass) THEN
79
+ ALTER TABLE "workspace_operations" ADD CONSTRAINT "workspace_operations_heartbeat_run_id_heartbeat_runs_id_fk" FOREIGN KEY ("heartbeat_run_id") REFERENCES "public"."heartbeat_runs"("id") ON DELETE set null ON UPDATE no action;
80
+ END IF;
81
+ END $$;--> statement-breakpoint
82
+ DO $$ BEGIN
83
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'workspace_operations_issue_id_issues_id_fk' AND conrelid = 'public.workspace_operations'::regclass) THEN
84
+ ALTER TABLE "workspace_operations" ADD CONSTRAINT "workspace_operations_issue_id_issues_id_fk" FOREIGN KEY ("issue_id") REFERENCES "public"."issues"("id") ON DELETE set null ON UPDATE no action;
85
+ END IF;
86
+ END $$;--> statement-breakpoint
87
+ CREATE INDEX CONCURRENTLY IF NOT EXISTS "heartbeat_runs_company_issue_created_idx" ON "heartbeat_runs" USING btree ("company_id","issue_id","created_at");--> statement-breakpoint
88
+ CREATE INDEX CONCURRENTLY IF NOT EXISTS "issues_company_privacy_parent_idx" ON "issues" USING btree ("company_id","privacy_parent_issue_id");--> statement-breakpoint
89
+ CREATE INDEX CONCURRENTLY IF NOT EXISTS "issues_company_privacy_root_idx" ON "issues" USING btree ("company_id","privacy_root_issue_id");--> statement-breakpoint
90
+ CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS "projects_company_personal_owner_uq" ON "projects" USING btree ("company_id","personal_owner_user_id") WHERE "projects"."personal_owner_user_id" is not null;--> statement-breakpoint
91
+ DO $$ BEGIN
92
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'heartbeat_runs_scope_binding_check' AND conrelid = 'public.heartbeat_runs'::regclass) THEN
93
+ ALTER TABLE "heartbeat_runs" ADD CONSTRAINT "heartbeat_runs_scope_binding_check" CHECK (("heartbeat_runs"."scope_kind" = 'company' AND "heartbeat_runs"."issue_id" IS NULL)
94
+ OR "heartbeat_runs"."scope_kind" = 'issue');
95
+ END IF;
96
+ END $$;--> statement-breakpoint
97
+ DO $$ BEGIN
98
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'issues_visibility_check' AND conrelid = 'public.issues'::regclass) THEN
99
+ ALTER TABLE "issues" ADD CONSTRAINT "issues_visibility_check" CHECK ("issues"."visibility" in ('open', 'private'));
100
+ END IF;
101
+ END $$;--> statement-breakpoint
102
+ DO $$ BEGIN
103
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'projects_visibility_check' AND conrelid = 'public.projects'::regclass) THEN
104
+ ALTER TABLE "projects" ADD CONSTRAINT "projects_visibility_check" CHECK ("projects"."visibility" in ('open', 'private'));
105
+ END IF;
106
+ END $$;
107
+ --> statement-breakpoint
108
+ CREATE OR REPLACE FUNCTION set_heartbeat_run_scope_kind() RETURNS trigger LANGUAGE plpgsql AS $$
109
+ DECLARE candidate uuid;
110
+ BEGIN
111
+ -- A private run never becomes a company run through context edits or deletion.
112
+ IF TG_OP = 'UPDATE' AND OLD.scope_kind = 'issue' THEN
113
+ IF OLD.issue_id IS NOT NULL AND NEW.issue_id IS NOT NULL AND OLD.issue_id <> NEW.issue_id THEN
114
+ RAISE EXCEPTION 'run task binding is immutable';
115
+ END IF;
116
+ IF OLD.issue_id IS NOT NULL AND NEW.native_issue_id IS NOT NULL AND OLD.issue_id <> NEW.native_issue_id THEN
117
+ RAISE EXCEPTION 'native run task binding disagrees with privacy binding';
118
+ END IF;
119
+ NEW.scope_kind := 'issue';
120
+ RETURN NEW;
121
+ END IF;
122
+ candidate := coalesce(NEW.native_issue_id, NEW.issue_id);
123
+ IF candidate IS NULL AND coalesce(NEW.context_snapshot->>'issueId', NEW.context_snapshot->>'taskId') ~* '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' THEN
124
+ candidate := coalesce(NEW.context_snapshot->>'issueId', NEW.context_snapshot->>'taskId')::uuid;
125
+ END IF;
126
+ IF candidate IS NOT NULL OR NEW.context_snapshot ? 'issueId' OR NEW.context_snapshot ? 'taskId' OR NEW.scope_kind = 'issue' THEN
127
+ NEW.scope_kind := 'issue';
128
+ SELECT id INTO NEW.issue_id FROM issues WHERE id = candidate AND company_id = NEW.company_id;
129
+ ELSE
130
+ NEW.scope_kind := 'company';
131
+ END IF;
132
+ RETURN NEW;
133
+ END $$;
134
+ --> statement-breakpoint
135
+ DO $$ BEGIN
136
+ DROP TRIGGER IF EXISTS heartbeat_runs_set_scope_kind ON heartbeat_runs;
137
+ CREATE TRIGGER heartbeat_runs_set_scope_kind BEFORE INSERT OR UPDATE OF issue_id, native_issue_id, context_snapshot, scope_kind
138
+ ON heartbeat_runs FOR EACH ROW EXECUTE FUNCTION set_heartbeat_run_scope_kind();
139
+ END $$;
140
+ --> statement-breakpoint
141
+ -- Indexed primary-key keyset batches visit each historical run once. Missing
142
+ -- source tasks become issue-scoped tombstones, never company-visible run content.
143
+ DO $$ DECLARE cursor_id uuid := '00000000-0000-0000-0000-000000000000'; next_id uuid;
144
+ BEGIN LOOP
145
+ WITH batch AS MATERIALIZED (SELECT id FROM heartbeat_runs WHERE id > cursor_id ORDER BY id LIMIT 1000),
146
+ changed AS (UPDATE heartbeat_runs r SET scope_kind = r.scope_kind FROM batch b WHERE r.id = b.id RETURNING r.id)
147
+ SELECT id INTO next_id FROM changed ORDER BY id DESC LIMIT 1;
148
+ EXIT WHEN next_id IS NULL;
149
+ cursor_id := next_id;
150
+ COMMIT;
151
+ END LOOP; END $$;
152
+ --> statement-breakpoint
153
+ -- Existing structural children acquire downward inheritance without widening
154
+ -- any task's visibility. New provenance edges are written at task creation.
155
+ DO $$ DECLARE cursor_id uuid := '00000000-0000-0000-0000-000000000000'; next_id uuid;
156
+ BEGIN LOOP
157
+ WITH batch AS MATERIALIZED (SELECT id FROM issues WHERE id > cursor_id ORDER BY id LIMIT 1000),
158
+ changed AS (UPDATE issues i SET privacy_parent_issue_id = coalesce(i.privacy_parent_issue_id, i.parent_id)
159
+ FROM batch b WHERE i.id = b.id RETURNING i.id)
160
+ SELECT id INTO next_id FROM changed ORDER BY id DESC LIMIT 1;
161
+ EXIT WHEN next_id IS NULL;
162
+ cursor_id := next_id;
163
+ COMMIT;
164
+ END LOOP; END $$;
165
+ --> statement-breakpoint
166
+ -- Keep workspace provenance after reassignment/deletion. A previous task may
167
+ -- have left files behind; losing the live FK must not publish those files.
168
+ CREATE OR REPLACE FUNCTION retain_workspace_privacy_sources() RETURNS trigger LANGUAGE plpgsql AS $$
169
+ DECLARE sources jsonb := '{}'::jsonb; linked jsonb;
170
+ BEGIN
171
+ IF TG_OP = 'UPDATE' AND jsonb_typeof(OLD.metadata->'_issuePrivacySources') = 'object' THEN
172
+ sources := OLD.metadata->'_issuePrivacySources';
173
+ END IF;
174
+ IF NEW.source_issue_id IS NOT NULL THEN
175
+ sources := sources || jsonb_build_object(NEW.source_issue_id::text, true);
176
+ END IF;
177
+ SELECT jsonb_object_agg(id::text, true) INTO linked FROM issues
178
+ WHERE execution_workspace_id = NEW.id AND company_id = NEW.company_id;
179
+ sources := sources || coalesce(linked, '{}'::jsonb);
180
+ NEW.metadata := coalesce(NEW.metadata, '{}'::jsonb) || jsonb_build_object('_issuePrivacySources', sources);
181
+ RETURN NEW;
182
+ END $$;
183
+ --> statement-breakpoint
184
+ DO $$ BEGIN
185
+ DROP TRIGGER IF EXISTS execution_workspaces_privacy_sources ON execution_workspaces;
186
+ CREATE TRIGGER execution_workspaces_privacy_sources BEFORE INSERT OR UPDATE OF source_issue_id, metadata
187
+ ON execution_workspaces FOR EACH ROW EXECUTE FUNCTION retain_workspace_privacy_sources();
188
+ END $$;
189
+ --> statement-breakpoint
190
+ CREATE OR REPLACE FUNCTION record_issue_workspace_privacy_source() RETURNS trigger LANGUAGE plpgsql AS $$
191
+ BEGIN
192
+ IF NEW.execution_workspace_id IS NOT NULL THEN
193
+ UPDATE execution_workspaces SET metadata = metadata
194
+ WHERE id = NEW.execution_workspace_id AND company_id = NEW.company_id;
195
+ END IF;
196
+ RETURN NEW;
197
+ END $$;
198
+ --> statement-breakpoint
199
+ DO $$ BEGIN
200
+ DROP TRIGGER IF EXISTS issues_record_workspace_privacy_source ON issues;
201
+ CREATE TRIGGER issues_record_workspace_privacy_source AFTER INSERT OR UPDATE OF execution_workspace_id
202
+ ON issues FOR EACH ROW EXECUTE FUNCTION record_issue_workspace_privacy_source();
203
+ END $$;
204
+ --> statement-breakpoint
205
+ DO $$ DECLARE cursor_id uuid := '00000000-0000-0000-0000-000000000000'; next_id uuid;
206
+ BEGIN LOOP
207
+ WITH batch AS MATERIALIZED (SELECT id FROM execution_workspaces WHERE id > cursor_id ORDER BY id LIMIT 1000),
208
+ changed AS (UPDATE execution_workspaces w SET metadata = metadata FROM batch b WHERE w.id = b.id RETURNING w.id)
209
+ SELECT id INTO next_id FROM changed ORDER BY id DESC LIMIT 1;
210
+ EXIT WHEN next_id IS NULL;
211
+ cursor_id := next_id;
212
+ COMMIT;
213
+ END LOOP; END $$;
214
+
215
+ --> statement-breakpoint
216
+ -- Preserve operation history while retaining every original task/run boundary.
217
+ CREATE OR REPLACE FUNCTION paperclip_keep_operation_privacy_sources() RETURNS trigger AS $$
218
+ DECLARE issue_sources jsonb := '{}'::jsonb; run_sources jsonb := '{}'::jsonb;
219
+ BEGIN
220
+ IF TG_OP = 'UPDATE' THEN
221
+ issue_sources := COALESCE(OLD.metadata->'_issuePrivacySources', '{}'::jsonb);
222
+ run_sources := COALESCE(OLD.metadata->'_runPrivacySources', '{}'::jsonb);
223
+ IF OLD.issue_id IS NOT NULL THEN issue_sources := issue_sources || jsonb_build_object(OLD.issue_id::text, true); END IF;
224
+ IF OLD.heartbeat_run_id IS NOT NULL THEN run_sources := run_sources || jsonb_build_object(OLD.heartbeat_run_id::text, true); END IF;
225
+ END IF;
226
+ IF NEW.issue_id IS NOT NULL THEN issue_sources := issue_sources || jsonb_build_object(NEW.issue_id::text, true); END IF;
227
+ IF NEW.heartbeat_run_id IS NOT NULL THEN run_sources := run_sources || jsonb_build_object(NEW.heartbeat_run_id::text, true); END IF;
228
+ NEW.metadata := COALESCE(NEW.metadata, '{}'::jsonb) || jsonb_build_object('_issuePrivacySources', issue_sources, '_runPrivacySources', run_sources);
229
+ RETURN NEW;
230
+ END;
231
+ $$ LANGUAGE plpgsql;
232
+ --> statement-breakpoint
233
+ DO $$ BEGIN
234
+ DROP TRIGGER IF EXISTS workspace_operations_keep_privacy_sources ON workspace_operations;
235
+ CREATE TRIGGER workspace_operations_keep_privacy_sources BEFORE INSERT OR UPDATE ON workspace_operations
236
+ FOR EACH ROW EXECUTE FUNCTION paperclip_keep_operation_privacy_sources();
237
+ END $$;
238
+ --> statement-breakpoint
239
+ DO $$
240
+ DECLARE cursor_id uuid := '00000000-0000-0000-0000-000000000000'; batch_ids uuid[];
241
+ BEGIN
242
+ LOOP
243
+ SELECT array_agg(id ORDER BY id) INTO batch_ids FROM (
244
+ SELECT id FROM workspace_operations WHERE id > cursor_id ORDER BY id LIMIT 1000
245
+ ) batch;
246
+ EXIT WHEN batch_ids IS NULL;
247
+ UPDATE workspace_operations SET metadata = metadata WHERE id = ANY(batch_ids);
248
+ cursor_id := batch_ids[array_length(batch_ids, 1)];
249
+ COMMIT;
250
+ END LOOP;
251
+ END $$;
252
+
253
+ --> statement-breakpoint
254
+ ALTER TABLE projects ADD COLUMN IF NOT EXISTS privacy_owner_user_id text;
255
+ --> statement-breakpoint
256
+ -- Recover management authority from creation evidence, never from read grants.
257
+ DO $$
258
+ DECLARE cursor_id uuid := '00000000-0000-0000-0000-000000000000'; batch_ids uuid[];
259
+ BEGIN
260
+ LOOP
261
+ SELECT array_agg(id ORDER BY id) INTO batch_ids FROM (
262
+ SELECT id FROM projects WHERE id > cursor_id ORDER BY id LIMIT 1000
263
+ ) batch;
264
+ EXIT WHEN batch_ids IS NULL;
265
+ UPDATE projects p SET privacy_owner_user_id = COALESCE(p.personal_owner_user_id, (
266
+ SELECT CASE WHEN a.actor_type = 'user' THEN a.actor_id ELSE coalesce(a.responsible_user_id, r.responsible_user_id) END
267
+ FROM activity_log a LEFT JOIN heartbeat_runs r ON r.id = a.run_id AND r.company_id = a.company_id
268
+ WHERE a.company_id = p.company_id AND a.entity_type = 'project' AND a.entity_id = p.id::text AND a.action = 'project.created'
269
+ ORDER BY a.created_at, a.id LIMIT 1
270
+ )) WHERE p.id = ANY(batch_ids) AND p.privacy_owner_user_id IS NULL;
271
+ INSERT INTO project_access_members (company_id, project_id, subject_type, subject_id)
272
+ SELECT company_id, id, 'user', privacy_owner_user_id FROM projects
273
+ WHERE id = ANY(batch_ids) AND visibility = 'private' AND privacy_owner_user_id IS NOT NULL
274
+ ON CONFLICT (project_id, subject_type, subject_id) DO NOTHING;
275
+ cursor_id := batch_ids[array_length(batch_ids, 1)];
276
+ COMMIT;
277
+ END LOOP;
278
+ END $$;
@@ -0,0 +1,28 @@
1
+ -- Runs through the Paperclip migration executor outside a file-wide transaction.
2
+ -- Each idempotent keyset batch commits before advancing; history is recorded last.
3
+ -- Bind historical inline draft images to their first owned task. Unbound drafts
4
+ -- remain uploader-only. Attachment deletion never makes the draft company-open.
5
+ DO $$
6
+ DECLARE cursor_id uuid := '00000000-0000-0000-0000-000000000000'; next_id uuid;
7
+ BEGIN
8
+ LOOP
9
+ SELECT max(id::text)::uuid INTO next_id FROM (
10
+ SELECT id FROM assets WHERE id > cursor_id AND object_key LIKE company_id::text || '/assets/issues/drafts/%'
11
+ ORDER BY id LIMIT 1000
12
+ ) batch;
13
+ EXIT WHEN next_id IS NULL;
14
+ INSERT INTO issue_attachments (company_id, issue_id, asset_id)
15
+ SELECT a.company_id, first_issue.id, a.id FROM assets a
16
+ CROSS JOIN LATERAL (
17
+ SELECT i.id FROM issues i WHERE i.company_id = a.company_id
18
+ AND (i.created_by_user_id = a.created_by_user_id OR i.responsible_user_id = a.created_by_user_id
19
+ OR i.created_by_agent_id = a.created_by_agent_id)
20
+ AND strpos(coalesce(i.description, ''), '/api/assets/' || a.id::text || '/content') > 0
21
+ ORDER BY i.created_at, i.id LIMIT 1
22
+ ) first_issue
23
+ WHERE a.id > cursor_id AND a.id <= next_id AND a.object_key LIKE a.company_id::text || '/assets/issues/drafts/%'
24
+ ON CONFLICT DO NOTHING;
25
+ cursor_id := next_id;
26
+ COMMIT;
27
+ END LOOP;
28
+ END $$;
@@ -0,0 +1,44 @@
1
+ CREATE TABLE IF NOT EXISTS "user_company_preferences" (
2
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
3
+ "company_id" uuid NOT NULL,
4
+ "user_id" text NOT NULL,
5
+ "primary_agent_id" uuid,
6
+ "primary_agent_initialized" boolean DEFAULT false NOT NULL,
7
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
8
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL
9
+ );
10
+ --> statement-breakpoint
11
+ DO $$ BEGIN
12
+ ALTER TABLE "user_company_preferences" ADD CONSTRAINT "user_company_preferences_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action;
13
+ EXCEPTION WHEN duplicate_object THEN NULL;
14
+ END $$;
15
+ --> statement-breakpoint
16
+ DO $$ BEGIN
17
+ ALTER TABLE "user_company_preferences" ADD CONSTRAINT "user_company_preferences_primary_agent_id_agents_id_fk" FOREIGN KEY ("primary_agent_id") REFERENCES "public"."agents"("id") ON DELETE set null ON UPDATE no action;
18
+ EXCEPTION WHEN duplicate_object THEN NULL;
19
+ END $$;
20
+ --> statement-breakpoint
21
+ CREATE UNIQUE INDEX IF NOT EXISTS "user_company_preferences_company_user_uq" ON "user_company_preferences" USING btree ("company_id","user_id");--> statement-breakpoint
22
+ CREATE INDEX IF NOT EXISTS "user_company_preferences_primary_agent_idx" ON "user_company_preferences" USING btree ("primary_agent_id");--> statement-breakpoint
23
+ -- Attribute only explicit human creation events. Select the original event
24
+ -- before joining the agent: deletion/termination must not promote a later hire.
25
+ WITH first_creations AS (
26
+ SELECT DISTINCT ON (company_id, actor_id)
27
+ company_id, actor_id AS user_id, entity_id AS agent_id
28
+ FROM activity_log
29
+ WHERE actor_type = 'user'
30
+ AND actor_id NOT IN ('', 'board', 'system', 'unknown', 'unknown-user')
31
+ AND entity_type = 'agent'
32
+ AND action IN ('agent.created', 'agent.hire_created')
33
+ ORDER BY company_id, actor_id, created_at, id
34
+ )
35
+ INSERT INTO user_company_preferences (company_id, user_id, primary_agent_id, primary_agent_initialized)
36
+ SELECT c.company_id, c.user_id,
37
+ CASE WHEN a.status != 'terminated' AND m.state IS DISTINCT FROM 'left'
38
+ THEN a.id ELSE NULL END,
39
+ true
40
+ FROM first_creations c
41
+ LEFT JOIN agents a ON a.id::text = c.agent_id AND a.company_id = c.company_id
42
+ LEFT JOIN agent_memberships m ON m.company_id = c.company_id AND m.user_id = c.user_id AND m.agent_id = a.id
43
+ WHERE a.metadata->'paperclipBuiltInAgent' IS NULL
44
+ ON CONFLICT (company_id, user_id) DO NOTHING;
@@ -0,0 +1,81 @@
1
+ -- Idempotent for installations that applied the pre-merge decision migration.
2
+ CREATE TABLE IF NOT EXISTS "company_decision_models" (
3
+ "company_id" uuid PRIMARY KEY NOT NULL,
4
+ "connection_id" uuid,
5
+ "grant_id" uuid,
6
+ "enabled" boolean DEFAULT false NOT NULL,
7
+ "allow_background" boolean DEFAULT true NOT NULL,
8
+ "provider" text,
9
+ "model" text,
10
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
11
+ CONSTRAINT "company_decisions_configured" CHECK (("company_decision_models"."connection_id" is null) = ("company_decision_models"."grant_id" is null) and (not "company_decision_models"."enabled" or ("company_decision_models"."connection_id" is not null and "company_decision_models"."provider" is not null and "company_decision_models"."model" is not null)))
12
+ );
13
+ --> statement-breakpoint
14
+ CREATE TABLE IF NOT EXISTS "decision_invocations" (
15
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
16
+ "company_id" uuid NOT NULL,
17
+ "feature" text NOT NULL,
18
+ "actor_type" text NOT NULL,
19
+ "actor_id" text NOT NULL,
20
+ "responsible_user_id" text,
21
+ "agent_id" uuid,
22
+ "issue_id" uuid,
23
+ "project_id" uuid,
24
+ "run_id" uuid,
25
+ "identity_context_id" uuid,
26
+ "connection_id" uuid NOT NULL,
27
+ "grant_id" uuid NOT NULL,
28
+ "provider" text NOT NULL,
29
+ "model" text NOT NULL,
30
+ "question_types" jsonb NOT NULL,
31
+ "status" text DEFAULT 'running' NOT NULL,
32
+ "error_code" text,
33
+ "provider_request_id" text,
34
+ "cost_event_id" uuid,
35
+ "started_at" timestamp with time zone DEFAULT now() NOT NULL,
36
+ "finished_at" timestamp with time zone,
37
+ "duration_ms" integer,
38
+ "input_tokens" integer,
39
+ "output_tokens" integer,
40
+ CONSTRAINT "decision_invocations_status_check" CHECK ("decision_invocations"."status" in ('running', 'succeeded', 'failed', 'unknown'))
41
+ );
42
+ --> statement-breakpoint
43
+ ALTER TABLE "budget_reservations" ALTER COLUMN "run_id" DROP NOT NULL;--> statement-breakpoint
44
+ ALTER TABLE "budget_reservations" ALTER COLUMN "agent_id" DROP NOT NULL;--> statement-breakpoint
45
+ ALTER TABLE "cost_events" ALTER COLUMN "agent_id" DROP NOT NULL;--> statement-breakpoint
46
+ ALTER TABLE "budget_reservations" ADD COLUMN IF NOT EXISTS "decision_invocation_id" uuid;--> statement-breakpoint
47
+ ALTER TABLE "cost_events" ADD COLUMN IF NOT EXISTS "usage_kind" text DEFAULT 'agent' NOT NULL;--> statement-breakpoint
48
+ ALTER TABLE "cost_events" ADD COLUMN IF NOT EXISTS "responsible_user_id" text;--> statement-breakpoint
49
+ DO $$ BEGIN
50
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'company_decision_models_company_id_companies_id_fk' AND conrelid = 'company_decision_models'::regclass) THEN
51
+ ALTER TABLE "company_decision_models" ADD CONSTRAINT "company_decision_models_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action;
52
+ END IF;
53
+ END $$;--> statement-breakpoint
54
+ DO $$ BEGIN
55
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'decision_invocations_company_id_companies_id_fk' AND conrelid = 'decision_invocations'::regclass) THEN
56
+ ALTER TABLE "decision_invocations" ADD CONSTRAINT "decision_invocations_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action;
57
+ END IF;
58
+ END $$;--> statement-breakpoint
59
+ DO $$ BEGIN
60
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'decision_invocations_cost_event_id_cost_events_id_fk' AND conrelid = 'decision_invocations'::regclass) THEN
61
+ ALTER TABLE "decision_invocations" ADD CONSTRAINT "decision_invocations_cost_event_id_cost_events_id_fk" FOREIGN KEY ("cost_event_id") REFERENCES "public"."cost_events"("id") ON DELETE no action ON UPDATE no action;
62
+ END IF;
63
+ END $$;--> statement-breakpoint
64
+ CREATE INDEX IF NOT EXISTS "decision_invocations_company_started_idx" ON "decision_invocations" USING btree ("company_id","started_at","id");--> statement-breakpoint
65
+ CREATE INDEX IF NOT EXISTS "decision_invocations_pending_idx" ON "decision_invocations" USING btree ("started_at") WHERE "decision_invocations"."status" = 'running';--> statement-breakpoint
66
+ CREATE INDEX IF NOT EXISTS "decision_invocations_cost_idx" ON "decision_invocations" USING btree ("cost_event_id");--> statement-breakpoint
67
+ CREATE UNIQUE INDEX IF NOT EXISTS "budget_reservations_decision_idx" ON "budget_reservations" USING btree ("company_id","decision_invocation_id");--> statement-breakpoint
68
+ DO $$ BEGIN
69
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'budget_reservations_source_check' AND conrelid = 'budget_reservations'::regclass) THEN
70
+ ALTER TABLE "budget_reservations" ADD CONSTRAINT "budget_reservations_source_check" CHECK (("budget_reservations"."run_id" is not null)::int + ("budget_reservations"."decision_invocation_id" is not null)::int = 1);
71
+ END IF;
72
+ END $$;--> statement-breakpoint
73
+ DO $$ BEGIN
74
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'cost_events_usage_kind_check' AND conrelid = 'cost_events'::regclass) THEN
75
+ ALTER TABLE "cost_events" ADD CONSTRAINT "cost_events_usage_kind_check" CHECK ("cost_events"."usage_kind" = 'decision' or ("cost_events"."usage_kind" = 'agent' and "cost_events"."agent_id" is not null));
76
+ END IF;
77
+ END $$;
78
+ --> statement-breakpoint
79
+ ALTER TABLE "company_decision_models" DROP CONSTRAINT IF EXISTS "company_decision_models_connection_id_tool_connections_id_fk";
80
+ --> statement-breakpoint
81
+ ALTER TABLE "company_decision_models" DROP CONSTRAINT IF EXISTS "company_decision_models_grant_id_connection_grants_id_fk";
@@ -0,0 +1,189 @@
1
+ -- Dot-only additions after the merged public MCP gateway. Safe to reapply to a prototype schema.
2
+ CREATE TABLE IF NOT EXISTS "dot_agent_bindings" (
3
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
4
+ "company_id" uuid NOT NULL,
5
+ "agent_id" uuid NOT NULL,
6
+ "operator_id" text NOT NULL,
7
+ "grant_id" uuid,
8
+ "generation" integer DEFAULT 1 NOT NULL,
9
+ "status" text DEFAULT 'pairing' NOT NULL,
10
+ "pairing_code_hash" text,
11
+ "pairing_expires_at" timestamp with time zone,
12
+ "dot_url" text,
13
+ "challenge_hash" text,
14
+ "challenge_expires_at" timestamp with time zone,
15
+ "ready_at" timestamp with time zone,
16
+ "revoked_at" timestamp with time zone,
17
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
18
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL
19
+ );
20
+ --> statement-breakpoint
21
+ CREATE TABLE IF NOT EXISTS "dot_mailbox_items" (
22
+ "id" bigserial PRIMARY KEY NOT NULL,
23
+ "company_id" uuid NOT NULL,
24
+ "binding_id" uuid NOT NULL,
25
+ "binding_generation" integer NOT NULL,
26
+ "assignment_id" uuid,
27
+ "kind" text NOT NULL,
28
+ "source_event_id" text NOT NULL,
29
+ "references" jsonb NOT NULL,
30
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL
31
+ );
32
+ --> statement-breakpoint
33
+ CREATE TABLE IF NOT EXISTS "dot_runner_assignments" (
34
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
35
+ "company_id" uuid NOT NULL,
36
+ "binding_id" uuid NOT NULL,
37
+ "binding_generation" integer NOT NULL,
38
+ "run_id" uuid NOT NULL,
39
+ "agent_id" uuid NOT NULL,
40
+ "normalized_session_id" text NOT NULL,
41
+ "turn_id" text NOT NULL,
42
+ "revision" integer DEFAULT 1 NOT NULL,
43
+ "controller_generation" integer NOT NULL,
44
+ "catalog_digest" text NOT NULL,
45
+ "status" text NOT NULL,
46
+ "projection" jsonb NOT NULL,
47
+ "accept_by" timestamp with time zone NOT NULL,
48
+ "expires_at" timestamp with time zone NOT NULL,
49
+ "accepted_at" timestamp with time zone,
50
+ "settled_at" timestamp with time zone,
51
+ "last_activity_at" timestamp with time zone DEFAULT now() NOT NULL,
52
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL
53
+ );
54
+ --> statement-breakpoint
55
+ CREATE TABLE IF NOT EXISTS "dot_runner_operations" (
56
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
57
+ "company_id" uuid NOT NULL,
58
+ "assignment_id" uuid NOT NULL,
59
+ "request_id" uuid NOT NULL,
60
+ "digest" text NOT NULL,
61
+ "command" jsonb NOT NULL,
62
+ "status" text DEFAULT 'reserved' NOT NULL,
63
+ "outcome" jsonb,
64
+ "source_event_id" text,
65
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
66
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL
67
+ );
68
+ --> statement-breakpoint
69
+ ALTER TABLE "mcp_event_deliveries" ALTER COLUMN "activity_id" DROP NOT NULL;
70
+ --> statement-breakpoint
71
+ ALTER TABLE "mcp_event_subscriptions" ALTER COLUMN "task_id" DROP NOT NULL;
72
+ --> statement-breakpoint
73
+ ALTER TABLE "mcp_event_deliveries" ADD COLUMN IF NOT EXISTS "mailbox_item_id" integer;
74
+ --> statement-breakpoint
75
+ ALTER TABLE "mcp_event_subscriptions" ADD COLUMN IF NOT EXISTS "binding_id" uuid;
76
+ --> statement-breakpoint
77
+ ALTER TABLE "mcp_oauth_grants" ADD COLUMN IF NOT EXISTS "purpose" text DEFAULT 'personal' NOT NULL;
78
+ --> statement-breakpoint
79
+ ALTER TABLE "mcp_oauth_grants" ADD COLUMN IF NOT EXISTS "agent_id" uuid;
80
+ --> statement-breakpoint
81
+ DO $$ BEGIN
82
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'dot_agent_bindings_company_id_companies_id_fk' AND conrelid = 'public.dot_agent_bindings'::regclass) THEN
83
+ ALTER TABLE "dot_agent_bindings" ADD CONSTRAINT "dot_agent_bindings_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action;
84
+ END IF;
85
+ END $$;
86
+ --> statement-breakpoint
87
+ DO $$ BEGIN
88
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'dot_agent_bindings_agent_id_agents_id_fk' AND conrelid = 'public.dot_agent_bindings'::regclass) THEN
89
+ ALTER TABLE "dot_agent_bindings" ADD CONSTRAINT "dot_agent_bindings_agent_id_agents_id_fk" FOREIGN KEY ("agent_id") REFERENCES "public"."agents"("id") ON DELETE cascade ON UPDATE no action;
90
+ END IF;
91
+ END $$;
92
+ --> statement-breakpoint
93
+ DO $$ BEGIN
94
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'dot_agent_bindings_grant_id_mcp_oauth_grants_id_fk' AND conrelid = 'public.dot_agent_bindings'::regclass) THEN
95
+ ALTER TABLE "dot_agent_bindings" ADD CONSTRAINT "dot_agent_bindings_grant_id_mcp_oauth_grants_id_fk" FOREIGN KEY ("grant_id") REFERENCES "public"."mcp_oauth_grants"("id") ON DELETE set null ON UPDATE no action;
96
+ END IF;
97
+ END $$;
98
+ --> statement-breakpoint
99
+ DO $$ BEGIN
100
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'dot_mailbox_items_company_id_companies_id_fk' AND conrelid = 'public.dot_mailbox_items'::regclass) THEN
101
+ ALTER TABLE "dot_mailbox_items" ADD CONSTRAINT "dot_mailbox_items_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action;
102
+ END IF;
103
+ END $$;
104
+ --> statement-breakpoint
105
+ DO $$ BEGIN
106
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'dot_mailbox_items_binding_id_dot_agent_bindings_id_fk' AND conrelid = 'public.dot_mailbox_items'::regclass) THEN
107
+ ALTER TABLE "dot_mailbox_items" ADD CONSTRAINT "dot_mailbox_items_binding_id_dot_agent_bindings_id_fk" FOREIGN KEY ("binding_id") REFERENCES "public"."dot_agent_bindings"("id") ON DELETE cascade ON UPDATE no action;
108
+ END IF;
109
+ END $$;
110
+ --> statement-breakpoint
111
+ DO $$ BEGIN
112
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'dot_mailbox_items_assignment_id_dot_runner_assignments_id_fk' AND conrelid = 'public.dot_mailbox_items'::regclass) THEN
113
+ ALTER TABLE "dot_mailbox_items" ADD CONSTRAINT "dot_mailbox_items_assignment_id_dot_runner_assignments_id_fk" FOREIGN KEY ("assignment_id") REFERENCES "public"."dot_runner_assignments"("id") ON DELETE cascade ON UPDATE no action;
114
+ END IF;
115
+ END $$;
116
+ --> statement-breakpoint
117
+ DO $$ BEGIN
118
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'dot_runner_assignments_company_id_companies_id_fk' AND conrelid = 'public.dot_runner_assignments'::regclass) THEN
119
+ ALTER TABLE "dot_runner_assignments" ADD CONSTRAINT "dot_runner_assignments_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action;
120
+ END IF;
121
+ END $$;
122
+ --> statement-breakpoint
123
+ DO $$ BEGIN
124
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'dot_runner_assignments_binding_id_dot_agent_bindings_id_fk' AND conrelid = 'public.dot_runner_assignments'::regclass) THEN
125
+ ALTER TABLE "dot_runner_assignments" ADD CONSTRAINT "dot_runner_assignments_binding_id_dot_agent_bindings_id_fk" FOREIGN KEY ("binding_id") REFERENCES "public"."dot_agent_bindings"("id") ON DELETE cascade ON UPDATE no action;
126
+ END IF;
127
+ END $$;
128
+ --> statement-breakpoint
129
+ DO $$ BEGIN
130
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'dot_runner_assignments_run_id_heartbeat_runs_id_fk' AND conrelid = 'public.dot_runner_assignments'::regclass) THEN
131
+ ALTER TABLE "dot_runner_assignments" ADD CONSTRAINT "dot_runner_assignments_run_id_heartbeat_runs_id_fk" FOREIGN KEY ("run_id") REFERENCES "public"."heartbeat_runs"("id") ON DELETE cascade ON UPDATE no action;
132
+ END IF;
133
+ END $$;
134
+ --> statement-breakpoint
135
+ DO $$ BEGIN
136
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'dot_runner_assignments_agent_id_agents_id_fk' AND conrelid = 'public.dot_runner_assignments'::regclass) THEN
137
+ ALTER TABLE "dot_runner_assignments" ADD CONSTRAINT "dot_runner_assignments_agent_id_agents_id_fk" FOREIGN KEY ("agent_id") REFERENCES "public"."agents"("id") ON DELETE cascade ON UPDATE no action;
138
+ END IF;
139
+ END $$;
140
+ --> statement-breakpoint
141
+ DO $$ BEGIN
142
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'dot_runner_operations_company_id_companies_id_fk' AND conrelid = 'public.dot_runner_operations'::regclass) THEN
143
+ ALTER TABLE "dot_runner_operations" ADD CONSTRAINT "dot_runner_operations_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action;
144
+ END IF;
145
+ END $$;
146
+ --> statement-breakpoint
147
+ DO $$ BEGIN
148
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'dot_runner_operations_assignment_id_dot_runner_assignments_id_fk' AND conrelid = 'public.dot_runner_operations'::regclass) THEN
149
+ ALTER TABLE "dot_runner_operations" ADD CONSTRAINT "dot_runner_operations_assignment_id_dot_runner_assignments_id_fk" FOREIGN KEY ("assignment_id") REFERENCES "public"."dot_runner_assignments"("id") ON DELETE cascade ON UPDATE no action;
150
+ END IF;
151
+ END $$;
152
+ --> statement-breakpoint
153
+ CREATE UNIQUE INDEX IF NOT EXISTS "dot_bindings_active_agent_uq" ON "dot_agent_bindings" USING btree ("company_id","agent_id") WHERE "dot_agent_bindings"."revoked_at" IS NULL;
154
+ --> statement-breakpoint
155
+ CREATE UNIQUE INDEX IF NOT EXISTS "dot_bindings_active_grant_uq" ON "dot_agent_bindings" USING btree ("grant_id") WHERE "dot_agent_bindings"."revoked_at" IS NULL AND "dot_agent_bindings"."grant_id" IS NOT NULL;
156
+ --> statement-breakpoint
157
+ CREATE UNIQUE INDEX IF NOT EXISTS "dot_bindings_pairing_code_uq" ON "dot_agent_bindings" USING btree ("pairing_code_hash");
158
+ --> statement-breakpoint
159
+ CREATE UNIQUE INDEX IF NOT EXISTS "dot_mailbox_event_uq" ON "dot_mailbox_items" USING btree ("binding_id","binding_generation","source_event_id");
160
+ --> statement-breakpoint
161
+ CREATE INDEX IF NOT EXISTS "dot_mailbox_binding_cursor_idx" ON "dot_mailbox_items" USING btree ("binding_id","binding_generation","id");
162
+ --> statement-breakpoint
163
+ CREATE UNIQUE INDEX IF NOT EXISTS "dot_assignments_turn_uq" ON "dot_runner_assignments" USING btree ("run_id","turn_id");
164
+ --> statement-breakpoint
165
+ CREATE UNIQUE INDEX IF NOT EXISTS "dot_assignments_active_binding_uq" ON "dot_runner_assignments" USING btree ("binding_id") WHERE "dot_runner_assignments"."status" IN ('offered', 'accepted');
166
+ --> statement-breakpoint
167
+ CREATE INDEX IF NOT EXISTS "dot_assignments_company_idx" ON "dot_runner_assignments" USING btree ("company_id");
168
+ --> statement-breakpoint
169
+ CREATE UNIQUE INDEX IF NOT EXISTS "dot_operations_request_uq" ON "dot_runner_operations" USING btree ("assignment_id","request_id");
170
+ --> statement-breakpoint
171
+ DO $$ BEGIN
172
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'mcp_oauth_grants_agent_id_agents_id_fk' AND conrelid = 'public.mcp_oauth_grants'::regclass) THEN
173
+ ALTER TABLE "mcp_oauth_grants" ADD CONSTRAINT "mcp_oauth_grants_agent_id_agents_id_fk" FOREIGN KEY ("agent_id") REFERENCES "public"."agents"("id") ON DELETE cascade ON UPDATE no action;
174
+ END IF;
175
+ END $$;
176
+ --> statement-breakpoint
177
+ CREATE UNIQUE INDEX IF NOT EXISTS "mcp_event_deliveries_mailbox_uq" ON "mcp_event_deliveries" USING btree ("subscription_id","mailbox_item_id");
178
+ --> statement-breakpoint
179
+ DO $$ BEGIN
180
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'mcp_delivery_source_check' AND conrelid = 'public.mcp_event_deliveries'::regclass) THEN
181
+ ALTER TABLE "mcp_event_deliveries" ADD CONSTRAINT "mcp_delivery_source_check" CHECK (("mcp_event_deliveries"."activity_id" IS NOT NULL AND "mcp_event_deliveries"."mailbox_item_id" IS NULL) OR ("mcp_event_deliveries"."activity_id" IS NULL AND "mcp_event_deliveries"."mailbox_item_id" IS NOT NULL));
182
+ END IF;
183
+ END $$;
184
+ --> statement-breakpoint
185
+ DO $$ BEGIN
186
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'mcp_subscription_resource_check' AND conrelid = 'public.mcp_event_subscriptions'::regclass) THEN
187
+ ALTER TABLE "mcp_event_subscriptions" ADD CONSTRAINT "mcp_subscription_resource_check" CHECK (("mcp_event_subscriptions"."task_id" IS NOT NULL AND "mcp_event_subscriptions"."binding_id" IS NULL) OR ("mcp_event_subscriptions"."task_id" IS NULL AND "mcp_event_subscriptions"."binding_id" IS NOT NULL));
188
+ END IF;
189
+ END $$;