@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.
- package/README.md +20 -27
- package/dist/bundled-skills/lobu/SKILL.md +11 -11
- package/dist/commands/_lib/apply/apply-cmd.d.ts +2 -0
- package/dist/commands/_lib/apply/apply-cmd.d.ts.map +1 -1
- package/dist/commands/_lib/apply/apply-cmd.js +26 -0
- package/dist/commands/_lib/apply/apply-cmd.js.map +1 -1
- package/dist/commands/_lib/apply/client.d.ts +1 -1
- package/dist/commands/_lib/apply/client.d.ts.map +1 -1
- package/dist/commands/_lib/apply/desired-state.js +4 -4
- package/dist/commands/_lib/apply/desired-state.js.map +1 -1
- package/dist/commands/agent.d.ts +7 -0
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +65 -1
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/chat.d.ts +12 -9
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +117 -56
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/dev.d.ts +15 -7
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +79 -44
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +136 -0
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/eval.d.ts +8 -0
- package/dist/commands/eval.d.ts.map +1 -1
- package/dist/commands/eval.js +56 -1
- package/dist/commands/eval.js.map +1 -1
- package/dist/commands/init.d.ts +20 -5
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +332 -183
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/link.d.ts +11 -0
- package/dist/commands/link.d.ts.map +1 -0
- package/dist/commands/link.js +28 -0
- package/dist/commands/link.js.map +1 -0
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +14 -2
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/memory/_lib/browser-auth-cmd.d.ts.map +1 -1
- package/dist/commands/memory/_lib/browser-auth-cmd.js +3 -3
- package/dist/commands/memory/_lib/browser-auth-cmd.js.map +1 -1
- package/dist/commands/memory/_lib/mcp.d.ts +2 -2
- package/dist/commands/memory/_lib/mcp.d.ts.map +1 -1
- package/dist/commands/memory/_lib/mcp.js +24 -12
- package/dist/commands/memory/_lib/mcp.js.map +1 -1
- package/dist/commands/memory/_lib/openclaw-auth.d.ts +1 -0
- package/dist/commands/memory/_lib/openclaw-auth.d.ts.map +1 -1
- package/dist/commands/memory/_lib/openclaw-auth.js +14 -3
- package/dist/commands/memory/_lib/openclaw-auth.js.map +1 -1
- package/dist/commands/memory/_lib/openclaw-cmd.js +1 -1
- package/dist/commands/memory/_lib/openclaw-cmd.js.map +1 -1
- package/dist/commands/memory/_lib/schema.d.ts +1 -1
- package/dist/commands/memory/_lib/schema.js +1 -1
- package/dist/commands/memory/_lib/seed-cmd.d.ts.map +1 -1
- package/dist/commands/memory/_lib/seed-cmd.js +5 -6
- package/dist/commands/memory/_lib/seed-cmd.js.map +1 -1
- package/dist/commands/memory/run.d.ts.map +1 -1
- package/dist/commands/memory/run.js +2 -2
- package/dist/commands/memory/run.js.map +1 -1
- package/dist/commands/platforms/platform-prompts.d.ts +0 -1
- package/dist/commands/platforms/platform-prompts.d.ts.map +1 -1
- package/dist/commands/platforms/platform-prompts.js +54 -8
- package/dist/commands/platforms/platform-prompts.js.map +1 -1
- package/dist/commands/telemetry.d.ts +10 -0
- package/dist/commands/telemetry.d.ts.map +1 -0
- package/dist/commands/telemetry.js +68 -0
- package/dist/commands/telemetry.js.map +1 -0
- package/dist/commands/whoami.d.ts.map +1 -1
- package/dist/commands/whoami.js +1 -1
- package/dist/commands/whoami.js.map +1 -1
- package/dist/connectors/README.md +534 -0
- package/dist/connectors/__tests__/browser-scraper-utils.test.ts +186 -0
- package/dist/connectors/browser-scraper-utils.ts +214 -0
- package/dist/connectors/capterra.ts +273 -0
- package/dist/connectors/g2.ts +286 -0
- package/dist/connectors/github.ts +1553 -0
- package/dist/connectors/glassdoor.ts +291 -0
- package/dist/connectors/gmaps.ts +197 -0
- package/dist/connectors/google_calendar.ts +631 -0
- package/dist/connectors/google_gmail.ts +751 -0
- package/dist/connectors/google_photos.ts +776 -0
- package/dist/connectors/google_play.ts +342 -0
- package/dist/connectors/hackernews.ts +471 -0
- package/dist/connectors/index.ts +23 -0
- package/dist/connectors/ios_appstore.ts +226 -0
- package/dist/connectors/linkedin.ts +471 -0
- package/dist/connectors/microsoft_outlook.ts +410 -0
- package/dist/connectors/producthunt.ts +471 -0
- package/dist/connectors/reddit.ts +600 -0
- package/dist/connectors/rss.ts +448 -0
- package/dist/connectors/spotify.ts +590 -0
- package/dist/connectors/trustpilot.ts +199 -0
- package/dist/connectors/website.ts +629 -0
- package/dist/connectors/whatsapp.ts +1073 -0
- package/dist/connectors/x.ts +526 -0
- package/dist/connectors/youtube.ts +666 -0
- package/dist/db/migrations/00000000000000_baseline.sql +4867 -0
- package/dist/db/migrations/20260405193000_add_mcp_sessions.sql +33 -0
- package/dist/db/migrations/20260408120000_remove_system_connectors.sql +48 -0
- package/dist/db/migrations/20260408120001_optional_compiled_code.sql +6 -0
- package/dist/db/migrations/20260409110000_add_active_watcher_run_index.sql +9 -0
- package/dist/db/migrations/20260409130000_connector_default_config.sql +5 -0
- package/dist/db/migrations/20260410120000_add_agent_secrets.sql +25 -0
- package/dist/db/migrations/20260413170000_add_watcher_group_id.sql +67 -0
- package/dist/db/migrations/20260416120000_add_entity_wa_jid_index.sql +14 -0
- package/dist/db/migrations/20260417100000_add_entity_identities.sql +77 -0
- package/dist/db/migrations/20260418100000_add_auth_runs.sql +83 -0
- package/dist/db/migrations/20260418110000_add_runs_created_by_user.sql +18 -0
- package/dist/db/migrations/20260419120000_add_event_identity_indexes.sql +56 -0
- package/dist/db/migrations/20260420120000_extend_reserved_org_slugs.sql +56 -0
- package/dist/db/migrations/20260424030000_add_watcher_run_correlation.sql +52 -0
- package/dist/db/migrations/20260424130000_relax_events_client_id_fk.sql +47 -0
- package/dist/db/migrations/20260425100000_normalize_watcher_feedback.sql +91 -0
- package/dist/db/migrations/20260425120000_add_run_diagnostics.sql +20 -0
- package/dist/db/migrations/20260425130000_add_repair_agent_plumbing.sql +46 -0
- package/dist/db/migrations/20260426120000_entities_entity_type_fk.sql +101 -0
- package/dist/db/migrations/20260426130000_db_integrity_cleanup.sql +104 -0
- package/dist/db/migrations/20260426130001_db_integrity_cleanup_concurrent.sql +187 -0
- package/dist/db/migrations/20260427133000_events_created_by_nullable.sql +74 -0
- package/dist/db/migrations/20260427140000_identity_engine_indexes.sql +140 -0
- package/dist/db/migrations/20260427150000_drop_events_source_id.sql +177 -0
- package/dist/db/migrations/20260427160000_drop_dead_schema.sql +76 -0
- package/dist/db/migrations/20260427170000_market_founder_to_member.sql +364 -0
- package/dist/db/migrations/20260428040000_cascade_events_watchers_org_fk.sql +66 -0
- package/dist/db/migrations/20260428050000_add_runs_approved_input.sql +9 -0
- package/dist/db/migrations/20260429010000_auth_profile_tenant_scoped_fk.sql +79 -0
- package/dist/db/migrations/20260429060000_extend_runs_for_lobu_queue.sql +108 -0
- package/dist/db/migrations/20260429120000_agent_changed_notify.sql +97 -0
- package/dist/db/migrations/20260429120100_user_auth_profiles_and_model_prefs.sql +36 -0
- package/dist/db/migrations/20260429120200_fix_notify_old_keys.sql +130 -0
- package/dist/db/migrations/20260429130000_oauth_states_cli_sessions_rate_limits.sql +83 -0
- package/dist/db/migrations/20260429140000_phase8_grants_chat_connections_mcp_sessions.sql +84 -0
- package/dist/db/migrations/20260429140100_runs_priority_expires_at_retry_delay.sql +44 -0
- package/dist/db/migrations/20260429180000_drop_invalidatable_cache_triggers.sql +25 -0
- package/dist/db/migrations/20260430005614_agents_apply_fields.sql +21 -0
- package/dist/db/migrations/20260430022231_fix_connection_config_encryption.sql +69 -0
- package/dist/db/migrations/20260430151215_add_task_run_type.sql +77 -0
- package/dist/db/migrations/20260501000000_drop_cli_sessions.sql +27 -0
- package/dist/db/migrations/20260501133000_lobu_memory_mcp_id.sql +117 -0
- package/dist/db/migrations/20260502000000_drop_chat_connections.sql +60 -0
- package/dist/db/migrations/20260503000000_agent_secrets_org_scope.sql +56 -0
- package/dist/db/migrations/20260504000000_flatten_agents_drop_sandbox_model.sql +48 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +147 -23
- package/dist/index.js.map +1 -1
- package/dist/internal/api-client.d.ts +4 -8
- package/dist/internal/api-client.d.ts.map +1 -1
- package/dist/internal/api-client.js +1 -1
- package/dist/internal/api-client.js.map +1 -1
- package/dist/internal/context.js +2 -2
- package/dist/internal/context.js.map +1 -1
- package/dist/internal/credentials.d.ts.map +1 -1
- package/dist/internal/credentials.js +6 -1
- package/dist/internal/credentials.js.map +1 -1
- package/dist/internal/index.d.ts +2 -3
- package/dist/internal/index.d.ts.map +1 -1
- package/dist/internal/index.js +2 -2
- package/dist/internal/index.js.map +1 -1
- package/dist/internal/oauth.d.ts +6 -5
- package/dist/internal/oauth.d.ts.map +1 -1
- package/dist/internal/oauth.js +2 -2
- package/dist/internal/project-link.d.ts +10 -0
- package/dist/internal/project-link.d.ts.map +1 -0
- package/dist/internal/project-link.js +48 -0
- package/dist/internal/project-link.js.map +1 -0
- package/dist/providers.json +2 -2
- package/dist/server.bundle.mjs +3090 -4321
- package/dist/start-local.bundle.mjs +71481 -0
- package/dist/templates/README.md.tmpl +10 -11
- package/package.json +14 -12
- package/dist/__tests__/chat.integration.test.d.ts +0 -2
- package/dist/__tests__/chat.integration.test.d.ts.map +0 -1
- package/dist/__tests__/chat.integration.test.js +0 -337
- package/dist/__tests__/chat.integration.test.js.map +0 -1
- package/dist/__tests__/dev.test.d.ts +0 -2
- package/dist/__tests__/dev.test.d.ts.map +0 -1
- package/dist/__tests__/dev.test.js +0 -25
- package/dist/__tests__/dev.test.js.map +0 -1
- package/dist/__tests__/init-memory.test.d.ts +0 -2
- package/dist/__tests__/init-memory.test.d.ts.map +0 -1
- package/dist/__tests__/init-memory.test.js +0 -45
- package/dist/__tests__/init-memory.test.js.map +0 -1
- package/dist/__tests__/token.test.d.ts +0 -2
- package/dist/__tests__/token.test.d.ts.map +0 -1
- package/dist/__tests__/token.test.js +0 -52
- package/dist/__tests__/token.test.js.map +0 -1
- package/dist/commands/_lib/apply/__tests__/client.test.d.ts +0 -2
- package/dist/commands/_lib/apply/__tests__/client.test.d.ts.map +0 -1
- package/dist/commands/_lib/apply/__tests__/client.test.js +0 -23
- package/dist/commands/_lib/apply/__tests__/client.test.js.map +0 -1
- package/dist/commands/_lib/apply/__tests__/desired-state.test.d.ts +0 -2
- package/dist/commands/_lib/apply/__tests__/desired-state.test.d.ts.map +0 -1
- package/dist/commands/_lib/apply/__tests__/desired-state.test.js +0 -140
- package/dist/commands/_lib/apply/__tests__/desired-state.test.js.map +0 -1
- package/dist/commands/_lib/apply/__tests__/diff.test.d.ts +0 -2
- package/dist/commands/_lib/apply/__tests__/diff.test.d.ts.map +0 -1
- package/dist/commands/_lib/apply/__tests__/diff.test.js +0 -378
- package/dist/commands/_lib/apply/__tests__/diff.test.js.map +0 -1
- package/dist/commands/apply.d.ts +0 -3
- package/dist/commands/apply.d.ts.map +0 -1
- package/dist/commands/apply.js +0 -5
- package/dist/commands/apply.js.map +0 -1
- package/dist/commands/memory/_lib/openclaw-auth.test.d.ts +0 -2
- package/dist/commands/memory/_lib/openclaw-auth.test.d.ts.map +0 -1
- package/dist/commands/memory/_lib/openclaw-auth.test.js +0 -9
- package/dist/commands/memory/_lib/openclaw-auth.test.js.map +0 -1
- package/dist/internal/__tests__/api-client.test.d.ts +0 -2
- package/dist/internal/__tests__/api-client.test.d.ts.map +0 -1
- package/dist/internal/__tests__/api-client.test.js +0 -95
- package/dist/internal/__tests__/api-client.test.js.map +0 -1
- package/dist/internal/__tests__/context.test.d.ts +0 -2
- package/dist/internal/__tests__/context.test.d.ts.map +0 -1
- package/dist/internal/__tests__/context.test.js +0 -77
- 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,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
|
+
]));
|