@lobu/cli 6.0.0 → 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 +12 -12
- 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 +6 -6
- 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 +4 -4
- package/dist/commands/memory/_lib/browser-auth-cmd.js.map +1 -1
- package/dist/commands/memory/_lib/install-targets.d.ts.map +1 -1
- package/dist/commands/memory/_lib/install-targets.js +1 -5
- package/dist/commands/memory/_lib/install-targets.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 +2 -2
- package/dist/commands/memory/_lib/schema.d.ts.map +1 -1
- package/dist/commands/memory/_lib/schema.js +3 -3
- package/dist/commands/memory/_lib/schema.js.map +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 +7 -6
- package/dist/internal/oauth.d.ts.map +1 -1
- package/dist/internal/oauth.js +3 -3
- 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 +3173 -4404
- 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,91 @@
|
|
|
1
|
+
-- migrate:up
|
|
2
|
+
|
|
3
|
+
-- Normalize watcher feedback to one row per corrected field.
|
|
4
|
+
--
|
|
5
|
+
-- The original `watcher_window_feedback` stored every correction batch as a
|
|
6
|
+
-- single JSONB blob (`corrections`) with one shared `notes` column. That
|
|
7
|
+
-- shape blocks per-field notes, makes "latest correction per field" a JSONB
|
|
8
|
+
-- aggregation, and lets duplicate submissions for the same field accumulate
|
|
9
|
+
-- forever (the prompt summary then injects every historical version).
|
|
10
|
+
--
|
|
11
|
+
-- New table is per-field with explicit mutation kind so structural edits
|
|
12
|
+
-- (remove an array item, append a new one) live alongside value corrections
|
|
13
|
+
-- without overloading the value column. Existing rows are migrated by
|
|
14
|
+
-- expanding the JSONB map into one row per key.
|
|
15
|
+
|
|
16
|
+
CREATE TABLE IF NOT EXISTS public.watcher_window_field_feedback (
|
|
17
|
+
id bigserial PRIMARY KEY,
|
|
18
|
+
window_id integer NOT NULL REFERENCES public.watcher_windows(id) ON DELETE CASCADE,
|
|
19
|
+
watcher_id integer NOT NULL REFERENCES public.watchers(id) ON DELETE CASCADE,
|
|
20
|
+
organization_id text NOT NULL,
|
|
21
|
+
field_path text NOT NULL,
|
|
22
|
+
mutation text NOT NULL DEFAULT 'set'
|
|
23
|
+
CHECK (mutation IN ('set', 'remove', 'add')),
|
|
24
|
+
corrected_value jsonb,
|
|
25
|
+
note text,
|
|
26
|
+
created_by text NOT NULL,
|
|
27
|
+
created_at timestamp with time zone NOT NULL DEFAULT now()
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
CREATE INDEX IF NOT EXISTS idx_wwff_watcher_field_recent
|
|
31
|
+
ON public.watcher_window_field_feedback (watcher_id, field_path, created_at DESC);
|
|
32
|
+
CREATE INDEX IF NOT EXISTS idx_wwff_window
|
|
33
|
+
ON public.watcher_window_field_feedback (window_id);
|
|
34
|
+
|
|
35
|
+
INSERT INTO public.watcher_window_field_feedback (
|
|
36
|
+
window_id, watcher_id, organization_id,
|
|
37
|
+
field_path, mutation, corrected_value, note,
|
|
38
|
+
created_by, created_at
|
|
39
|
+
)
|
|
40
|
+
SELECT
|
|
41
|
+
f.window_id,
|
|
42
|
+
f.watcher_id,
|
|
43
|
+
f.organization_id,
|
|
44
|
+
kv.key AS field_path,
|
|
45
|
+
'set' AS mutation,
|
|
46
|
+
kv.value AS corrected_value,
|
|
47
|
+
f.notes AS note,
|
|
48
|
+
f.created_by,
|
|
49
|
+
f.created_at
|
|
50
|
+
FROM public.watcher_window_feedback f,
|
|
51
|
+
LATERAL jsonb_each(f.corrections) AS kv;
|
|
52
|
+
|
|
53
|
+
DROP TABLE IF EXISTS public.watcher_window_feedback;
|
|
54
|
+
|
|
55
|
+
-- migrate:down
|
|
56
|
+
|
|
57
|
+
CREATE TABLE IF NOT EXISTS public.watcher_window_feedback (
|
|
58
|
+
id bigserial PRIMARY KEY,
|
|
59
|
+
window_id integer NOT NULL REFERENCES public.watcher_windows(id) ON DELETE CASCADE,
|
|
60
|
+
watcher_id integer NOT NULL REFERENCES public.watchers(id) ON DELETE CASCADE,
|
|
61
|
+
organization_id text NOT NULL,
|
|
62
|
+
corrections jsonb NOT NULL,
|
|
63
|
+
notes text,
|
|
64
|
+
created_by text NOT NULL,
|
|
65
|
+
created_at timestamp with time zone NOT NULL DEFAULT now()
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
CREATE INDEX IF NOT EXISTS idx_wwf_window ON public.watcher_window_feedback(window_id);
|
|
69
|
+
CREATE INDEX IF NOT EXISTS idx_wwf_watcher ON public.watcher_window_feedback(watcher_id);
|
|
70
|
+
|
|
71
|
+
-- Best-effort rollback: structural mutations ('add' / 'remove') don't fit
|
|
72
|
+
-- the old shape and are dropped (WHERE mutation = 'set'). When multiple
|
|
73
|
+
-- contributors edited the same window, MAX(note) and MAX(created_by) pick
|
|
74
|
+
-- one arbitrarily — this is an emergency recovery path, not a clean inverse.
|
|
75
|
+
INSERT INTO public.watcher_window_feedback (
|
|
76
|
+
window_id, watcher_id, organization_id,
|
|
77
|
+
corrections, notes, created_by, created_at
|
|
78
|
+
)
|
|
79
|
+
SELECT
|
|
80
|
+
window_id,
|
|
81
|
+
watcher_id,
|
|
82
|
+
organization_id,
|
|
83
|
+
jsonb_object_agg(field_path, COALESCE(corrected_value, 'null'::jsonb)) AS corrections,
|
|
84
|
+
MAX(note) AS notes,
|
|
85
|
+
MAX(created_by) AS created_by,
|
|
86
|
+
MAX(created_at) AS created_at
|
|
87
|
+
FROM public.watcher_window_field_feedback
|
|
88
|
+
WHERE mutation = 'set'
|
|
89
|
+
GROUP BY window_id, watcher_id, organization_id;
|
|
90
|
+
|
|
91
|
+
DROP TABLE IF EXISTS public.watcher_window_field_feedback;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
-- migrate:up
|
|
2
|
+
|
|
3
|
+
-- Add diagnostic columns to runs so subprocess failures carry enough context
|
|
4
|
+
-- to diagnose without reading gateway pod logs.
|
|
5
|
+
--
|
|
6
|
+
-- output_tail: redacted tail (~16 KiB) of child stdout+stderr at exit.
|
|
7
|
+
-- exit_code: subprocess exit code, NULL when terminated by IPC error path.
|
|
8
|
+
-- exit_signal: signal name (e.g. SIGKILL) when killed.
|
|
9
|
+
-- exit_reason: categorized — ok | error_message | timeout | oom | crash.
|
|
10
|
+
ALTER TABLE public.runs ADD COLUMN output_tail TEXT NULL;
|
|
11
|
+
ALTER TABLE public.runs ADD COLUMN exit_code INTEGER NULL;
|
|
12
|
+
ALTER TABLE public.runs ADD COLUMN exit_signal TEXT NULL;
|
|
13
|
+
ALTER TABLE public.runs ADD COLUMN exit_reason TEXT NULL;
|
|
14
|
+
|
|
15
|
+
-- migrate:down
|
|
16
|
+
|
|
17
|
+
ALTER TABLE public.runs DROP COLUMN IF EXISTS exit_reason;
|
|
18
|
+
ALTER TABLE public.runs DROP COLUMN IF EXISTS exit_signal;
|
|
19
|
+
ALTER TABLE public.runs DROP COLUMN IF EXISTS exit_code;
|
|
20
|
+
ALTER TABLE public.runs DROP COLUMN IF EXISTS output_tail;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
-- migrate:up
|
|
2
|
+
|
|
3
|
+
-- Repair-agent plumbing for connector reliability.
|
|
4
|
+
--
|
|
5
|
+
-- When a feed accumulates persistent failures, the worker-completion path
|
|
6
|
+
-- can open a Lobu agent thread for triage. These columns track the
|
|
7
|
+
-- per-feed override, the open thread (if any), the lifetime budget of
|
|
8
|
+
-- repair attempts, the start of the current failure streak, and the
|
|
9
|
+
-- last-posted content hash for append throttling.
|
|
10
|
+
ALTER TABLE public.feeds
|
|
11
|
+
ADD COLUMN repair_agent_id text NULL,
|
|
12
|
+
ADD COLUMN repair_thread_id text NULL,
|
|
13
|
+
ADD COLUMN repair_attempt_count integer NOT NULL DEFAULT 0,
|
|
14
|
+
ADD COLUMN last_repair_at timestamp with time zone NULL,
|
|
15
|
+
ADD COLUMN first_failure_at timestamp with time zone NULL,
|
|
16
|
+
ADD COLUMN last_repair_post_hash text NULL;
|
|
17
|
+
|
|
18
|
+
-- The unique partial index documents intent: a feed has at most one open
|
|
19
|
+
-- repair thread at a time. (`feeds.id` is already PK so the constraint is
|
|
20
|
+
-- redundant for uniqueness; the actual race guard is the conditional
|
|
21
|
+
-- UPDATE on repair_thread_id IS NULL in the worker-completion path.)
|
|
22
|
+
CREATE UNIQUE INDEX feeds_open_repair_thread_uniq
|
|
23
|
+
ON public.feeds (id) WHERE repair_thread_id IS NOT NULL;
|
|
24
|
+
|
|
25
|
+
-- Per-connector default repair agent. When a feed has no explicit
|
|
26
|
+
-- repair_agent_id, the trigger logic falls back to this value.
|
|
27
|
+
ALTER TABLE public.connector_definitions
|
|
28
|
+
ADD COLUMN default_repair_agent_id text NULL;
|
|
29
|
+
|
|
30
|
+
-- Per-org kill switch. When FALSE, no repair threads are opened for any
|
|
31
|
+
-- feed in the org regardless of per-feed configuration.
|
|
32
|
+
ALTER TABLE public.organization
|
|
33
|
+
ADD COLUMN repair_agents_enabled boolean NOT NULL DEFAULT TRUE;
|
|
34
|
+
|
|
35
|
+
-- migrate:down
|
|
36
|
+
|
|
37
|
+
ALTER TABLE public.organization DROP COLUMN IF EXISTS repair_agents_enabled;
|
|
38
|
+
ALTER TABLE public.connector_definitions DROP COLUMN IF EXISTS default_repair_agent_id;
|
|
39
|
+
DROP INDEX IF EXISTS feeds_open_repair_thread_uniq;
|
|
40
|
+
ALTER TABLE public.feeds
|
|
41
|
+
DROP COLUMN IF EXISTS last_repair_post_hash,
|
|
42
|
+
DROP COLUMN IF EXISTS first_failure_at,
|
|
43
|
+
DROP COLUMN IF EXISTS last_repair_at,
|
|
44
|
+
DROP COLUMN IF EXISTS repair_attempt_count,
|
|
45
|
+
DROP COLUMN IF EXISTS repair_thread_id,
|
|
46
|
+
DROP COLUMN IF EXISTS repair_agent_id;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
-- migrate:up
|
|
2
|
+
|
|
3
|
+
-- Convert entities.entity_type from a text slug to an FK on entity_types(id).
|
|
4
|
+
-- Two motivations folded into one change:
|
|
5
|
+
--
|
|
6
|
+
-- 1. Integrity. Today entity_types renames orphan all referencing entities
|
|
7
|
+
-- (slug-based reference is silent FK with no enforcement). Hard-deletes
|
|
8
|
+
-- bypass the validator entirely. With a real FK, Postgres refuses to
|
|
9
|
+
-- drop a referenced type and renames update for free (the slug becomes
|
|
10
|
+
-- display only — JOIN to entity_types for it).
|
|
11
|
+
--
|
|
12
|
+
-- 2. Cross-org vocabulary. entity_types.id is globally unique (one sequence
|
|
13
|
+
-- across all orgs), so an entity in tenant org A can carry a type defined
|
|
14
|
+
-- in public-catalog org B by FK alone. No additional org_id column on
|
|
15
|
+
-- entities is needed once the slug-based same-org coupling is gone.
|
|
16
|
+
--
|
|
17
|
+
-- Single-prod-DB migration: add nullable column, backfill, fail loudly on
|
|
18
|
+
-- orphans, set NOT NULL, drop the text column. Run manually.
|
|
19
|
+
|
|
20
|
+
-- 1. Add the FK column, nullable for backfill.
|
|
21
|
+
ALTER TABLE public.entities
|
|
22
|
+
ADD COLUMN entity_type_id integer REFERENCES public.entity_types(id);
|
|
23
|
+
|
|
24
|
+
-- 2. Backfill from existing (organization_id, entity_type slug) → entity_types.id.
|
|
25
|
+
-- Prefer live entity_types rows; fall back to soft-deleted ones to preserve
|
|
26
|
+
-- history. Without the ORDER BY, a slug+org pair with both an active and a
|
|
27
|
+
-- soft-deleted row would resolve non-deterministically — entity_types' UNIQUE
|
|
28
|
+
-- index on slug only covers `deleted_at IS NULL` rows, so collisions can exist.
|
|
29
|
+
UPDATE public.entities e
|
|
30
|
+
SET entity_type_id = (
|
|
31
|
+
SELECT et.id
|
|
32
|
+
FROM public.entity_types et
|
|
33
|
+
WHERE et.slug = e.entity_type
|
|
34
|
+
AND et.organization_id = e.organization_id
|
|
35
|
+
ORDER BY (et.deleted_at IS NULL) DESC, et.id DESC
|
|
36
|
+
LIMIT 1
|
|
37
|
+
)
|
|
38
|
+
WHERE e.entity_type_id IS NULL;
|
|
39
|
+
|
|
40
|
+
-- 3. Fail loudly on orphans. If any entities reference a slug with no matching
|
|
41
|
+
-- entity_types row, that's pre-existing data corruption from the slug-based
|
|
42
|
+
-- regime. Surface it; don't paper over.
|
|
43
|
+
DO $$
|
|
44
|
+
DECLARE
|
|
45
|
+
orphan_count integer;
|
|
46
|
+
BEGIN
|
|
47
|
+
SELECT COUNT(*) INTO orphan_count FROM public.entities WHERE entity_type_id IS NULL;
|
|
48
|
+
IF orphan_count > 0 THEN
|
|
49
|
+
RAISE EXCEPTION
|
|
50
|
+
'entity_type FK migration: % entities have entity_type slugs with no matching entity_types row. Investigate before re-running.',
|
|
51
|
+
orphan_count;
|
|
52
|
+
END IF;
|
|
53
|
+
END $$;
|
|
54
|
+
|
|
55
|
+
-- 4. Tighten the FK column.
|
|
56
|
+
ALTER TABLE public.entities
|
|
57
|
+
ALTER COLUMN entity_type_id SET NOT NULL;
|
|
58
|
+
|
|
59
|
+
-- 5. Index for filter/list queries that previously used entity_type slug.
|
|
60
|
+
CREATE INDEX idx_entities_entity_type_id
|
|
61
|
+
ON public.entities (entity_type_id)
|
|
62
|
+
WHERE deleted_at IS NULL;
|
|
63
|
+
|
|
64
|
+
-- 6. Drop the redundant UNIQUE constraint that referenced entity_type. The
|
|
65
|
+
-- stronger `entities_slug_parent_unique` (UNIQUE on org_id, COALESCE(parent_id,
|
|
66
|
+
-- 0), slug) already enforces slug uniqueness within (org, parent) regardless
|
|
67
|
+
-- of entity type, with NULL-parent collapsing — so this constraint never
|
|
68
|
+
-- caught anything the index didn't already catch. Drop it explicitly rather
|
|
69
|
+
-- than letting DROP COLUMN cascade silently.
|
|
70
|
+
ALTER TABLE public.entities
|
|
71
|
+
DROP CONSTRAINT IF EXISTS entities_organization_id_entity_type_slug_parent_id_key;
|
|
72
|
+
|
|
73
|
+
-- 7. Drop the column comment so DROP COLUMN doesn't carry a stale doc string
|
|
74
|
+
-- if this migration is ever rolled back and re-applied.
|
|
75
|
+
COMMENT ON COLUMN public.entities.entity_type IS NULL;
|
|
76
|
+
|
|
77
|
+
-- 8. Drop the text column. All readers JOIN to entity_types for the slug.
|
|
78
|
+
ALTER TABLE public.entities DROP COLUMN entity_type;
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
-- migrate:down
|
|
82
|
+
|
|
83
|
+
ALTER TABLE public.entities ADD COLUMN entity_type text;
|
|
84
|
+
|
|
85
|
+
UPDATE public.entities e
|
|
86
|
+
SET entity_type = et.slug
|
|
87
|
+
FROM public.entity_types et
|
|
88
|
+
WHERE et.id = e.entity_type_id;
|
|
89
|
+
|
|
90
|
+
ALTER TABLE public.entities ALTER COLUMN entity_type SET NOT NULL;
|
|
91
|
+
|
|
92
|
+
COMMENT ON COLUMN public.entities.entity_type IS
|
|
93
|
+
'Type of entity: brand, product (future: location, feature, team)';
|
|
94
|
+
|
|
95
|
+
ALTER TABLE public.entities
|
|
96
|
+
ADD CONSTRAINT entities_organization_id_entity_type_slug_parent_id_key
|
|
97
|
+
UNIQUE (organization_id, entity_type, slug, parent_id);
|
|
98
|
+
|
|
99
|
+
DROP INDEX IF EXISTS public.idx_entities_entity_type_id;
|
|
100
|
+
|
|
101
|
+
ALTER TABLE public.entities DROP COLUMN entity_type_id;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
-- migrate:up
|
|
2
|
+
|
|
3
|
+
-- DB integrity cleanup pass (transactional half).
|
|
4
|
+
--
|
|
5
|
+
-- This file groups the changes that are safe to run inside dbmate's default
|
|
6
|
+
-- per-migration transaction: small-table tightenings and a UNIQUE swap that
|
|
7
|
+
-- only takes a brief ACCESS EXCLUSIVE lock. Operations that would hold a
|
|
8
|
+
-- long lock on busy tables (events.created_by NOT NULL, runs CHECK swap,
|
|
9
|
+
-- connections UNIQUE) live in the companion `transaction:false` migration
|
|
10
|
+
-- 20260426120001_db_integrity_cleanup_concurrent.sql.
|
|
11
|
+
--
|
|
12
|
+
-- Each tightening validates its precondition first and aborts loudly via
|
|
13
|
+
-- RAISE EXCEPTION if dirty data is present, so a green run on staging is a
|
|
14
|
+
-- strong signal for production.
|
|
15
|
+
|
|
16
|
+
-- 1. Drop the legacy events archive (renamed during the append-only cutover
|
|
17
|
+
-- on 2026-04-06; no application code references it).
|
|
18
|
+
|
|
19
|
+
DROP TABLE IF EXISTS public.events_legacy_pre_append_only_20260406;
|
|
20
|
+
|
|
21
|
+
-- 2. event_classifiers.status / organization_id -> NOT NULL.
|
|
22
|
+
-- `status` already has DEFAULT 'active' but is nullable; backfill any
|
|
23
|
+
-- pre-default rows then enforce. `organization_id` should never be null.
|
|
24
|
+
|
|
25
|
+
UPDATE public.event_classifiers
|
|
26
|
+
SET status = 'active'
|
|
27
|
+
WHERE status IS NULL;
|
|
28
|
+
|
|
29
|
+
DO $$
|
|
30
|
+
DECLARE
|
|
31
|
+
null_org bigint;
|
|
32
|
+
BEGIN
|
|
33
|
+
SELECT count(*) INTO null_org
|
|
34
|
+
FROM public.event_classifiers
|
|
35
|
+
WHERE organization_id IS NULL;
|
|
36
|
+
IF null_org > 0 THEN
|
|
37
|
+
RAISE EXCEPTION
|
|
38
|
+
'event_classifiers has % row(s) with NULL organization_id; backfill before re-running this migration',
|
|
39
|
+
null_org;
|
|
40
|
+
END IF;
|
|
41
|
+
END$$;
|
|
42
|
+
|
|
43
|
+
ALTER TABLE public.event_classifiers
|
|
44
|
+
ALTER COLUMN status SET NOT NULL,
|
|
45
|
+
ALTER COLUMN organization_id SET NOT NULL;
|
|
46
|
+
|
|
47
|
+
-- 3. watchers.status / organization_id -> NOT NULL.
|
|
48
|
+
-- `status` has DEFAULT 'active'; `organization_id` should never be null.
|
|
49
|
+
|
|
50
|
+
UPDATE public.watchers
|
|
51
|
+
SET status = 'active'
|
|
52
|
+
WHERE status IS NULL;
|
|
53
|
+
|
|
54
|
+
DO $$
|
|
55
|
+
DECLARE
|
|
56
|
+
null_org bigint;
|
|
57
|
+
BEGIN
|
|
58
|
+
SELECT count(*) INTO null_org
|
|
59
|
+
FROM public.watchers
|
|
60
|
+
WHERE organization_id IS NULL;
|
|
61
|
+
IF null_org > 0 THEN
|
|
62
|
+
RAISE EXCEPTION
|
|
63
|
+
'watchers has % row(s) with NULL organization_id; backfill before re-running this migration',
|
|
64
|
+
null_org;
|
|
65
|
+
END IF;
|
|
66
|
+
END$$;
|
|
67
|
+
|
|
68
|
+
ALTER TABLE public.watchers
|
|
69
|
+
ALTER COLUMN status SET NOT NULL,
|
|
70
|
+
ALTER COLUMN organization_id SET NOT NULL;
|
|
71
|
+
|
|
72
|
+
-- 4. event_classifiers UNIQUE -> NULLS NOT DISTINCT.
|
|
73
|
+
-- The previous (entity_id, watcher_id, slug) UNIQUE silently allowed
|
|
74
|
+
-- duplicates whenever entity_id or watcher_id were NULL because Postgres
|
|
75
|
+
-- treats NULLs as distinct. PG15+ supports NULLS NOT DISTINCT for the
|
|
76
|
+
-- intended "one row per scope+slug" semantic.
|
|
77
|
+
-- The table is small; the brief ACCESS EXCLUSIVE inside this transaction
|
|
78
|
+
-- is acceptable.
|
|
79
|
+
|
|
80
|
+
ALTER TABLE public.event_classifiers
|
|
81
|
+
DROP CONSTRAINT event_classifiers_unique_per_insight;
|
|
82
|
+
|
|
83
|
+
ALTER TABLE public.event_classifiers
|
|
84
|
+
ADD CONSTRAINT event_classifiers_unique_per_insight
|
|
85
|
+
UNIQUE NULLS NOT DISTINCT (entity_id, watcher_id, slug);
|
|
86
|
+
|
|
87
|
+
-- migrate:down
|
|
88
|
+
|
|
89
|
+
ALTER TABLE public.event_classifiers
|
|
90
|
+
DROP CONSTRAINT event_classifiers_unique_per_insight;
|
|
91
|
+
|
|
92
|
+
ALTER TABLE public.event_classifiers
|
|
93
|
+
ADD CONSTRAINT event_classifiers_unique_per_insight
|
|
94
|
+
UNIQUE (entity_id, watcher_id, slug);
|
|
95
|
+
|
|
96
|
+
ALTER TABLE public.watchers
|
|
97
|
+
ALTER COLUMN organization_id DROP NOT NULL,
|
|
98
|
+
ALTER COLUMN status DROP NOT NULL;
|
|
99
|
+
|
|
100
|
+
ALTER TABLE public.event_classifiers
|
|
101
|
+
ALTER COLUMN organization_id DROP NOT NULL,
|
|
102
|
+
ALTER COLUMN status DROP NOT NULL;
|
|
103
|
+
|
|
104
|
+
-- The legacy events table is intentionally not recreated on rollback.
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
-- migrate:up transaction:false
|
|
2
|
+
|
|
3
|
+
-- DB integrity cleanup pass (non-transactional half).
|
|
4
|
+
--
|
|
5
|
+
-- Statements here would hold ACCESS EXCLUSIVE on busy tables for the
|
|
6
|
+
-- duration of a table scan or index build if wrapped in dbmate's default
|
|
7
|
+
-- per-migration transaction. Running with `transaction:false` lets each
|
|
8
|
+
-- statement release its lock on completion so VALIDATE CONSTRAINT only
|
|
9
|
+
-- needs SHARE UPDATE EXCLUSIVE (compatible with concurrent reads/writes)
|
|
10
|
+
-- and CREATE INDEX CONCURRENTLY can do its non-blocking build.
|
|
11
|
+
--
|
|
12
|
+
-- All steps are written to be idempotent: re-running after a partial
|
|
13
|
+
-- failure converges on the desired state. Validation aborts (RAISE
|
|
14
|
+
-- EXCEPTION) leave the migration NOT applied so dbmate retries cleanly.
|
|
15
|
+
--
|
|
16
|
+
-- Set a short lock_timeout so any contention surfaces as a fast failure
|
|
17
|
+
-- instead of a stuck deploy.
|
|
18
|
+
SET lock_timeout = '5s';
|
|
19
|
+
|
|
20
|
+
-- 1. events.created_by -> NOT NULL.
|
|
21
|
+
-- ADD CHECK NOT VALID adds the catalog row under a brief ACCESS
|
|
22
|
+
-- EXCLUSIVE without scanning the table. VALIDATE then takes only
|
|
23
|
+
-- SHARE UPDATE EXCLUSIVE so concurrent reads/writes are unaffected.
|
|
24
|
+
-- Once validated, SET NOT NULL is metadata-only because Postgres uses
|
|
25
|
+
-- the validated CHECK as proof there are no NULLs (PG12+).
|
|
26
|
+
|
|
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_created_by_not_null'
|
|
34
|
+
) THEN
|
|
35
|
+
ALTER TABLE public.events
|
|
36
|
+
ADD CONSTRAINT events_created_by_not_null
|
|
37
|
+
CHECK (created_by IS NOT NULL) NOT VALID;
|
|
38
|
+
END IF;
|
|
39
|
+
END$$;
|
|
40
|
+
|
|
41
|
+
-- Backfill historical NULLs with the existing 'system' sentinel before
|
|
42
|
+
-- VALIDATE. Pre-`created_by` events end up here.
|
|
43
|
+
--
|
|
44
|
+
-- The session's lock_timeout is 5s (set at the top of this migration)
|
|
45
|
+
-- which is appropriate for DDL but too aggressive for a row-level
|
|
46
|
+
-- backfill running concurrently with live inserts: the deploying
|
|
47
|
+
-- gateway and the live old pod can hold per-row locks briefly, and
|
|
48
|
+
-- our UPDATE waits on each one. Bump locally for the UPDATE and the
|
|
49
|
+
-- VALIDATE that follows, then restore.
|
|
50
|
+
SET lock_timeout = 0;
|
|
51
|
+
UPDATE public.events SET created_by = 'system' WHERE created_by IS NULL;
|
|
52
|
+
|
|
53
|
+
ALTER TABLE public.events
|
|
54
|
+
VALIDATE CONSTRAINT events_created_by_not_null;
|
|
55
|
+
|
|
56
|
+
ALTER TABLE public.events
|
|
57
|
+
ALTER COLUMN created_by SET NOT NULL;
|
|
58
|
+
|
|
59
|
+
ALTER TABLE public.events
|
|
60
|
+
DROP CONSTRAINT IF EXISTS events_created_by_not_null;
|
|
61
|
+
|
|
62
|
+
SET lock_timeout = '5s';
|
|
63
|
+
|
|
64
|
+
-- 2. Prune historical run_type values from the runs CHECK.
|
|
65
|
+
-- 'embed_backfill' is still in active use and stays. 'insight' and
|
|
66
|
+
-- 'code' are remnants of earlier orchestration models with no
|
|
67
|
+
-- production references; abort if any rows still carry them.
|
|
68
|
+
-- Use the NOT VALID + VALIDATE pattern so the swap doesn't hold
|
|
69
|
+
-- ACCESS EXCLUSIVE on runs across the table scan.
|
|
70
|
+
|
|
71
|
+
DO $$
|
|
72
|
+
DECLARE
|
|
73
|
+
historical bigint;
|
|
74
|
+
BEGIN
|
|
75
|
+
SELECT count(*) INTO historical
|
|
76
|
+
FROM public.runs
|
|
77
|
+
WHERE run_type IN ('insight', 'code');
|
|
78
|
+
IF historical > 0 THEN
|
|
79
|
+
RAISE EXCEPTION
|
|
80
|
+
'runs has % row(s) with deprecated run_type (insight/code); migrate them before re-running',
|
|
81
|
+
historical;
|
|
82
|
+
END IF;
|
|
83
|
+
END$$;
|
|
84
|
+
|
|
85
|
+
DO $$
|
|
86
|
+
BEGIN
|
|
87
|
+
IF NOT EXISTS (
|
|
88
|
+
SELECT 1
|
|
89
|
+
FROM pg_constraint
|
|
90
|
+
WHERE conrelid = 'public.runs'::regclass
|
|
91
|
+
AND conname = 'runs_run_type_check_v2'
|
|
92
|
+
) THEN
|
|
93
|
+
ALTER TABLE public.runs
|
|
94
|
+
ADD CONSTRAINT runs_run_type_check_v2
|
|
95
|
+
CHECK (run_type = ANY (ARRAY[
|
|
96
|
+
'sync'::text,
|
|
97
|
+
'action'::text,
|
|
98
|
+
'embed_backfill'::text,
|
|
99
|
+
'watcher'::text,
|
|
100
|
+
'auth'::text
|
|
101
|
+
])) NOT VALID;
|
|
102
|
+
END IF;
|
|
103
|
+
END$$;
|
|
104
|
+
|
|
105
|
+
ALTER TABLE public.runs
|
|
106
|
+
VALIDATE CONSTRAINT runs_run_type_check_v2;
|
|
107
|
+
|
|
108
|
+
ALTER TABLE public.runs
|
|
109
|
+
DROP CONSTRAINT IF EXISTS runs_run_type_check;
|
|
110
|
+
|
|
111
|
+
ALTER TABLE public.runs
|
|
112
|
+
RENAME CONSTRAINT runs_run_type_check_v2 TO runs_run_type_check;
|
|
113
|
+
|
|
114
|
+
-- 3. Connections natural-key UNIQUE among live, authenticated rows.
|
|
115
|
+
-- A reconnect should refresh credentials in place, not insert a new
|
|
116
|
+
-- row. Partial index limits scope to non-deleted rows with an
|
|
117
|
+
-- account_id so soft-deletes and unauthenticated stubs don't block
|
|
118
|
+
-- re-creation. CONCURRENTLY avoids blocking writes during the build.
|
|
119
|
+
|
|
120
|
+
DO $$
|
|
121
|
+
DECLARE
|
|
122
|
+
dup_groups bigint;
|
|
123
|
+
BEGIN
|
|
124
|
+
SELECT count(*) INTO dup_groups
|
|
125
|
+
FROM (
|
|
126
|
+
SELECT 1
|
|
127
|
+
FROM public.connections
|
|
128
|
+
WHERE deleted_at IS NULL
|
|
129
|
+
AND account_id IS NOT NULL
|
|
130
|
+
GROUP BY organization_id, connector_key, account_id
|
|
131
|
+
HAVING count(*) > 1
|
|
132
|
+
) t;
|
|
133
|
+
IF dup_groups > 0 THEN
|
|
134
|
+
RAISE EXCEPTION
|
|
135
|
+
'connections has % duplicate (organization_id, connector_key, account_id) group(s) among live rows; dedupe before re-running',
|
|
136
|
+
dup_groups;
|
|
137
|
+
END IF;
|
|
138
|
+
END$$;
|
|
139
|
+
|
|
140
|
+
-- A previous failed run can leave an INVALID index behind. `IF NOT EXISTS`
|
|
141
|
+
-- alone would skip creation in that case, leaving uniqueness unenforced.
|
|
142
|
+
-- Drop first (no-op when the index doesn't exist) so the create always
|
|
143
|
+
-- produces a valid index.
|
|
144
|
+
--
|
|
145
|
+
-- Originally CONCURRENTLY (which is why this migration is in the
|
|
146
|
+
-- transaction:false half) but dbmate's transaction handling against
|
|
147
|
+
-- the pq driver still presents these as in-transaction to Postgres,
|
|
148
|
+
-- failing with "DROP INDEX CONCURRENTLY cannot run inside a
|
|
149
|
+
-- transaction block". The connections table only has a handful of
|
|
150
|
+
-- rows matching the partial-index predicate (≈2 in prod), so the
|
|
151
|
+
-- ACCESS EXCLUSIVE held during a non-concurrent build is sub-second.
|
|
152
|
+
DROP INDEX IF EXISTS public.idx_connections_org_connector_account_live;
|
|
153
|
+
|
|
154
|
+
CREATE UNIQUE INDEX idx_connections_org_connector_account_live
|
|
155
|
+
ON public.connections (organization_id, connector_key, account_id)
|
|
156
|
+
WHERE deleted_at IS NULL AND account_id IS NOT NULL;
|
|
157
|
+
|
|
158
|
+
-- migrate:down transaction:false
|
|
159
|
+
|
|
160
|
+
DROP INDEX CONCURRENTLY IF EXISTS public.idx_connections_org_connector_account_live;
|
|
161
|
+
|
|
162
|
+
DO $$
|
|
163
|
+
BEGIN
|
|
164
|
+
IF EXISTS (
|
|
165
|
+
SELECT 1
|
|
166
|
+
FROM pg_constraint
|
|
167
|
+
WHERE conrelid = 'public.runs'::regclass
|
|
168
|
+
AND conname = 'runs_run_type_check'
|
|
169
|
+
) THEN
|
|
170
|
+
ALTER TABLE public.runs DROP CONSTRAINT runs_run_type_check;
|
|
171
|
+
END IF;
|
|
172
|
+
END$$;
|
|
173
|
+
|
|
174
|
+
ALTER TABLE public.runs
|
|
175
|
+
ADD CONSTRAINT runs_run_type_check
|
|
176
|
+
CHECK (run_type = ANY (ARRAY[
|
|
177
|
+
'sync'::text,
|
|
178
|
+
'action'::text,
|
|
179
|
+
'code'::text,
|
|
180
|
+
'insight'::text,
|
|
181
|
+
'embed_backfill'::text,
|
|
182
|
+
'watcher'::text,
|
|
183
|
+
'auth'::text
|
|
184
|
+
]));
|
|
185
|
+
|
|
186
|
+
ALTER TABLE public.events
|
|
187
|
+
ALTER COLUMN created_by DROP NOT NULL;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
-- migrate:up transaction:false
|
|
2
|
+
|
|
3
|
+
-- `events.created_by` represents the Lobu user that explicitly saved or
|
|
4
|
+
-- authored an event. Connector/scheduled/system-produced events have no human
|
|
5
|
+
-- saver; their provenance is represented by connector_key, connection_id,
|
|
6
|
+
-- feed_id, run_id, and client_id. Keep user attribution nullable and enforce
|
|
7
|
+
-- that any non-null value is a real user id instead of a sentinel string.
|
|
8
|
+
SET lock_timeout = '5s';
|
|
9
|
+
|
|
10
|
+
ALTER TABLE public.events
|
|
11
|
+
ALTER COLUMN created_by DROP NOT NULL;
|
|
12
|
+
|
|
13
|
+
-- During a rolling deploy, old application pods may still write the historical
|
|
14
|
+
-- 'system'/'api' sentinel actors. Normalize only those known sentinels to NULL
|
|
15
|
+
-- before the FK sees them. Unknown non-user values should still be rejected.
|
|
16
|
+
CREATE OR REPLACE FUNCTION public.normalize_event_created_by()
|
|
17
|
+
RETURNS trigger
|
|
18
|
+
LANGUAGE plpgsql
|
|
19
|
+
AS $$
|
|
20
|
+
BEGIN
|
|
21
|
+
IF NEW.created_by IN ('system', 'api') AND NOT EXISTS (
|
|
22
|
+
SELECT 1 FROM public."user" u WHERE u.id = NEW.created_by
|
|
23
|
+
) THEN
|
|
24
|
+
NEW.created_by := NULL;
|
|
25
|
+
END IF;
|
|
26
|
+
RETURN NEW;
|
|
27
|
+
END;
|
|
28
|
+
$$;
|
|
29
|
+
|
|
30
|
+
DROP TRIGGER IF EXISTS normalize_event_created_by ON public.events;
|
|
31
|
+
CREATE TRIGGER normalize_event_created_by
|
|
32
|
+
BEFORE INSERT OR UPDATE OF created_by ON public.events
|
|
33
|
+
FOR EACH ROW
|
|
34
|
+
EXECUTE FUNCTION public.normalize_event_created_by();
|
|
35
|
+
|
|
36
|
+
-- The previous integrity migration backfilled NULL event actors to 'system'.
|
|
37
|
+
-- Convert those sentinel values back to NULL. This uses idx_events_created_by.
|
|
38
|
+
SET lock_timeout = 0;
|
|
39
|
+
UPDATE public.events e
|
|
40
|
+
SET created_by = NULL
|
|
41
|
+
WHERE e.created_by IN ('system', 'api')
|
|
42
|
+
AND NOT EXISTS (
|
|
43
|
+
SELECT 1 FROM public."user" u WHERE u.id = e.created_by
|
|
44
|
+
);
|
|
45
|
+
SET lock_timeout = '5s';
|
|
46
|
+
|
|
47
|
+
ALTER TABLE public.events
|
|
48
|
+
ADD CONSTRAINT events_created_by_fkey
|
|
49
|
+
FOREIGN KEY (created_by)
|
|
50
|
+
REFERENCES public."user"(id)
|
|
51
|
+
ON DELETE SET NULL
|
|
52
|
+
NOT VALID;
|
|
53
|
+
|
|
54
|
+
SET lock_timeout = 0;
|
|
55
|
+
ALTER TABLE public.events
|
|
56
|
+
VALIDATE CONSTRAINT events_created_by_fkey;
|
|
57
|
+
SET lock_timeout = '5s';
|
|
58
|
+
|
|
59
|
+
-- migrate:down transaction:false
|
|
60
|
+
|
|
61
|
+
ALTER TABLE public.events
|
|
62
|
+
DROP CONSTRAINT IF EXISTS events_created_by_fkey;
|
|
63
|
+
|
|
64
|
+
DROP TRIGGER IF EXISTS normalize_event_created_by ON public.events;
|
|
65
|
+
DROP FUNCTION IF EXISTS public.normalize_event_created_by();
|
|
66
|
+
|
|
67
|
+
SET lock_timeout = 0;
|
|
68
|
+
UPDATE public.events
|
|
69
|
+
SET created_by = 'system'
|
|
70
|
+
WHERE created_by IS NULL;
|
|
71
|
+
SET lock_timeout = '5s';
|
|
72
|
+
|
|
73
|
+
ALTER TABLE public.events
|
|
74
|
+
ALTER COLUMN created_by SET NOT NULL;
|