@lobu/cli 6.0.1 → 6.1.1

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 (217) hide show
  1. package/README.md +20 -27
  2. package/dist/bundled-skills/lobu/SKILL.md +11 -11
  3. package/dist/commands/_lib/apply/apply-cmd.d.ts +2 -0
  4. package/dist/commands/_lib/apply/apply-cmd.d.ts.map +1 -1
  5. package/dist/commands/_lib/apply/apply-cmd.js +26 -0
  6. package/dist/commands/_lib/apply/apply-cmd.js.map +1 -1
  7. package/dist/commands/_lib/apply/client.d.ts +1 -1
  8. package/dist/commands/_lib/apply/client.d.ts.map +1 -1
  9. package/dist/commands/_lib/apply/desired-state.js +4 -4
  10. package/dist/commands/_lib/apply/desired-state.js.map +1 -1
  11. package/dist/commands/agent.d.ts +7 -0
  12. package/dist/commands/agent.d.ts.map +1 -1
  13. package/dist/commands/agent.js +65 -1
  14. package/dist/commands/agent.js.map +1 -1
  15. package/dist/commands/chat.d.ts +12 -9
  16. package/dist/commands/chat.d.ts.map +1 -1
  17. package/dist/commands/chat.js +117 -56
  18. package/dist/commands/chat.js.map +1 -1
  19. package/dist/commands/dev.d.ts +15 -7
  20. package/dist/commands/dev.d.ts.map +1 -1
  21. package/dist/commands/dev.js +79 -44
  22. package/dist/commands/dev.js.map +1 -1
  23. package/dist/commands/doctor.d.ts +1 -0
  24. package/dist/commands/doctor.d.ts.map +1 -1
  25. package/dist/commands/doctor.js +136 -0
  26. package/dist/commands/doctor.js.map +1 -1
  27. package/dist/commands/eval.d.ts +8 -0
  28. package/dist/commands/eval.d.ts.map +1 -1
  29. package/dist/commands/eval.js +56 -1
  30. package/dist/commands/eval.js.map +1 -1
  31. package/dist/commands/init.d.ts +20 -5
  32. package/dist/commands/init.d.ts.map +1 -1
  33. package/dist/commands/init.js +332 -183
  34. package/dist/commands/init.js.map +1 -1
  35. package/dist/commands/link.d.ts +11 -0
  36. package/dist/commands/link.d.ts.map +1 -0
  37. package/dist/commands/link.js +28 -0
  38. package/dist/commands/link.js.map +1 -0
  39. package/dist/commands/login.d.ts.map +1 -1
  40. package/dist/commands/login.js +14 -2
  41. package/dist/commands/login.js.map +1 -1
  42. package/dist/commands/memory/_lib/browser-auth-cmd.d.ts.map +1 -1
  43. package/dist/commands/memory/_lib/browser-auth-cmd.js +3 -3
  44. package/dist/commands/memory/_lib/browser-auth-cmd.js.map +1 -1
  45. package/dist/commands/memory/_lib/mcp.d.ts +2 -2
  46. package/dist/commands/memory/_lib/mcp.d.ts.map +1 -1
  47. package/dist/commands/memory/_lib/mcp.js +24 -12
  48. package/dist/commands/memory/_lib/mcp.js.map +1 -1
  49. package/dist/commands/memory/_lib/openclaw-auth.d.ts +1 -0
  50. package/dist/commands/memory/_lib/openclaw-auth.d.ts.map +1 -1
  51. package/dist/commands/memory/_lib/openclaw-auth.js +14 -3
  52. package/dist/commands/memory/_lib/openclaw-auth.js.map +1 -1
  53. package/dist/commands/memory/_lib/openclaw-cmd.js +1 -1
  54. package/dist/commands/memory/_lib/openclaw-cmd.js.map +1 -1
  55. package/dist/commands/memory/_lib/schema.d.ts +1 -1
  56. package/dist/commands/memory/_lib/schema.js +1 -1
  57. package/dist/commands/memory/_lib/seed-cmd.d.ts.map +1 -1
  58. package/dist/commands/memory/_lib/seed-cmd.js +5 -6
  59. package/dist/commands/memory/_lib/seed-cmd.js.map +1 -1
  60. package/dist/commands/memory/run.d.ts.map +1 -1
  61. package/dist/commands/memory/run.js +2 -2
  62. package/dist/commands/memory/run.js.map +1 -1
  63. package/dist/commands/platforms/platform-prompts.d.ts +0 -1
  64. package/dist/commands/platforms/platform-prompts.d.ts.map +1 -1
  65. package/dist/commands/platforms/platform-prompts.js +54 -8
  66. package/dist/commands/platforms/platform-prompts.js.map +1 -1
  67. package/dist/commands/telemetry.d.ts +10 -0
  68. package/dist/commands/telemetry.d.ts.map +1 -0
  69. package/dist/commands/telemetry.js +68 -0
  70. package/dist/commands/telemetry.js.map +1 -0
  71. package/dist/commands/whoami.d.ts.map +1 -1
  72. package/dist/commands/whoami.js +1 -1
  73. package/dist/commands/whoami.js.map +1 -1
  74. package/dist/connectors/README.md +534 -0
  75. package/dist/connectors/__tests__/browser-scraper-utils.test.ts +186 -0
  76. package/dist/connectors/browser-scraper-utils.ts +214 -0
  77. package/dist/connectors/capterra.ts +273 -0
  78. package/dist/connectors/g2.ts +286 -0
  79. package/dist/connectors/github.ts +1553 -0
  80. package/dist/connectors/glassdoor.ts +291 -0
  81. package/dist/connectors/gmaps.ts +197 -0
  82. package/dist/connectors/google_calendar.ts +631 -0
  83. package/dist/connectors/google_gmail.ts +751 -0
  84. package/dist/connectors/google_photos.ts +776 -0
  85. package/dist/connectors/google_play.ts +342 -0
  86. package/dist/connectors/hackernews.ts +471 -0
  87. package/dist/connectors/index.ts +23 -0
  88. package/dist/connectors/ios_appstore.ts +226 -0
  89. package/dist/connectors/linkedin.ts +471 -0
  90. package/dist/connectors/microsoft_outlook.ts +410 -0
  91. package/dist/connectors/producthunt.ts +471 -0
  92. package/dist/connectors/reddit.ts +600 -0
  93. package/dist/connectors/rss.ts +448 -0
  94. package/dist/connectors/spotify.ts +590 -0
  95. package/dist/connectors/trustpilot.ts +199 -0
  96. package/dist/connectors/website.ts +629 -0
  97. package/dist/connectors/whatsapp.ts +1073 -0
  98. package/dist/connectors/x.ts +526 -0
  99. package/dist/connectors/youtube.ts +666 -0
  100. package/dist/db/migrations/00000000000000_baseline.sql +4867 -0
  101. package/dist/db/migrations/20260405193000_add_mcp_sessions.sql +33 -0
  102. package/dist/db/migrations/20260408120000_remove_system_connectors.sql +48 -0
  103. package/dist/db/migrations/20260408120001_optional_compiled_code.sql +6 -0
  104. package/dist/db/migrations/20260409110000_add_active_watcher_run_index.sql +9 -0
  105. package/dist/db/migrations/20260409130000_connector_default_config.sql +5 -0
  106. package/dist/db/migrations/20260410120000_add_agent_secrets.sql +25 -0
  107. package/dist/db/migrations/20260413170000_add_watcher_group_id.sql +67 -0
  108. package/dist/db/migrations/20260416120000_add_entity_wa_jid_index.sql +14 -0
  109. package/dist/db/migrations/20260417100000_add_entity_identities.sql +77 -0
  110. package/dist/db/migrations/20260418100000_add_auth_runs.sql +83 -0
  111. package/dist/db/migrations/20260418110000_add_runs_created_by_user.sql +18 -0
  112. package/dist/db/migrations/20260419120000_add_event_identity_indexes.sql +56 -0
  113. package/dist/db/migrations/20260420120000_extend_reserved_org_slugs.sql +56 -0
  114. package/dist/db/migrations/20260424030000_add_watcher_run_correlation.sql +52 -0
  115. package/dist/db/migrations/20260424130000_relax_events_client_id_fk.sql +47 -0
  116. package/dist/db/migrations/20260425100000_normalize_watcher_feedback.sql +91 -0
  117. package/dist/db/migrations/20260425120000_add_run_diagnostics.sql +20 -0
  118. package/dist/db/migrations/20260425130000_add_repair_agent_plumbing.sql +46 -0
  119. package/dist/db/migrations/20260426120000_entities_entity_type_fk.sql +101 -0
  120. package/dist/db/migrations/20260426130000_db_integrity_cleanup.sql +104 -0
  121. package/dist/db/migrations/20260426130001_db_integrity_cleanup_concurrent.sql +187 -0
  122. package/dist/db/migrations/20260427133000_events_created_by_nullable.sql +74 -0
  123. package/dist/db/migrations/20260427140000_identity_engine_indexes.sql +140 -0
  124. package/dist/db/migrations/20260427150000_drop_events_source_id.sql +177 -0
  125. package/dist/db/migrations/20260427160000_drop_dead_schema.sql +76 -0
  126. package/dist/db/migrations/20260427170000_market_founder_to_member.sql +364 -0
  127. package/dist/db/migrations/20260428040000_cascade_events_watchers_org_fk.sql +66 -0
  128. package/dist/db/migrations/20260428050000_add_runs_approved_input.sql +9 -0
  129. package/dist/db/migrations/20260429010000_auth_profile_tenant_scoped_fk.sql +79 -0
  130. package/dist/db/migrations/20260429060000_extend_runs_for_lobu_queue.sql +108 -0
  131. package/dist/db/migrations/20260429120000_agent_changed_notify.sql +97 -0
  132. package/dist/db/migrations/20260429120100_user_auth_profiles_and_model_prefs.sql +36 -0
  133. package/dist/db/migrations/20260429120200_fix_notify_old_keys.sql +130 -0
  134. package/dist/db/migrations/20260429130000_oauth_states_cli_sessions_rate_limits.sql +83 -0
  135. package/dist/db/migrations/20260429140000_phase8_grants_chat_connections_mcp_sessions.sql +84 -0
  136. package/dist/db/migrations/20260429140100_runs_priority_expires_at_retry_delay.sql +44 -0
  137. package/dist/db/migrations/20260429180000_drop_invalidatable_cache_triggers.sql +25 -0
  138. package/dist/db/migrations/20260430005614_agents_apply_fields.sql +21 -0
  139. package/dist/db/migrations/20260430022231_fix_connection_config_encryption.sql +69 -0
  140. package/dist/db/migrations/20260430151215_add_task_run_type.sql +77 -0
  141. package/dist/db/migrations/20260501000000_drop_cli_sessions.sql +27 -0
  142. package/dist/db/migrations/20260501133000_lobu_memory_mcp_id.sql +117 -0
  143. package/dist/db/migrations/20260502000000_drop_chat_connections.sql +60 -0
  144. package/dist/db/migrations/20260503000000_agent_secrets_org_scope.sql +56 -0
  145. package/dist/db/migrations/20260504000000_flatten_agents_drop_sandbox_model.sql +48 -0
  146. package/dist/index.d.ts.map +1 -1
  147. package/dist/index.js +147 -23
  148. package/dist/index.js.map +1 -1
  149. package/dist/internal/api-client.d.ts +4 -8
  150. package/dist/internal/api-client.d.ts.map +1 -1
  151. package/dist/internal/api-client.js +1 -1
  152. package/dist/internal/api-client.js.map +1 -1
  153. package/dist/internal/context.js +2 -2
  154. package/dist/internal/context.js.map +1 -1
  155. package/dist/internal/credentials.d.ts.map +1 -1
  156. package/dist/internal/credentials.js +6 -1
  157. package/dist/internal/credentials.js.map +1 -1
  158. package/dist/internal/index.d.ts +2 -3
  159. package/dist/internal/index.d.ts.map +1 -1
  160. package/dist/internal/index.js +2 -2
  161. package/dist/internal/index.js.map +1 -1
  162. package/dist/internal/oauth.d.ts +6 -5
  163. package/dist/internal/oauth.d.ts.map +1 -1
  164. package/dist/internal/oauth.js +2 -2
  165. package/dist/internal/project-link.d.ts +10 -0
  166. package/dist/internal/project-link.d.ts.map +1 -0
  167. package/dist/internal/project-link.js +48 -0
  168. package/dist/internal/project-link.js.map +1 -0
  169. package/dist/providers.json +2 -2
  170. package/dist/server.bundle.mjs +3090 -4321
  171. package/dist/start-local.bundle.mjs +71481 -0
  172. package/dist/templates/README.md.tmpl +10 -11
  173. package/package.json +14 -12
  174. package/dist/__tests__/chat.integration.test.d.ts +0 -2
  175. package/dist/__tests__/chat.integration.test.d.ts.map +0 -1
  176. package/dist/__tests__/chat.integration.test.js +0 -337
  177. package/dist/__tests__/chat.integration.test.js.map +0 -1
  178. package/dist/__tests__/dev.test.d.ts +0 -2
  179. package/dist/__tests__/dev.test.d.ts.map +0 -1
  180. package/dist/__tests__/dev.test.js +0 -25
  181. package/dist/__tests__/dev.test.js.map +0 -1
  182. package/dist/__tests__/init-memory.test.d.ts +0 -2
  183. package/dist/__tests__/init-memory.test.d.ts.map +0 -1
  184. package/dist/__tests__/init-memory.test.js +0 -45
  185. package/dist/__tests__/init-memory.test.js.map +0 -1
  186. package/dist/__tests__/token.test.d.ts +0 -2
  187. package/dist/__tests__/token.test.d.ts.map +0 -1
  188. package/dist/__tests__/token.test.js +0 -52
  189. package/dist/__tests__/token.test.js.map +0 -1
  190. package/dist/commands/_lib/apply/__tests__/client.test.d.ts +0 -2
  191. package/dist/commands/_lib/apply/__tests__/client.test.d.ts.map +0 -1
  192. package/dist/commands/_lib/apply/__tests__/client.test.js +0 -23
  193. package/dist/commands/_lib/apply/__tests__/client.test.js.map +0 -1
  194. package/dist/commands/_lib/apply/__tests__/desired-state.test.d.ts +0 -2
  195. package/dist/commands/_lib/apply/__tests__/desired-state.test.d.ts.map +0 -1
  196. package/dist/commands/_lib/apply/__tests__/desired-state.test.js +0 -140
  197. package/dist/commands/_lib/apply/__tests__/desired-state.test.js.map +0 -1
  198. package/dist/commands/_lib/apply/__tests__/diff.test.d.ts +0 -2
  199. package/dist/commands/_lib/apply/__tests__/diff.test.d.ts.map +0 -1
  200. package/dist/commands/_lib/apply/__tests__/diff.test.js +0 -378
  201. package/dist/commands/_lib/apply/__tests__/diff.test.js.map +0 -1
  202. package/dist/commands/apply.d.ts +0 -3
  203. package/dist/commands/apply.d.ts.map +0 -1
  204. package/dist/commands/apply.js +0 -5
  205. package/dist/commands/apply.js.map +0 -1
  206. package/dist/commands/memory/_lib/openclaw-auth.test.d.ts +0 -2
  207. package/dist/commands/memory/_lib/openclaw-auth.test.d.ts.map +0 -1
  208. package/dist/commands/memory/_lib/openclaw-auth.test.js +0 -9
  209. package/dist/commands/memory/_lib/openclaw-auth.test.js.map +0 -1
  210. package/dist/internal/__tests__/api-client.test.d.ts +0 -2
  211. package/dist/internal/__tests__/api-client.test.d.ts.map +0 -1
  212. package/dist/internal/__tests__/api-client.test.js +0 -95
  213. package/dist/internal/__tests__/api-client.test.js.map +0 -1
  214. package/dist/internal/__tests__/context.test.d.ts +0 -2
  215. package/dist/internal/__tests__/context.test.d.ts.map +0 -1
  216. package/dist/internal/__tests__/context.test.js +0 -77
  217. package/dist/internal/__tests__/context.test.js.map +0 -1
@@ -0,0 +1,364 @@
1
+ -- migrate:up
2
+
3
+ -- Migrate `founder` entities in the `market` org to `$member` entities so
4
+ -- the identity engine can adopt them on OAuth sign-in.
5
+ --
6
+ -- Rationale: the engine binds a signing-in user to an existing `$member` by
7
+ -- multi-namespace identity lookup (email, linkedin_url, github_username,
8
+ -- etc.). Pre-curated founders need to BE `$member` rows for that adoption
9
+ -- to fire. Keeping `founder` as a separate type means the engine can never
10
+ -- bind a user to their pre-curated profile.
11
+ --
12
+ -- The post-migration shape:
13
+ -- - `$member` entities in market carry the founder's metadata, including
14
+ -- `metadata.role='founder'` so the public browse route in
15
+ -- `lobu-ai/lobu-web` can filter `$member` rows on role.
16
+ -- - `entity_identities` rows are written for every namespace value the
17
+ -- migration can extract (email, linkedin_url, twitter_handle).
18
+ -- - Existing relationship rows (`works_at`, `founded`, `previously_at`,
19
+ -- `educated_at`, etc.) are re-pointed from founder.id to the new
20
+ -- $member.id.
21
+ -- - Relationship-type rules are widened to accept `$member` as the
22
+ -- source slug for the relationships that pointed at founders.
23
+ -- - Source `founder` rows are soft-deleted (deleted_at=NOW()) — kept for
24
+ -- audit history; the application path filters them out.
25
+ --
26
+ -- Idempotent: each step uses ON CONFLICT / WHERE NOT EXISTS, so a re-run
27
+ -- on a partially-migrated DB completes the unfinished work without
28
+ -- duplicating rows. Safe to run on a fresh DB (no founders yet) — every
29
+ -- query returns the empty set.
30
+
31
+ DO $$
32
+ DECLARE
33
+ v_market_org_id text;
34
+ v_market_org_slug text;
35
+ v_member_type_id integer;
36
+ v_founder_type_id integer;
37
+ BEGIN
38
+ FOR v_market_org_id, v_market_org_slug IN
39
+ SELECT id, slug
40
+ FROM public.organization
41
+ WHERE slug IN ('market', 'venture-capital')
42
+ ORDER BY CASE slug WHEN 'market' THEN 0 ELSE 1 END
43
+ LOOP
44
+ RAISE NOTICE 'running founder→$member migration for org %', v_market_org_slug;
45
+
46
+ SELECT id INTO v_member_type_id
47
+ FROM public.entity_types
48
+ WHERE organization_id = v_market_org_id AND slug = '$member' AND deleted_at IS NULL
49
+ LIMIT 1;
50
+ IF v_member_type_id IS NULL THEN
51
+ INSERT INTO public.entity_types (organization_id, slug, name, created_at, updated_at)
52
+ VALUES (v_market_org_id, '$member', 'Member', NOW(), NOW())
53
+ RETURNING id INTO v_member_type_id;
54
+ END IF;
55
+
56
+ SELECT id INTO v_founder_type_id
57
+ FROM public.entity_types
58
+ WHERE organization_id = v_market_org_id AND slug = 'founder' AND deleted_at IS NULL
59
+ LIMIT 1;
60
+
61
+ IF v_founder_type_id IS NULL THEN
62
+ RAISE NOTICE 'no founder entity_type in % — nothing to migrate', v_market_org_slug;
63
+ CONTINUE;
64
+ END IF;
65
+
66
+ -- 1. Create $member rows for each founder. Idempotent via slug uniqueness:
67
+ -- we generate a deterministic slug derived from the founder id so a
68
+ -- re-run without source data still produces the same slug.
69
+ INSERT INTO public.entities (
70
+ name, slug, entity_type_id, organization_id, parent_id,
71
+ metadata, created_by, created_at, updated_at
72
+ )
73
+ SELECT
74
+ f.name,
75
+ 'member-from-founder-' || f.id::text AS slug,
76
+ v_member_type_id,
77
+ v_market_org_id,
78
+ NULL,
79
+ COALESCE(f.metadata, '{}'::jsonb) || jsonb_build_object('role', 'founder', 'migrated_from_founder_id', f.id),
80
+ COALESCE(f.created_by, 'system'),
81
+ NOW(),
82
+ NOW()
83
+ FROM public.entities f
84
+ WHERE f.organization_id = v_market_org_id
85
+ AND f.entity_type_id = v_founder_type_id
86
+ AND f.deleted_at IS NULL
87
+ AND NOT EXISTS (
88
+ SELECT 1 FROM public.entities m
89
+ WHERE m.organization_id = v_market_org_id
90
+ AND m.entity_type_id = v_member_type_id
91
+ AND m.metadata->>'migrated_from_founder_id' = f.id::text
92
+ AND m.deleted_at IS NULL
93
+ );
94
+
95
+ -- 2. Write entity_identities rows for any namespace values present on
96
+ -- the founder metadata. Limited to the namespaces the engine consults.
97
+ -- email, linkedin_url, twitter_handle.
98
+ INSERT INTO public.entity_identities (
99
+ organization_id, entity_id, namespace, identifier, source_connector
100
+ )
101
+ SELECT
102
+ v_market_org_id,
103
+ m.id,
104
+ 'email',
105
+ LOWER(f.metadata->>'email'),
106
+ 'migration:founder_to_member'
107
+ FROM public.entities m
108
+ JOIN public.entities f
109
+ ON f.id = (m.metadata->>'migrated_from_founder_id')::bigint
110
+ WHERE m.organization_id = v_market_org_id
111
+ AND m.entity_type_id = v_member_type_id
112
+ AND m.deleted_at IS NULL
113
+ AND f.metadata ? 'email'
114
+ AND f.metadata->>'email' IS NOT NULL
115
+ AND f.metadata->>'email' <> ''
116
+ ON CONFLICT (organization_id, namespace, identifier) WHERE deleted_at IS NULL
117
+ DO NOTHING;
118
+
119
+ INSERT INTO public.entity_identities (
120
+ organization_id, entity_id, namespace, identifier, source_connector
121
+ )
122
+ SELECT
123
+ v_market_org_id,
124
+ m.id,
125
+ 'linkedin_url',
126
+ f.metadata->>'linkedin_url',
127
+ 'migration:founder_to_member'
128
+ FROM public.entities m
129
+ JOIN public.entities f
130
+ ON f.id = (m.metadata->>'migrated_from_founder_id')::bigint
131
+ WHERE m.organization_id = v_market_org_id
132
+ AND m.entity_type_id = v_member_type_id
133
+ AND m.deleted_at IS NULL
134
+ AND f.metadata ? 'linkedin_url'
135
+ AND f.metadata->>'linkedin_url' IS NOT NULL
136
+ AND f.metadata->>'linkedin_url' <> ''
137
+ ON CONFLICT (organization_id, namespace, identifier) WHERE deleted_at IS NULL
138
+ DO NOTHING;
139
+
140
+ INSERT INTO public.entity_identities (
141
+ organization_id, entity_id, namespace, identifier, source_connector
142
+ )
143
+ SELECT
144
+ v_market_org_id,
145
+ m.id,
146
+ 'twitter_handle',
147
+ LOWER(REPLACE(f.metadata->>'twitter_handle', '@', '')),
148
+ 'migration:founder_to_member'
149
+ FROM public.entities m
150
+ JOIN public.entities f
151
+ ON f.id = (m.metadata->>'migrated_from_founder_id')::bigint
152
+ WHERE m.organization_id = v_market_org_id
153
+ AND m.entity_type_id = v_member_type_id
154
+ AND m.deleted_at IS NULL
155
+ AND f.metadata ? 'twitter_handle'
156
+ AND f.metadata->>'twitter_handle' IS NOT NULL
157
+ AND f.metadata->>'twitter_handle' <> ''
158
+ ON CONFLICT (organization_id, namespace, identifier) WHERE deleted_at IS NULL
159
+ DO NOTHING;
160
+
161
+ -- 3. Re-point existing relationships pointing FROM the founder rows to
162
+ -- point FROM the new $member rows. The reverse direction (relationships
163
+ -- pointing AT founders) is handled symmetrically. If the destination edge
164
+ -- already exists, archive the founder edge first so the live-triple unique
165
+ -- index remains satisfied.
166
+ UPDATE public.entity_relationships r
167
+ SET deleted_at = NOW(), updated_at = NOW()
168
+ FROM public.entities f
169
+ JOIN public.entities m
170
+ ON m.organization_id = f.organization_id
171
+ AND m.entity_type_id = v_member_type_id
172
+ AND m.metadata->>'migrated_from_founder_id' = f.id::text
173
+ AND m.deleted_at IS NULL
174
+ WHERE r.from_entity_id = f.id
175
+ AND r.deleted_at IS NULL
176
+ AND f.organization_id = v_market_org_id
177
+ AND f.entity_type_id = v_founder_type_id
178
+ AND f.deleted_at IS NULL
179
+ AND EXISTS (
180
+ SELECT 1
181
+ FROM public.entity_relationships existing
182
+ WHERE existing.from_entity_id = m.id
183
+ AND existing.to_entity_id = r.to_entity_id
184
+ AND existing.relationship_type_id = r.relationship_type_id
185
+ AND existing.deleted_at IS NULL
186
+ AND existing.id <> r.id
187
+ );
188
+
189
+ UPDATE public.entity_relationships r
190
+ SET from_entity_id = m.id, updated_at = NOW()
191
+ FROM public.entities f
192
+ JOIN public.entities m
193
+ ON m.organization_id = f.organization_id
194
+ AND m.entity_type_id = v_member_type_id
195
+ AND m.metadata->>'migrated_from_founder_id' = f.id::text
196
+ AND m.deleted_at IS NULL
197
+ WHERE r.from_entity_id = f.id
198
+ AND r.deleted_at IS NULL
199
+ AND f.organization_id = v_market_org_id
200
+ AND f.entity_type_id = v_founder_type_id
201
+ AND f.deleted_at IS NULL;
202
+
203
+ UPDATE public.entity_relationships r
204
+ SET deleted_at = NOW(), updated_at = NOW()
205
+ FROM public.entities f
206
+ JOIN public.entities m
207
+ ON m.organization_id = f.organization_id
208
+ AND m.entity_type_id = v_member_type_id
209
+ AND m.metadata->>'migrated_from_founder_id' = f.id::text
210
+ AND m.deleted_at IS NULL
211
+ WHERE r.to_entity_id = f.id
212
+ AND r.deleted_at IS NULL
213
+ AND f.organization_id = v_market_org_id
214
+ AND f.entity_type_id = v_founder_type_id
215
+ AND f.deleted_at IS NULL
216
+ AND EXISTS (
217
+ SELECT 1
218
+ FROM public.entity_relationships existing
219
+ WHERE existing.from_entity_id = r.from_entity_id
220
+ AND existing.to_entity_id = m.id
221
+ AND existing.relationship_type_id = r.relationship_type_id
222
+ AND existing.deleted_at IS NULL
223
+ AND existing.id <> r.id
224
+ );
225
+
226
+ UPDATE public.entity_relationships r
227
+ SET to_entity_id = m.id, updated_at = NOW()
228
+ FROM public.entities f
229
+ JOIN public.entities m
230
+ ON m.organization_id = f.organization_id
231
+ AND m.entity_type_id = v_member_type_id
232
+ AND m.metadata->>'migrated_from_founder_id' = f.id::text
233
+ AND m.deleted_at IS NULL
234
+ WHERE r.to_entity_id = f.id
235
+ AND r.deleted_at IS NULL
236
+ AND f.organization_id = v_market_org_id
237
+ AND f.entity_type_id = v_founder_type_id
238
+ AND f.deleted_at IS NULL;
239
+
240
+ -- 4. Widen relationship_type rules to accept `$member` as a source
241
+ -- where they previously accepted `founder`. Keep both slugs for now —
242
+ -- if the founder slug ever resurrects (e.g. for catalog imports that
243
+ -- still emit the old type), the rules tolerate it.
244
+ INSERT INTO public.entity_relationship_type_rules (
245
+ relationship_type_id, source_entity_type_slug, target_entity_type_slug, created_at
246
+ )
247
+ SELECT DISTINCT r.relationship_type_id, '$member', r.target_entity_type_slug, NOW()
248
+ FROM public.entity_relationship_type_rules r
249
+ JOIN public.entity_relationship_types rt ON rt.id = r.relationship_type_id
250
+ WHERE r.source_entity_type_slug = 'founder'
251
+ AND rt.organization_id = v_market_org_id
252
+ AND NOT EXISTS (
253
+ SELECT 1 FROM public.entity_relationship_type_rules existing
254
+ WHERE existing.relationship_type_id = r.relationship_type_id
255
+ AND existing.source_entity_type_slug = '$member'
256
+ AND existing.target_entity_type_slug = r.target_entity_type_slug
257
+ );
258
+
259
+ INSERT INTO public.entity_relationship_type_rules (
260
+ relationship_type_id, source_entity_type_slug, target_entity_type_slug, created_at
261
+ )
262
+ SELECT DISTINCT r.relationship_type_id, r.source_entity_type_slug, '$member', NOW()
263
+ FROM public.entity_relationship_type_rules r
264
+ JOIN public.entity_relationship_types rt ON rt.id = r.relationship_type_id
265
+ WHERE r.target_entity_type_slug = 'founder'
266
+ AND rt.organization_id = v_market_org_id
267
+ AND NOT EXISTS (
268
+ SELECT 1 FROM public.entity_relationship_type_rules existing
269
+ WHERE existing.relationship_type_id = r.relationship_type_id
270
+ AND existing.source_entity_type_slug = r.source_entity_type_slug
271
+ AND existing.target_entity_type_slug = '$member'
272
+ );
273
+
274
+ -- 5. Repoint any pre-existing entity_identities rows that pointed at
275
+ -- a founder. Step 2 wrote *new* identity rows for the $member, but if
276
+ -- some other path had already written identity rows on the founder
277
+ -- (e.g. an earlier provisioning script), those would dangle once the
278
+ -- founder is soft-deleted in step 7 — entity-identity lookups join on
279
+ -- entities.deleted_at IS NULL and silently miss the binding.
280
+ UPDATE public.entity_identities ei
281
+ SET deleted_at = NOW(), updated_at = NOW()
282
+ FROM public.entities f
283
+ JOIN public.entities m
284
+ ON m.organization_id = f.organization_id
285
+ AND m.entity_type_id = v_member_type_id
286
+ AND m.metadata->>'migrated_from_founder_id' = f.id::text
287
+ AND m.deleted_at IS NULL
288
+ WHERE ei.entity_id = f.id
289
+ AND ei.organization_id = v_market_org_id
290
+ AND ei.deleted_at IS NULL
291
+ AND f.organization_id = v_market_org_id
292
+ AND f.entity_type_id = v_founder_type_id
293
+ AND EXISTS (
294
+ SELECT 1
295
+ FROM public.entity_identities existing
296
+ WHERE existing.organization_id = ei.organization_id
297
+ AND existing.namespace = ei.namespace
298
+ AND existing.identifier = ei.identifier
299
+ AND existing.entity_id = m.id
300
+ AND existing.deleted_at IS NULL
301
+ AND existing.id <> ei.id
302
+ );
303
+
304
+ UPDATE public.entity_identities ei
305
+ SET entity_id = m.id, updated_at = NOW()
306
+ FROM public.entities f
307
+ JOIN public.entities m
308
+ ON m.organization_id = f.organization_id
309
+ AND m.entity_type_id = v_member_type_id
310
+ AND m.metadata->>'migrated_from_founder_id' = f.id::text
311
+ AND m.deleted_at IS NULL
312
+ WHERE ei.entity_id = f.id
313
+ AND ei.organization_id = v_market_org_id
314
+ AND ei.deleted_at IS NULL
315
+ AND f.organization_id = v_market_org_id
316
+ AND f.entity_type_id = v_founder_type_id;
317
+
318
+ -- 6. Rewrite event.entity_ids arrays so historical events on founder
319
+ -- rows resolve to the corresponding $member going forward. array_replace
320
+ -- is a no-op on a re-run because after the first pass the founder id is
321
+ -- gone from the array and the WHERE filter excludes the row.
322
+ UPDATE public.events e
323
+ SET entity_ids = array_replace(e.entity_ids, f.id, m.id)
324
+ FROM public.entities f
325
+ JOIN public.entities m
326
+ ON m.organization_id = f.organization_id
327
+ AND m.entity_type_id = v_member_type_id
328
+ AND m.metadata->>'migrated_from_founder_id' = f.id::text
329
+ AND m.deleted_at IS NULL
330
+ WHERE e.entity_ids @> ARRAY[f.id]
331
+ AND e.organization_id = v_market_org_id
332
+ AND f.organization_id = v_market_org_id
333
+ AND f.entity_type_id = v_founder_type_id;
334
+
335
+ -- 7. Soft-delete the source founder rows. Audit trail survives in the
336
+ -- entities table itself; queries already filter `deleted_at IS NULL`.
337
+ UPDATE public.entities f
338
+ SET deleted_at = NOW(), updated_at = NOW()
339
+ WHERE f.organization_id = v_market_org_id
340
+ AND f.entity_type_id = v_founder_type_id
341
+ AND f.deleted_at IS NULL
342
+ AND EXISTS (
343
+ SELECT 1 FROM public.entities m
344
+ WHERE m.organization_id = v_market_org_id
345
+ AND m.entity_type_id = v_member_type_id
346
+ AND m.metadata->>'migrated_from_founder_id' = f.id::text
347
+ AND m.deleted_at IS NULL
348
+ );
349
+ END LOOP;
350
+ END $$;
351
+
352
+
353
+ -- migrate:down
354
+
355
+ -- Fail loudly. A bare `SELECT 1` would let an automatic rollback (CI,
356
+ -- incident response, dbmate down) succeed silently while leaving the DB
357
+ -- in the migrated state, masking the irreversibility. Operators wanting
358
+ -- to revert run a manual playbook: clear deleted_at on the founder rows,
359
+ -- re-point relationships using `metadata.migrated_from_founder_id`, then
360
+ -- delete the new $member rows.
361
+ DO $$
362
+ BEGIN
363
+ RAISE EXCEPTION 'irreversible: founder→$member is a one-way data migration. Reverse by manual playbook only.';
364
+ END $$;
@@ -0,0 +1,66 @@
1
+ -- migrate:up transaction:false
2
+
3
+ -- Add ON DELETE CASCADE FK on events.organization_id and watchers.organization_id.
4
+ --
5
+ -- These two columns were declared as `text NOT NULL` with no FK to organization,
6
+ -- so dropping an org left orphaned events/watchers behind that had to be DELETE'd
7
+ -- separately. Every other org-scoped table has a CASCADE FK; these two were
8
+ -- oversights. Add them so future org deletes are atomic with their event/watcher
9
+ -- data.
10
+ --
11
+ -- Lock-window strategy: ADD CONSTRAINT NOT VALID adds the catalog row under
12
+ -- a brief ACCESS EXCLUSIVE without scanning the table. VALIDATE then takes
13
+ -- only SHARE UPDATE EXCLUSIVE so concurrent reads and writes are unaffected.
14
+ -- Running with `transaction:false` lets each ALTER release its lock on
15
+ -- completion — keeping ADD + VALIDATE in a single transaction would hold
16
+ -- ACCESS EXCLUSIVE through the validate scan and block writers on the
17
+ -- 575k-row events table.
18
+ --
19
+ -- Idempotency: each ADD is gated on `pg_constraint`. If the FK already
20
+ -- exists (e.g. it was applied out-of-band before this migration was
21
+ -- recorded), the ADD is skipped; VALIDATE on a constraint that's already
22
+ -- validated is a no-op.
23
+
24
+ SET lock_timeout = '5s';
25
+
26
+ -- 1. events.organization_id → organization(id) ON DELETE CASCADE.
27
+ DO $$
28
+ BEGIN
29
+ IF NOT EXISTS (
30
+ SELECT 1
31
+ FROM pg_constraint
32
+ WHERE conrelid = 'public.events'::regclass
33
+ AND conname = 'events_organization_id_fkey'
34
+ ) THEN
35
+ ALTER TABLE public.events
36
+ ADD CONSTRAINT events_organization_id_fkey
37
+ FOREIGN KEY (organization_id) REFERENCES public.organization(id) ON DELETE CASCADE
38
+ NOT VALID;
39
+ END IF;
40
+ END$$;
41
+
42
+ ALTER TABLE public.events VALIDATE CONSTRAINT events_organization_id_fkey;
43
+
44
+ -- 2. watchers.organization_id → organization(id) ON DELETE CASCADE.
45
+ DO $$
46
+ BEGIN
47
+ IF NOT EXISTS (
48
+ SELECT 1
49
+ FROM pg_constraint
50
+ WHERE conrelid = 'public.watchers'::regclass
51
+ AND conname = 'watchers_organization_id_fkey'
52
+ ) THEN
53
+ ALTER TABLE public.watchers
54
+ ADD CONSTRAINT watchers_organization_id_fkey
55
+ FOREIGN KEY (organization_id) REFERENCES public.organization(id) ON DELETE CASCADE
56
+ NOT VALID;
57
+ END IF;
58
+ END$$;
59
+
60
+ ALTER TABLE public.watchers VALIDATE CONSTRAINT watchers_organization_id_fkey;
61
+
62
+
63
+ -- migrate:down transaction:false
64
+
65
+ ALTER TABLE public.events DROP CONSTRAINT IF EXISTS events_organization_id_fkey;
66
+ ALTER TABLE public.watchers DROP CONSTRAINT IF EXISTS watchers_organization_id_fkey;
@@ -0,0 +1,9 @@
1
+ -- migrate:up
2
+
3
+ ALTER TABLE public.runs
4
+ ADD COLUMN IF NOT EXISTS approved_input jsonb;
5
+
6
+ -- migrate:down
7
+
8
+ ALTER TABLE public.runs
9
+ DROP COLUMN IF EXISTS approved_input;
@@ -0,0 +1,79 @@
1
+ -- migrate:up
2
+
3
+ -- Enforce tenant scope on connection.auth_profile FKs.
4
+ --
5
+ -- Background: connections.app_auth_profile_id and connections.auth_profile_id
6
+ -- referenced auth_profiles(id) without checking that the profile belongs to
7
+ -- the same organization as the connection. A bug in some prior create path
8
+ -- left at least one connection (id=212, org buremba) pointing at an auth
9
+ -- profile in a different org (id=10, org_617e78013e6ff72d). The UI correctly
10
+ -- refused to load it ("Auth profile 'reddit-oauth-app' not found"), but the
11
+ -- bad reference blocked setup.
12
+ --
13
+ -- Fix: switch both FKs to multi-column (organization_id, profile_id) so
14
+ -- references can't escape their tenant. MATCH SIMPLE (the default) keeps
15
+ -- profile_id-NULL satisfying the constraint, which is what we want — the
16
+ -- column stays nullable.
17
+
18
+ -- 1. Clean up any cross-org or dangling references in existing data.
19
+ -- Idempotent: 0 rows once applied; safe to re-run.
20
+ UPDATE connections c
21
+ SET app_auth_profile_id = NULL, updated_at = NOW()
22
+ WHERE app_auth_profile_id IS NOT NULL
23
+ AND NOT EXISTS (
24
+ SELECT 1 FROM auth_profiles ap
25
+ WHERE ap.id = c.app_auth_profile_id
26
+ AND ap.organization_id = c.organization_id
27
+ );
28
+
29
+ UPDATE connections c
30
+ SET auth_profile_id = NULL, updated_at = NOW()
31
+ WHERE auth_profile_id IS NOT NULL
32
+ AND NOT EXISTS (
33
+ SELECT 1 FROM auth_profiles ap
34
+ WHERE ap.id = c.auth_profile_id
35
+ AND ap.organization_id = c.organization_id
36
+ );
37
+
38
+ -- 2. Add (organization_id, id) UNIQUE on auth_profiles to give the new FK a
39
+ -- target. Cheap on the existing 31-row table.
40
+ ALTER TABLE auth_profiles
41
+ ADD CONSTRAINT auth_profiles_org_id_unique UNIQUE (organization_id, id);
42
+
43
+ -- 3. Replace single-column FKs with tenant-scoped multi-column FKs.
44
+ ALTER TABLE connections
45
+ DROP CONSTRAINT connections_app_auth_profile_id_fkey;
46
+ ALTER TABLE connections
47
+ ADD CONSTRAINT connections_app_auth_profile_id_fkey
48
+ FOREIGN KEY (organization_id, app_auth_profile_id)
49
+ REFERENCES auth_profiles (organization_id, id)
50
+ ON DELETE SET NULL;
51
+
52
+ ALTER TABLE connections
53
+ DROP CONSTRAINT connections_auth_profile_id_fkey;
54
+ ALTER TABLE connections
55
+ ADD CONSTRAINT connections_auth_profile_id_fkey
56
+ FOREIGN KEY (organization_id, auth_profile_id)
57
+ REFERENCES auth_profiles (organization_id, id)
58
+ ON DELETE SET NULL;
59
+
60
+ -- migrate:down
61
+
62
+ ALTER TABLE connections
63
+ DROP CONSTRAINT connections_app_auth_profile_id_fkey;
64
+ ALTER TABLE connections
65
+ ADD CONSTRAINT connections_app_auth_profile_id_fkey
66
+ FOREIGN KEY (app_auth_profile_id)
67
+ REFERENCES auth_profiles (id)
68
+ ON DELETE SET NULL;
69
+
70
+ ALTER TABLE connections
71
+ DROP CONSTRAINT connections_auth_profile_id_fkey;
72
+ ALTER TABLE connections
73
+ ADD CONSTRAINT connections_auth_profile_id_fkey
74
+ FOREIGN KEY (auth_profile_id)
75
+ REFERENCES auth_profiles (id)
76
+ ON DELETE SET NULL;
77
+
78
+ ALTER TABLE auth_profiles
79
+ DROP CONSTRAINT auth_profiles_org_id_unique;
@@ -0,0 +1,108 @@
1
+ -- migrate:up
2
+
3
+ -- Phase 5 of Redis -> Postgres migration: route the lobu queue (chat_message,
4
+ -- thread_message_*, thread_response, schedule, agent_run) through the runs
5
+ -- table instead of BullMQ.
6
+ --
7
+ -- 1. Extend the run_type CHECK to allow the new lobu-queue lanes alongside
8
+ -- the existing connector/auth/embed lanes.
9
+ -- 2. Allow organization_id to be NULL for lobu-queue runs (chat_message,
10
+ -- schedule, etc.). The original lanes still require it via the partial
11
+ -- CHECK below.
12
+ -- 3. Add columns required for in-process claim + retry:
13
+ -- queue_name — distinguishes thread_message_* sub-queues that all
14
+ -- share run_type='chat_message'.
15
+ -- idempotency_key — singletonKey dedup; partial unique index below.
16
+ -- attempts — current retry count.
17
+ -- max_attempts — cap for retries before DLQ/failed.
18
+ -- run_at — when the row becomes claimable; supports delayMs.
19
+ -- 4. Add the claim index used by the in-process polling loop. Filter to the
20
+ -- new run types so the connector worker (run_type IN ('sync','action',...))
21
+ -- is never woken by lobu-queue inserts and vice versa.
22
+
23
+ -- Drop the old run_type CHECK and re-add with the new lobu-queue lanes.
24
+ ALTER TABLE public.runs
25
+ DROP CONSTRAINT IF EXISTS runs_run_type_check;
26
+
27
+ ALTER TABLE public.runs
28
+ ADD CONSTRAINT runs_run_type_check CHECK (run_type = ANY (ARRAY[
29
+ 'sync'::text,
30
+ 'action'::text,
31
+ 'embed_backfill'::text,
32
+ 'watcher'::text,
33
+ 'auth'::text,
34
+ 'chat_message'::text,
35
+ 'schedule'::text,
36
+ 'agent_run'::text,
37
+ 'internal'::text
38
+ ]));
39
+
40
+ -- organization_id is required for connector lanes (sync/action/embed/watcher/
41
+ -- auth) but optional for lobu-queue lanes. Drop NOT NULL and enforce per-lane.
42
+ ALTER TABLE public.runs
43
+ ALTER COLUMN organization_id DROP NOT NULL;
44
+
45
+ ALTER TABLE public.runs
46
+ ADD CONSTRAINT runs_legacy_org_required CHECK (
47
+ run_type NOT IN (
48
+ 'sync', 'action', 'embed_backfill', 'watcher', 'auth'
49
+ )
50
+ OR organization_id IS NOT NULL
51
+ );
52
+
53
+ -- Lobu queue columns.
54
+ ALTER TABLE public.runs
55
+ ADD COLUMN IF NOT EXISTS queue_name text,
56
+ ADD COLUMN IF NOT EXISTS idempotency_key text,
57
+ ADD COLUMN IF NOT EXISTS attempts integer NOT NULL DEFAULT 0,
58
+ ADD COLUMN IF NOT EXISTS max_attempts integer NOT NULL DEFAULT 3,
59
+ ADD COLUMN IF NOT EXISTS run_at timestamp with time zone NOT NULL DEFAULT now();
60
+
61
+ -- Idempotency: partial unique on (idempotency_key) for runs that are still in
62
+ -- a non-terminal state. Once a run completes / fails / cancels / times out it
63
+ -- no longer participates in dedup, so a future enqueue with the same singleton
64
+ -- key (e.g. a Slack retry that lands minutes after the first attempt
65
+ -- finished) can insert a fresh row. Connector lanes never set this column.
66
+ CREATE UNIQUE INDEX IF NOT EXISTS runs_idempotency_key_uniq
67
+ ON public.runs (idempotency_key)
68
+ WHERE idempotency_key IS NOT NULL
69
+ AND status IN ('pending', 'claimed', 'running');
70
+
71
+ -- Claim index for the in-process poll loop. Limited to lobu-queue run types
72
+ -- so the connector worker's HTTP-poll claim stays on its own indexes.
73
+ CREATE INDEX IF NOT EXISTS runs_lobu_claim_idx
74
+ ON public.runs (run_type, queue_name, run_at)
75
+ WHERE status = 'pending'
76
+ AND run_type IN ('chat_message', 'schedule', 'agent_run', 'internal');
77
+
78
+ -- migrate:down
79
+
80
+ DROP INDEX IF EXISTS public.runs_lobu_claim_idx;
81
+ DROP INDEX IF EXISTS public.runs_idempotency_key_uniq;
82
+
83
+ ALTER TABLE public.runs
84
+ DROP COLUMN IF EXISTS run_at,
85
+ DROP COLUMN IF EXISTS max_attempts,
86
+ DROP COLUMN IF EXISTS attempts,
87
+ DROP COLUMN IF EXISTS idempotency_key,
88
+ DROP COLUMN IF EXISTS queue_name;
89
+
90
+ ALTER TABLE public.runs
91
+ DROP CONSTRAINT IF EXISTS runs_legacy_org_required;
92
+
93
+ -- Restoring NOT NULL would fail if any lobu-queue rows still exist. Operators
94
+ -- running `migrate:down` are expected to truncate or migrate those first.
95
+ ALTER TABLE public.runs
96
+ ALTER COLUMN organization_id SET NOT NULL;
97
+
98
+ ALTER TABLE public.runs
99
+ DROP CONSTRAINT IF EXISTS runs_run_type_check;
100
+
101
+ ALTER TABLE public.runs
102
+ ADD CONSTRAINT runs_run_type_check CHECK (run_type = ANY (ARRAY[
103
+ 'sync'::text,
104
+ 'action'::text,
105
+ 'embed_backfill'::text,
106
+ 'watcher'::text,
107
+ 'auth'::text
108
+ ]));