@lobu/cli 7.0.0 → 7.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/_lib/apply/apply-cmd.d.ts.map +1 -1
- package/dist/commands/_lib/apply/apply-cmd.js +160 -12
- package/dist/commands/_lib/apply/apply-cmd.js.map +1 -1
- package/dist/commands/_lib/apply/client.d.ts +106 -0
- package/dist/commands/_lib/apply/client.d.ts.map +1 -1
- package/dist/commands/_lib/apply/client.js +163 -2
- package/dist/commands/_lib/apply/client.js.map +1 -1
- package/dist/commands/_lib/apply/desired-state.d.ts +53 -0
- package/dist/commands/_lib/apply/desired-state.d.ts.map +1 -1
- package/dist/commands/_lib/apply/desired-state.js +182 -5
- package/dist/commands/_lib/apply/desired-state.js.map +1 -1
- package/dist/commands/_lib/apply/diff.d.ts +12 -1
- package/dist/commands/_lib/apply/diff.d.ts.map +1 -1
- package/dist/commands/_lib/apply/diff.js +106 -7
- package/dist/commands/_lib/apply/diff.js.map +1 -1
- package/dist/commands/_lib/connector-loader.d.ts +3 -0
- package/dist/commands/_lib/connector-loader.d.ts.map +1 -0
- package/dist/commands/_lib/connector-loader.js +129 -0
- package/dist/commands/_lib/connector-loader.js.map +1 -0
- package/dist/commands/_lib/connector-run-cmd.d.ts +35 -0
- package/dist/commands/_lib/connector-run-cmd.d.ts.map +1 -0
- package/dist/commands/_lib/connector-run-cmd.js +351 -0
- package/dist/commands/_lib/connector-run-cmd.js.map +1 -0
- package/dist/commands/_lib/export/export-cmd.d.ts +35 -0
- package/dist/commands/_lib/export/export-cmd.d.ts.map +1 -0
- package/dist/commands/_lib/export/export-cmd.js +329 -0
- package/dist/commands/_lib/export/export-cmd.js.map +1 -0
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +11 -14
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +19 -5
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/connector.d.ts +3 -0
- package/dist/commands/connector.d.ts.map +1 -0
- package/dist/commands/connector.js +5 -0
- package/dist/commands/connector.js.map +1 -0
- package/dist/commands/context.d.ts +7 -0
- package/dist/commands/context.d.ts.map +1 -1
- package/dist/commands/context.js +19 -2
- package/dist/commands/context.js.map +1 -1
- package/dist/commands/dev.d.ts +15 -0
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +156 -4
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +2 -3
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/eval.d.ts.map +1 -1
- package/dist/commands/eval.js +12 -13
- package/dist/commands/eval.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +5 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +22 -16
- 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 +15 -144
- package/dist/commands/memory/_lib/browser-auth-cmd.js.map +1 -1
- package/dist/commands/token.d.ts.map +1 -1
- package/dist/commands/token.js +1 -4
- package/dist/commands/token.js.map +1 -1
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +4 -13
- package/dist/commands/validate.js.map +1 -1
- package/dist/config/loader.js +2 -2
- package/dist/config/loader.js.map +1 -1
- package/dist/connectors/README.md +0 -1
- package/dist/connectors/apple_photos.ts +178 -0
- package/dist/connectors/browser-scraper-utils.ts +76 -0
- package/dist/connectors/chrome.ts +351 -0
- package/dist/connectors/chrome_bookmarks.ts +79 -0
- package/dist/connectors/chrome_downloads.ts +80 -0
- package/dist/connectors/chrome_history.ts +80 -0
- package/dist/connectors/github.ts +1 -0
- package/dist/connectors/google_calendar.ts +14 -2
- package/dist/connectors/google_play.ts +22 -2
- package/dist/connectors/hackernews.ts +37 -2
- package/dist/connectors/index.ts +15 -1
- package/dist/connectors/reddit.ts +1 -0
- package/dist/connectors/revolut.ts +10 -13
- package/dist/connectors/rss.ts +33 -8
- package/dist/connectors/trustpilot.ts +31 -20
- package/dist/connectors/website.ts +7 -68
- package/dist/connectors/whatsapp.ts +12 -21
- package/dist/db/migrations/20260514130000_connection_action_modes.sql +103 -0
- package/dist/db/migrations/20260514160000_auth_profiles_mirror_mode.sql +32 -0
- package/dist/db/migrations/20260515120000_agents_per_org_pk.sql +66 -0
- package/dist/db/migrations/20260515150000_geo_enrichment.sql +208 -0
- package/dist/db/migrations/20260515160000_drop_agents_org_id_unique.sql +24 -0
- package/dist/db/migrations/20260515170000_auth_profiles_default_for_connector.sql +23 -0
- package/dist/db/migrations/20260516120000_agents_per_org_pk_swap.sql +125 -0
- package/dist/db/migrations/20260516200000_events_search_tsv.sql +134 -0
- package/dist/db/migrations/20260516200100_events_lifecycle_changes_index.sql +25 -0
- package/dist/db/migrations/20260517010000_drop_unused_indexes.sql +49 -0
- package/dist/db/migrations/20260517020000_softdelete_orphan_feeds.sql +56 -0
- package/dist/db/migrations/20260517030000_pat_worker_id_binding.sql +27 -0
- package/dist/db/migrations/20260517040000_archive_orphan_watchers.sql +30 -0
- package/dist/db/migrations/20260517050000_watcher_agent_id_not_null.sql +34 -0
- package/dist/db/migrations/20260517060000_watcher_schema_additions.sql +78 -0
- package/dist/db/migrations/20260517150000_goals_primitive.sql +55 -0
- package/dist/db/migrations/20260517160000_drop_goals_primitive.sql +45 -0
- package/dist/db/migrations/20260518000000_pending_interactions.sql +49 -0
- package/dist/db/migrations/20260518010000_runs_heartbeat_reaper_index.sql +22 -0
- package/dist/db/migrations/20260518020000_runs_heartbeat_inflight_narrow.sql +36 -0
- package/dist/db/migrations/20260518040000_agent_transcript_snapshot.sql +54 -0
- package/dist/db/migrations/20260518050000_runs_denormalize_agent_conversation.sql +36 -0
- package/dist/db/migrations/20260518060000_revert_runs_denormalize.sql +29 -0
- package/dist/db/migrations/20260518070000_runs_heartbeat_inflight_widen.sql +33 -0
- package/dist/eval/client.d.ts.map +1 -1
- package/dist/eval/client.js +11 -0
- package/dist/eval/client.js.map +1 -1
- package/dist/eval/grader.js +2 -1
- package/dist/eval/grader.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +84 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/context.d.ts +13 -1
- package/dist/internal/context.d.ts.map +1 -1
- package/dist/internal/context.js +83 -8
- package/dist/internal/context.js.map +1 -1
- package/dist/internal/credentials.d.ts +5 -0
- package/dist/internal/credentials.d.ts.map +1 -1
- package/dist/internal/credentials.js +75 -1
- package/dist/internal/credentials.js.map +1 -1
- package/dist/internal/index.d.ts +2 -2
- 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/local-env.d.ts.map +1 -1
- package/dist/internal/local-env.js +9 -2
- package/dist/internal/local-env.js.map +1 -1
- package/dist/server.bundle.mjs +7085 -2832
- package/dist/start-local.bundle.mjs +8269 -3656
- package/package.json +7 -5
- package/dist/connectors/google_photos.ts +0 -776
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
-- migrate:up
|
|
2
|
+
-- Phase C: swap `agents` PK from globally-unique `id` to per-org composite
|
|
3
|
+
-- `(organization_id, id)`. The application has always treated agents as
|
|
4
|
+
-- org-scoped (every read/list filters by organization_id) but the global PK
|
|
5
|
+
-- silently blocked two orgs from sharing an agent ID — for example, a stale
|
|
6
|
+
-- `food-ordering` in one org would prevent `food-ordering` in another.
|
|
7
|
+
--
|
|
8
|
+
-- Phase A (20260515120000) added the org column + composite indexes on the
|
|
9
|
+
-- 5 FK-holding child tables and backfilled values from agents. Phase B (the
|
|
10
|
+
-- application-code refactor in this PR) plumbs `organization_id` through every
|
|
11
|
+
-- INSERT/UPDATE/DELETE/SELECT touching these tables. This migration is the
|
|
12
|
+
-- final structural swap: it drops the single-column PK + FKs, adds the
|
|
13
|
+
-- composite PK + FKs, and widens the per-(agent,kind,pattern) uniques on
|
|
14
|
+
-- agent_users / agent_grants / grants with organization_id.
|
|
15
|
+
|
|
16
|
+
-- ── 0. Set NOT NULL on the columns Phase A backfilled.
|
|
17
|
+
-- Backfill defensively in case any rows snuck in NULL (e.g. embedded PGlite
|
|
18
|
+
-- installs that bypassed the dbmate runner during a partial state).
|
|
19
|
+
UPDATE public.agent_grants c SET organization_id = a.organization_id FROM public.agents a WHERE c.organization_id IS NULL AND c.agent_id = a.id;
|
|
20
|
+
UPDATE public.agent_connections c SET organization_id = a.organization_id FROM public.agents a WHERE c.organization_id IS NULL AND c.agent_id = a.id;
|
|
21
|
+
UPDATE public.agent_users c SET organization_id = a.organization_id FROM public.agents a WHERE c.organization_id IS NULL AND c.agent_id = a.id;
|
|
22
|
+
UPDATE public.agent_channel_bindings c SET organization_id = a.organization_id FROM public.agents a WHERE c.organization_id IS NULL AND c.agent_id = a.id;
|
|
23
|
+
UPDATE public.grants c SET organization_id = a.organization_id FROM public.agents a WHERE c.organization_id IS NULL AND c.agent_id = a.id;
|
|
24
|
+
|
|
25
|
+
-- Drop any orphan rows (agent_id with no matching agents row). Backfill
|
|
26
|
+
-- can't recover these.
|
|
27
|
+
DELETE FROM public.agent_grants WHERE organization_id IS NULL;
|
|
28
|
+
DELETE FROM public.agent_connections WHERE organization_id IS NULL;
|
|
29
|
+
DELETE FROM public.agent_users WHERE organization_id IS NULL;
|
|
30
|
+
DELETE FROM public.agent_channel_bindings WHERE organization_id IS NULL;
|
|
31
|
+
DELETE FROM public.grants WHERE organization_id IS NULL;
|
|
32
|
+
|
|
33
|
+
ALTER TABLE public.agent_grants ALTER COLUMN organization_id SET NOT NULL;
|
|
34
|
+
ALTER TABLE public.agent_connections ALTER COLUMN organization_id SET NOT NULL;
|
|
35
|
+
ALTER TABLE public.agent_users ALTER COLUMN organization_id SET NOT NULL;
|
|
36
|
+
ALTER TABLE public.agent_channel_bindings ALTER COLUMN organization_id SET NOT NULL;
|
|
37
|
+
ALTER TABLE public.grants ALTER COLUMN organization_id SET NOT NULL;
|
|
38
|
+
|
|
39
|
+
-- ── 1. Drop the 6 single-column FKs into agents(id).
|
|
40
|
+
ALTER TABLE public.agent_grants DROP CONSTRAINT IF EXISTS agent_grants_agent_id_fkey;
|
|
41
|
+
ALTER TABLE public.agent_connections DROP CONSTRAINT IF EXISTS agent_connections_agent_id_fkey;
|
|
42
|
+
ALTER TABLE public.agent_users DROP CONSTRAINT IF EXISTS agent_users_agent_id_fkey;
|
|
43
|
+
ALTER TABLE public.agent_channel_bindings DROP CONSTRAINT IF EXISTS agent_channel_bindings_agent_id_fkey;
|
|
44
|
+
ALTER TABLE public.grants DROP CONSTRAINT IF EXISTS grants_agent_id_fkey;
|
|
45
|
+
ALTER TABLE public.scheduled_jobs DROP CONSTRAINT IF EXISTS scheduled_jobs_agent_fkey;
|
|
46
|
+
|
|
47
|
+
-- ── 2. Drop the unique/PK constraints on child tables that scope to bare agent_id.
|
|
48
|
+
ALTER TABLE public.agent_grants DROP CONSTRAINT IF EXISTS agent_grants_agent_id_pattern_key;
|
|
49
|
+
ALTER TABLE public.agent_users DROP CONSTRAINT IF EXISTS agent_users_pkey;
|
|
50
|
+
ALTER TABLE public.grants DROP CONSTRAINT IF EXISTS grants_pkey;
|
|
51
|
+
|
|
52
|
+
-- ── 3. Swap the PK on agents from (id) to (organization_id, id).
|
|
53
|
+
ALTER TABLE public.agents DROP CONSTRAINT IF EXISTS agents_pkey;
|
|
54
|
+
ALTER TABLE public.agents ADD CONSTRAINT agents_pkey PRIMARY KEY (organization_id, id);
|
|
55
|
+
|
|
56
|
+
-- ── 4. Re-add per-org-scoped uniques on the child tables.
|
|
57
|
+
ALTER TABLE public.agent_grants
|
|
58
|
+
ADD CONSTRAINT agent_grants_org_agent_pattern_key UNIQUE (organization_id, agent_id, pattern);
|
|
59
|
+
ALTER TABLE public.agent_users
|
|
60
|
+
ADD CONSTRAINT agent_users_pkey PRIMARY KEY (organization_id, agent_id, platform, user_id);
|
|
61
|
+
ALTER TABLE public.grants
|
|
62
|
+
ADD CONSTRAINT grants_pkey PRIMARY KEY (organization_id, agent_id, kind, pattern);
|
|
63
|
+
|
|
64
|
+
-- ── 5. Re-add composite FKs (organization_id, agent_id) → agents(organization_id, id).
|
|
65
|
+
ALTER TABLE public.agent_grants
|
|
66
|
+
ADD CONSTRAINT agent_grants_org_agent_fkey
|
|
67
|
+
FOREIGN KEY (organization_id, agent_id) REFERENCES public.agents(organization_id, id) ON DELETE CASCADE;
|
|
68
|
+
ALTER TABLE public.agent_connections
|
|
69
|
+
ADD CONSTRAINT agent_connections_org_agent_fkey
|
|
70
|
+
FOREIGN KEY (organization_id, agent_id) REFERENCES public.agents(organization_id, id) ON DELETE CASCADE;
|
|
71
|
+
ALTER TABLE public.agent_users
|
|
72
|
+
ADD CONSTRAINT agent_users_org_agent_fkey
|
|
73
|
+
FOREIGN KEY (organization_id, agent_id) REFERENCES public.agents(organization_id, id) ON DELETE CASCADE;
|
|
74
|
+
ALTER TABLE public.agent_channel_bindings
|
|
75
|
+
ADD CONSTRAINT agent_channel_bindings_org_agent_fkey
|
|
76
|
+
FOREIGN KEY (organization_id, agent_id) REFERENCES public.agents(organization_id, id) ON DELETE CASCADE;
|
|
77
|
+
ALTER TABLE public.grants
|
|
78
|
+
ADD CONSTRAINT grants_org_agent_fkey
|
|
79
|
+
FOREIGN KEY (organization_id, agent_id) REFERENCES public.agents(organization_id, id) ON DELETE CASCADE;
|
|
80
|
+
ALTER TABLE public.scheduled_jobs
|
|
81
|
+
ADD CONSTRAINT scheduled_jobs_org_agent_fkey
|
|
82
|
+
FOREIGN KEY (organization_id, created_by_agent) REFERENCES public.agents(organization_id, id) ON DELETE CASCADE;
|
|
83
|
+
|
|
84
|
+
-- migrate:down
|
|
85
|
+
-- Reverse the swap. NOTE: this WILL FAIL if two orgs ended up sharing an
|
|
86
|
+
-- agent ID after this migration shipped (the previous PK on (id) requires
|
|
87
|
+
-- global uniqueness). That's by design — this migration's whole purpose is
|
|
88
|
+
-- to allow per-org agent IDs that the old PK forbids.
|
|
89
|
+
|
|
90
|
+
ALTER TABLE public.scheduled_jobs DROP CONSTRAINT IF EXISTS scheduled_jobs_org_agent_fkey;
|
|
91
|
+
ALTER TABLE public.grants DROP CONSTRAINT IF EXISTS grants_org_agent_fkey;
|
|
92
|
+
ALTER TABLE public.agent_channel_bindings DROP CONSTRAINT IF EXISTS agent_channel_bindings_org_agent_fkey;
|
|
93
|
+
ALTER TABLE public.agent_users DROP CONSTRAINT IF EXISTS agent_users_org_agent_fkey;
|
|
94
|
+
ALTER TABLE public.agent_connections DROP CONSTRAINT IF EXISTS agent_connections_org_agent_fkey;
|
|
95
|
+
ALTER TABLE public.agent_grants DROP CONSTRAINT IF EXISTS agent_grants_org_agent_fkey;
|
|
96
|
+
|
|
97
|
+
ALTER TABLE public.grants DROP CONSTRAINT IF EXISTS grants_pkey;
|
|
98
|
+
ALTER TABLE public.agent_users DROP CONSTRAINT IF EXISTS agent_users_pkey;
|
|
99
|
+
ALTER TABLE public.agent_grants DROP CONSTRAINT IF EXISTS agent_grants_org_agent_pattern_key;
|
|
100
|
+
|
|
101
|
+
ALTER TABLE public.agents DROP CONSTRAINT IF EXISTS agents_pkey;
|
|
102
|
+
ALTER TABLE public.agents ADD CONSTRAINT agents_pkey PRIMARY KEY (id);
|
|
103
|
+
|
|
104
|
+
ALTER TABLE public.agent_grants ADD CONSTRAINT agent_grants_agent_id_pattern_key UNIQUE (agent_id, pattern);
|
|
105
|
+
ALTER TABLE public.agent_users ADD CONSTRAINT agent_users_pkey PRIMARY KEY (agent_id, platform, user_id);
|
|
106
|
+
ALTER TABLE public.grants ADD CONSTRAINT grants_pkey PRIMARY KEY (agent_id, kind, pattern);
|
|
107
|
+
|
|
108
|
+
ALTER TABLE public.agent_grants
|
|
109
|
+
ADD CONSTRAINT agent_grants_agent_id_fkey FOREIGN KEY (agent_id) REFERENCES public.agents(id) ON DELETE CASCADE;
|
|
110
|
+
ALTER TABLE public.agent_connections
|
|
111
|
+
ADD CONSTRAINT agent_connections_agent_id_fkey FOREIGN KEY (agent_id) REFERENCES public.agents(id) ON DELETE CASCADE;
|
|
112
|
+
ALTER TABLE public.agent_users
|
|
113
|
+
ADD CONSTRAINT agent_users_agent_id_fkey FOREIGN KEY (agent_id) REFERENCES public.agents(id) ON DELETE CASCADE;
|
|
114
|
+
ALTER TABLE public.agent_channel_bindings
|
|
115
|
+
ADD CONSTRAINT agent_channel_bindings_agent_id_fkey FOREIGN KEY (agent_id) REFERENCES public.agents(id) ON DELETE CASCADE;
|
|
116
|
+
ALTER TABLE public.grants
|
|
117
|
+
ADD CONSTRAINT grants_agent_id_fkey FOREIGN KEY (agent_id) REFERENCES public.agents(id) ON DELETE CASCADE;
|
|
118
|
+
ALTER TABLE public.scheduled_jobs
|
|
119
|
+
ADD CONSTRAINT scheduled_jobs_agent_fkey FOREIGN KEY (created_by_agent) REFERENCES public.agents(id) ON DELETE CASCADE;
|
|
120
|
+
|
|
121
|
+
ALTER TABLE public.grants ALTER COLUMN organization_id DROP NOT NULL;
|
|
122
|
+
ALTER TABLE public.agent_channel_bindings ALTER COLUMN organization_id DROP NOT NULL;
|
|
123
|
+
ALTER TABLE public.agent_users ALTER COLUMN organization_id DROP NOT NULL;
|
|
124
|
+
ALTER TABLE public.agent_connections ALTER COLUMN organization_id DROP NOT NULL;
|
|
125
|
+
ALTER TABLE public.agent_grants ALTER COLUMN organization_id DROP NOT NULL;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
-- migrate:up
|
|
2
|
+
|
|
3
|
+
-- Materialize the fulltext search vector as a STORED column.
|
|
4
|
+
--
|
|
5
|
+
-- Why a real column instead of the previous expression-indexed `to_tsvector(payload_text)`:
|
|
6
|
+
-- 1. It includes both `title` (weight A) and `payload_text` (weight B) — the
|
|
7
|
+
-- same shape buildSearchDocumentExpr() in content-search.ts uses for
|
|
8
|
+
-- ranking. Retrieval (@@) and ts_rank_cd now read the same vector, so
|
|
9
|
+
-- title-only hits surface correctly and ranking doesn't recompute the
|
|
10
|
+
-- vector per matched row at query time.
|
|
11
|
+
-- 2. Planner-stable: `search_tsv @@ to_tsquery(...)` is a plain column
|
|
12
|
+
-- reference — no expression-shape matching, no aliasing risk where the
|
|
13
|
+
-- GIN gets skipped because the WHERE expression isn't byte-identical to
|
|
14
|
+
-- the indexed expression.
|
|
15
|
+
-- 3. The new GIN strictly subsumes the old payload-only one (same lexemes
|
|
16
|
+
-- plus title's), so we drop the old index and recover its write
|
|
17
|
+
-- amplification on every events insert.
|
|
18
|
+
--
|
|
19
|
+
-- Operational note: ADD COLUMN ... GENERATED STORED rewrites the events
|
|
20
|
+
-- table under ACCESS EXCLUSIVE. On a 1M-row table expect on the order of a
|
|
21
|
+
-- minute; run during a quiet window. CONCURRENTLY does not apply to ADD
|
|
22
|
+
-- COLUMN; it only applies to CREATE INDEX, which is a separate statement
|
|
23
|
+
-- below.
|
|
24
|
+
|
|
25
|
+
ALTER TABLE public.events
|
|
26
|
+
ADD COLUMN search_tsv tsvector GENERATED ALWAYS AS (
|
|
27
|
+
setweight(to_tsvector('english', COALESCE(title, '')), 'A') ||
|
|
28
|
+
setweight(to_tsvector('english', COALESCE(payload_text, '')), 'B')
|
|
29
|
+
) STORED;
|
|
30
|
+
|
|
31
|
+
CREATE INDEX idx_events_search_tsv ON public.events USING gin (search_tsv);
|
|
32
|
+
|
|
33
|
+
DROP INDEX IF EXISTS public.idx_events_fulltext;
|
|
34
|
+
|
|
35
|
+
-- Recreate the current_event_records view so it exposes search_tsv to
|
|
36
|
+
-- callers that go through the supersession filter (the view materializes
|
|
37
|
+
-- its column list at create time, so `e.*`-style auto-pickup doesn't
|
|
38
|
+
-- apply). buildSearchDocumentExpr() reads `f.search_tsv` / `fi.search_tsv`
|
|
39
|
+
-- where f and fi are this view's aliases, so without this the view-backed
|
|
40
|
+
-- queries fail with "column ... does not exist".
|
|
41
|
+
|
|
42
|
+
DROP VIEW IF EXISTS public.current_event_records;
|
|
43
|
+
CREATE VIEW public.current_event_records AS
|
|
44
|
+
SELECT e.id,
|
|
45
|
+
e.organization_id,
|
|
46
|
+
e.entity_ids,
|
|
47
|
+
e.origin_id,
|
|
48
|
+
e.title,
|
|
49
|
+
e.payload_type,
|
|
50
|
+
e.payload_text,
|
|
51
|
+
e.payload_data,
|
|
52
|
+
e.payload_template,
|
|
53
|
+
e.attachments,
|
|
54
|
+
e.metadata,
|
|
55
|
+
e.score,
|
|
56
|
+
emb.embedding,
|
|
57
|
+
e.author_name,
|
|
58
|
+
e.source_url,
|
|
59
|
+
e.occurred_at,
|
|
60
|
+
e.created_at,
|
|
61
|
+
e.origin_parent_id,
|
|
62
|
+
COALESCE(length(e.payload_text), 0) AS content_length,
|
|
63
|
+
e.search_tsv,
|
|
64
|
+
e.origin_type,
|
|
65
|
+
e.connector_key,
|
|
66
|
+
e.connection_id,
|
|
67
|
+
e.feed_key,
|
|
68
|
+
e.feed_id,
|
|
69
|
+
e.run_id,
|
|
70
|
+
e.semantic_type,
|
|
71
|
+
e.client_id,
|
|
72
|
+
e.created_by,
|
|
73
|
+
e.interaction_type,
|
|
74
|
+
e.interaction_status,
|
|
75
|
+
e.interaction_input_schema,
|
|
76
|
+
e.interaction_input,
|
|
77
|
+
e.interaction_output,
|
|
78
|
+
e.interaction_error,
|
|
79
|
+
e.supersedes_event_id
|
|
80
|
+
FROM (public.events e
|
|
81
|
+
LEFT JOIN public.event_embeddings emb ON ((emb.event_id = e.id)))
|
|
82
|
+
WHERE (NOT (EXISTS ( SELECT 1
|
|
83
|
+
FROM public.events newer
|
|
84
|
+
WHERE (newer.supersedes_event_id = e.id))));
|
|
85
|
+
|
|
86
|
+
-- migrate:down
|
|
87
|
+
|
|
88
|
+
DROP VIEW IF EXISTS public.current_event_records;
|
|
89
|
+
CREATE VIEW public.current_event_records AS
|
|
90
|
+
SELECT e.id,
|
|
91
|
+
e.organization_id,
|
|
92
|
+
e.entity_ids,
|
|
93
|
+
e.origin_id,
|
|
94
|
+
e.title,
|
|
95
|
+
e.payload_type,
|
|
96
|
+
e.payload_text,
|
|
97
|
+
e.payload_data,
|
|
98
|
+
e.payload_template,
|
|
99
|
+
e.attachments,
|
|
100
|
+
e.metadata,
|
|
101
|
+
e.score,
|
|
102
|
+
emb.embedding,
|
|
103
|
+
e.author_name,
|
|
104
|
+
e.source_url,
|
|
105
|
+
e.occurred_at,
|
|
106
|
+
e.created_at,
|
|
107
|
+
e.origin_parent_id,
|
|
108
|
+
COALESCE(length(e.payload_text), 0) AS content_length,
|
|
109
|
+
e.origin_type,
|
|
110
|
+
e.connector_key,
|
|
111
|
+
e.connection_id,
|
|
112
|
+
e.feed_key,
|
|
113
|
+
e.feed_id,
|
|
114
|
+
e.run_id,
|
|
115
|
+
e.semantic_type,
|
|
116
|
+
e.client_id,
|
|
117
|
+
e.created_by,
|
|
118
|
+
e.interaction_type,
|
|
119
|
+
e.interaction_status,
|
|
120
|
+
e.interaction_input_schema,
|
|
121
|
+
e.interaction_input,
|
|
122
|
+
e.interaction_output,
|
|
123
|
+
e.interaction_error,
|
|
124
|
+
e.supersedes_event_id
|
|
125
|
+
FROM (public.events e
|
|
126
|
+
LEFT JOIN public.event_embeddings emb ON ((emb.event_id = e.id)))
|
|
127
|
+
WHERE (NOT (EXISTS ( SELECT 1
|
|
128
|
+
FROM public.events newer
|
|
129
|
+
WHERE (newer.supersedes_event_id = e.id))));
|
|
130
|
+
|
|
131
|
+
CREATE INDEX idx_events_fulltext ON public.events
|
|
132
|
+
USING gin (to_tsvector('english'::regconfig, COALESCE(payload_text, ''::text)));
|
|
133
|
+
DROP INDEX IF EXISTS public.idx_events_search_tsv;
|
|
134
|
+
ALTER TABLE public.events DROP COLUMN search_tsv;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
-- migrate:up
|
|
2
|
+
|
|
3
|
+
-- Partial index for the dashboard's 14-day lifecycle-cumulative-stats query
|
|
4
|
+
-- (lifecycleCumulativeStatsSql in packages/owletto/src/lib/api/metric-series.ts).
|
|
5
|
+
--
|
|
6
|
+
-- The query reads events older than `now() - interval '14 days'` and
|
|
7
|
+
-- aggregates by date_trunc('day', created_at), with every COUNT FILTER
|
|
8
|
+
-- already gated on semantic_type='change' AND metadata->>'category'='lifecycle'.
|
|
9
|
+
-- A partial index whose predicate matches that gate prunes the scan from
|
|
10
|
+
-- "all events in the last 14 days for the org" to "just the lifecycle
|
|
11
|
+
-- changes" — typically a small slice of total events. The aggregation
|
|
12
|
+
-- itself is cheap once the row set is narrow.
|
|
13
|
+
--
|
|
14
|
+
-- Why not a materialized view: at current scale the bottleneck is the row
|
|
15
|
+
-- scan, not the per-bucket COUNT FILTER work. A partial index removes the
|
|
16
|
+
-- scan cost without introducing a refresh job or staleness. Revisit if the
|
|
17
|
+
-- post-prune row count grows into the millions.
|
|
18
|
+
|
|
19
|
+
CREATE INDEX idx_events_lifecycle_changes
|
|
20
|
+
ON public.events (organization_id, created_at)
|
|
21
|
+
WHERE semantic_type = 'change' AND metadata->>'category' = 'lifecycle';
|
|
22
|
+
|
|
23
|
+
-- migrate:down
|
|
24
|
+
|
|
25
|
+
DROP INDEX IF EXISTS public.idx_events_lifecycle_changes;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
-- migrate:up
|
|
2
|
+
|
|
3
|
+
-- Drop 4 indexes that pg_stat_user_indexes reported `idx_scan = 0` after 28h
|
|
4
|
+
-- of prod uptime AND are not referenced from any active code path.
|
|
5
|
+
--
|
|
6
|
+
-- A larger set (4 more, ~5 GB combined) was originally bundled here but
|
|
7
|
+
-- review caught they're not actually unused — they're dormant. The three
|
|
8
|
+
-- big search indexes (`idx_events_embedding`, `idx_events_raw_content_trgm`,
|
|
9
|
+
-- `idx_events_search_tsv`) are explicitly used by the ANN/fulltext/trigram
|
|
10
|
+
-- branches of `approximate_candidate_search` in
|
|
11
|
+
-- `packages/server/src/utils/content-search.ts:1707-1733`. The `search()`
|
|
12
|
+
-- agent tool path threads through there; prod just hasn't called it in
|
|
13
|
+
-- 28h, but a single user-initiated search would now time out at 6s and
|
|
14
|
+
-- return empty results without those indexes (`content-search.ts:1850-1863`).
|
|
15
|
+
-- Similarly `idx_events_run_id` backs the "view in memory" filter
|
|
16
|
+
-- (`content-query-filters.ts:197-201`); rare, but a real path.
|
|
17
|
+
--
|
|
18
|
+
-- Keep those four until either (a) the dormant features are removed in
|
|
19
|
+
-- code, or (b) measured prod traffic confirms they're abandoned.
|
|
20
|
+
--
|
|
21
|
+
-- What remains is small but still real write amplification: each kept
|
|
22
|
+
-- INSERT into events updates these btrees. Combined size ~66 MB —
|
|
23
|
+
-- modest reclaim, but zero downside since the underlying queries don't
|
|
24
|
+
-- exist anywhere in the codebase today (verified by grep).
|
|
25
|
+
--
|
|
26
|
+
-- Plain `DROP INDEX` (not CONCURRENTLY) is used because dbmate's
|
|
27
|
+
-- `transaction:false` directive doesn't actually exit the transaction
|
|
28
|
+
-- block against the `pq` driver — see the comment in
|
|
29
|
+
-- 20260426130001_db_integrity_cleanup_concurrent.sql. These 4 indexes
|
|
30
|
+
-- are all small btrees so the ACCESS EXCLUSIVE on `events` during the
|
|
31
|
+
-- drop is sub-second; no operator runbook needed.
|
|
32
|
+
|
|
33
|
+
DROP INDEX IF EXISTS public.idx_events_entity_ids_occurred_at;
|
|
34
|
+
DROP INDEX IF EXISTS public.idx_events_origin_parent_id;
|
|
35
|
+
DROP INDEX IF EXISTS public.idx_events_thread_lookup;
|
|
36
|
+
DROP INDEX IF EXISTS public.idx_events_type;
|
|
37
|
+
|
|
38
|
+
-- migrate:down
|
|
39
|
+
|
|
40
|
+
CREATE INDEX IF NOT EXISTS idx_events_entity_ids_occurred_at
|
|
41
|
+
ON public.events USING btree ((entity_ids[1]), occurred_at DESC, id DESC)
|
|
42
|
+
WHERE ((entity_ids IS NOT NULL) AND (entity_ids <> '{}'::bigint[]));
|
|
43
|
+
CREATE INDEX IF NOT EXISTS idx_events_origin_parent_id
|
|
44
|
+
ON public.events USING btree (origin_parent_id);
|
|
45
|
+
CREATE INDEX IF NOT EXISTS idx_events_thread_lookup
|
|
46
|
+
ON public.events USING btree (origin_parent_id, occurred_at)
|
|
47
|
+
WHERE (origin_parent_id IS NOT NULL);
|
|
48
|
+
CREATE INDEX IF NOT EXISTS idx_events_type
|
|
49
|
+
ON public.events USING btree (origin_type) WHERE (origin_type IS NOT NULL);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
-- migrate:up
|
|
2
|
+
|
|
3
|
+
-- Soft-delete feeds whose (connector_key, organization_id) has no active
|
|
4
|
+
-- connector_definition row.
|
|
5
|
+
--
|
|
6
|
+
-- Why: the 2026-05-16 audit found feeds 117-155 (and others) referencing
|
|
7
|
+
-- connector_key='website' in orgs that have no active definition for it
|
|
8
|
+
-- (only one org has `website` active; one definition is archived). Every
|
|
9
|
+
-- CheckDueFeeds tick (every minute) tried to materialize a sync run for
|
|
10
|
+
-- these feeds and threw "No active connector definition found for X." —
|
|
11
|
+
-- producing ~380 error logs / minute that masked real signal in stdout.
|
|
12
|
+
--
|
|
13
|
+
-- The app-side code path now warns + skips (no throw) for the same case
|
|
14
|
+
-- so future orphans don't spam logs either. This migration is the one-time
|
|
15
|
+
-- cleanup of the existing data.
|
|
16
|
+
--
|
|
17
|
+
-- Conservative criteria — match exactly the set CheckDueFeeds processes
|
|
18
|
+
-- (so we only soft-delete feeds that actually produce the error stream).
|
|
19
|
+
-- - feed has no pinned_version (= would have looked up connector_definitions)
|
|
20
|
+
-- - feed.deleted_at IS NULL (still considered active)
|
|
21
|
+
-- - feed.status = 'active' (CheckDueFeeds filters on this — see
|
|
22
|
+
-- packages/server/src/scheduled/check-due-feeds.ts:36-43)
|
|
23
|
+
-- - connection.deleted_at IS NULL AND connection.status = 'active' (same)
|
|
24
|
+
-- - NO active connector_definition exists for that (key, organization) pair
|
|
25
|
+
--
|
|
26
|
+
-- Feeds in paused / pending_auth / error / revoked states are left alone
|
|
27
|
+
-- — operators may be mid-recovery on them and they don't contribute to
|
|
28
|
+
-- the error spam (CheckDueFeeds skips them anyway).
|
|
29
|
+
--
|
|
30
|
+
-- The same feed remains recoverable: clearing `deleted_at` + reinstalling
|
|
31
|
+
-- the connector definition for the org restores it.
|
|
32
|
+
|
|
33
|
+
UPDATE public.feeds f
|
|
34
|
+
SET deleted_at = now()
|
|
35
|
+
FROM public.connections c
|
|
36
|
+
WHERE f.connection_id = c.id
|
|
37
|
+
AND f.deleted_at IS NULL
|
|
38
|
+
AND f.pinned_version IS NULL
|
|
39
|
+
AND f.status = 'active'
|
|
40
|
+
AND c.deleted_at IS NULL
|
|
41
|
+
AND c.status = 'active'
|
|
42
|
+
AND NOT EXISTS (
|
|
43
|
+
SELECT 1
|
|
44
|
+
FROM public.connector_definitions cd
|
|
45
|
+
WHERE cd.key = c.connector_key
|
|
46
|
+
AND cd.organization_id = f.organization_id
|
|
47
|
+
AND cd.status = 'active'
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
-- migrate:down
|
|
51
|
+
|
|
52
|
+
-- No-op: re-attaching the orphan feeds would require knowing which were
|
|
53
|
+
-- soft-deleted by this migration vs. by an operator action. The original
|
|
54
|
+
-- error condition is fixed in code; this migration is a one-shot data
|
|
55
|
+
-- cleanup. To recover specific feeds in prod, clear `deleted_at` on the
|
|
56
|
+
-- targeted rows manually and re-install the connector definition.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
-- migrate:up
|
|
2
|
+
|
|
3
|
+
-- Bind PATs minted via /api/me/devices/mint-child-token to a specific
|
|
4
|
+
-- worker_id. Without this, a chrome-extension child PAT could post any
|
|
5
|
+
-- worker_id at /api/workers/poll and register a new device_workers row
|
|
6
|
+
-- under a different platform, bypassing the gateway's capability
|
|
7
|
+
-- authorization. The poll handler now refuses a request whose body's
|
|
8
|
+
-- `worker_id` doesn't match the PAT's bound `worker_id` (when non-NULL).
|
|
9
|
+
--
|
|
10
|
+
-- Legacy PATs (CLI tokens, OAuth-issued bearers via dynamic client
|
|
11
|
+
-- registration on the Mac/iOS bridges) keep worker_id = NULL; the poll
|
|
12
|
+
-- handler treats NULL as "no binding" and lets the body's value pass.
|
|
13
|
+
|
|
14
|
+
ALTER TABLE public.personal_access_tokens
|
|
15
|
+
ADD COLUMN IF NOT EXISTS worker_id text;
|
|
16
|
+
|
|
17
|
+
CREATE INDEX IF NOT EXISTS idx_personal_access_tokens_worker_id
|
|
18
|
+
ON public.personal_access_tokens (worker_id)
|
|
19
|
+
WHERE worker_id IS NOT NULL;
|
|
20
|
+
|
|
21
|
+
COMMENT ON COLUMN public.personal_access_tokens.worker_id IS
|
|
22
|
+
'Optional binding to a specific device_workers.worker_id. Set by /api/me/devices/mint-child-token. When non-NULL, /api/workers/poll requires the request body''s worker_id to match.';
|
|
23
|
+
|
|
24
|
+
-- migrate:down
|
|
25
|
+
|
|
26
|
+
DROP INDEX IF EXISTS public.idx_personal_access_tokens_worker_id;
|
|
27
|
+
ALTER TABLE public.personal_access_tokens DROP COLUMN IF EXISTS worker_id;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
-- migrate:up
|
|
2
|
+
|
|
3
|
+
-- Archive watchers that are flagged `status = 'active'` but have no `agent_id`.
|
|
4
|
+
-- The scheduler (packages/server/src/watchers/automation.ts:469) already
|
|
5
|
+
-- filters them out with `WHERE w.agent_id IS NOT NULL`, so these rows are
|
|
6
|
+
-- zombies — visible in the API, redirect-bounced on the watcher detail
|
|
7
|
+
-- route, never actually executing.
|
|
8
|
+
--
|
|
9
|
+
-- 2026-05-17 prod audit: 28 active orphans across 11 orgs (most originated
|
|
10
|
+
-- from older create paths before `agent_id` was wired in). Archiving them
|
|
11
|
+
-- aligns the stored status with their actual execution state and lets the
|
|
12
|
+
-- `/$owner/watchers/$watcherId` redirect logic stop silently sending users
|
|
13
|
+
-- to /agents.
|
|
14
|
+
--
|
|
15
|
+
-- The write-time guard (this same PR, manage_watchers.ts) rejects new
|
|
16
|
+
-- watcher creates/updates that set a schedule without an agent, so the
|
|
17
|
+
-- orphan set can't grow again.
|
|
18
|
+
|
|
19
|
+
UPDATE public.watchers
|
|
20
|
+
SET status = 'archived',
|
|
21
|
+
updated_at = now()
|
|
22
|
+
WHERE status = 'active'
|
|
23
|
+
AND agent_id IS NULL;
|
|
24
|
+
|
|
25
|
+
-- migrate:down
|
|
26
|
+
|
|
27
|
+
-- No-op: this is a one-shot data cleanup. The original rows can be restored
|
|
28
|
+
-- manually if needed by assigning an agent_id and flipping status back to
|
|
29
|
+
-- 'active'; without an agent, status='active' is meaningless to the
|
|
30
|
+
-- scheduler.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
-- migrate:up
|
|
2
|
+
|
|
3
|
+
-- Delete every watcher with `agent_id IS NULL` and enforce the column at
|
|
4
|
+
-- the schema level. These rows are leftovers from before the watcher
|
|
5
|
+
-- scheduler required `agent_id` (see watchers/automation.ts:469) — they
|
|
6
|
+
-- couldn't fire and the prior migration 20260517040000 had already
|
|
7
|
+
-- archived the active subset. Their dependent rows (windows, reactions,
|
|
8
|
+
-- versions, classifiers, field feedback) describe runtime state for
|
|
9
|
+
-- watchers that will never execute; ON DELETE CASCADE clears them.
|
|
10
|
+
-- `runs.watcher_id` is ON DELETE SET NULL, so the 21k historical run
|
|
11
|
+
-- records remain — they just lose the watcher linkage.
|
|
12
|
+
--
|
|
13
|
+
-- Application-level guards in manage_watchers.ts (handleCreate +
|
|
14
|
+
-- handleCreateFromVersion) reject new inserts without `agent_id`; the
|
|
15
|
+
-- NOT NULL constraint below is the matching DB-level enforcement so
|
|
16
|
+
-- bypass paths can't reintroduce the zombie state.
|
|
17
|
+
|
|
18
|
+
DELETE FROM public.watchers WHERE agent_id IS NULL;
|
|
19
|
+
|
|
20
|
+
ALTER TABLE public.watchers
|
|
21
|
+
ALTER COLUMN agent_id SET NOT NULL;
|
|
22
|
+
|
|
23
|
+
-- The existing index is partial (`WHERE agent_id IS NOT NULL`), which is
|
|
24
|
+
-- a tautology once the column is NOT NULL. Replace it with an unconditional
|
|
25
|
+
-- index so explain plans don't show the dead predicate.
|
|
26
|
+
DROP INDEX IF EXISTS public.idx_watchers_agent_id;
|
|
27
|
+
CREATE INDEX idx_watchers_agent_id ON public.watchers USING btree (agent_id);
|
|
28
|
+
|
|
29
|
+
-- migrate:down
|
|
30
|
+
|
|
31
|
+
ALTER TABLE public.watchers
|
|
32
|
+
ALTER COLUMN agent_id DROP NOT NULL;
|
|
33
|
+
|
|
34
|
+
-- No restoring deleted rows — this is forward-only data cleanup.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
-- migrate:up
|
|
2
|
+
|
|
3
|
+
-- Adds the columns needed by the device-aware watcher dispatcher (see
|
|
4
|
+
-- lobu-ai/lobu#798 / #799):
|
|
5
|
+
--
|
|
6
|
+
-- * device_worker_id — pin a watcher to a specific device worker
|
|
7
|
+
-- (when its inputs live on that device).
|
|
8
|
+
-- * agent_kind — overrides the owning agent's default kind for
|
|
9
|
+
-- this watcher (e.g. "background", "notifier").
|
|
10
|
+
-- * notification_channel — where firings surface: canvas, OS notification,
|
|
11
|
+
-- or both.
|
|
12
|
+
-- * notification_priority — priority class used by the dispatcher's
|
|
13
|
+
-- interrupt budget.
|
|
14
|
+
-- * min_cooldown_seconds — minimum seconds between two firings of the
|
|
15
|
+
-- same watcher (0 = no cooldown).
|
|
16
|
+
-- * last_fired_at — last time this watcher actually dispatched
|
|
17
|
+
-- a notification/canvas item.
|
|
18
|
+
--
|
|
19
|
+
-- Also adds device_workers.notification_budget_per_day for the per-device
|
|
20
|
+
-- global interrupt budget. 10/day is a placeholder default; tune later.
|
|
21
|
+
|
|
22
|
+
ALTER TABLE public.watchers
|
|
23
|
+
ADD COLUMN device_worker_id uuid REFERENCES public.device_workers(id),
|
|
24
|
+
ADD COLUMN agent_kind text,
|
|
25
|
+
ADD COLUMN notification_channel text NOT NULL DEFAULT 'canvas',
|
|
26
|
+
ADD COLUMN notification_priority text NOT NULL DEFAULT 'normal',
|
|
27
|
+
ADD COLUMN min_cooldown_seconds integer NOT NULL DEFAULT 0,
|
|
28
|
+
ADD COLUMN last_fired_at timestamp with time zone;
|
|
29
|
+
|
|
30
|
+
ALTER TABLE public.watchers
|
|
31
|
+
ADD CONSTRAINT watchers_notification_channel_check
|
|
32
|
+
CHECK (notification_channel IN ('canvas', 'notification', 'both'));
|
|
33
|
+
|
|
34
|
+
ALTER TABLE public.watchers
|
|
35
|
+
ADD CONSTRAINT watchers_notification_priority_check
|
|
36
|
+
CHECK (notification_priority IN ('low', 'normal', 'high'));
|
|
37
|
+
|
|
38
|
+
ALTER TABLE public.watchers
|
|
39
|
+
ADD CONSTRAINT watchers_min_cooldown_seconds_nonneg
|
|
40
|
+
CHECK (min_cooldown_seconds >= 0);
|
|
41
|
+
|
|
42
|
+
CREATE INDEX IF NOT EXISTS idx_watchers_device_worker_id
|
|
43
|
+
ON public.watchers (device_worker_id)
|
|
44
|
+
WHERE device_worker_id IS NOT NULL;
|
|
45
|
+
|
|
46
|
+
ALTER TABLE public.device_workers
|
|
47
|
+
ADD COLUMN notification_budget_per_day integer NOT NULL DEFAULT 10;
|
|
48
|
+
|
|
49
|
+
ALTER TABLE public.device_workers
|
|
50
|
+
ADD CONSTRAINT device_workers_notification_budget_per_day_nonneg
|
|
51
|
+
CHECK (notification_budget_per_day >= 0);
|
|
52
|
+
|
|
53
|
+
-- migrate:down
|
|
54
|
+
|
|
55
|
+
ALTER TABLE public.device_workers
|
|
56
|
+
DROP CONSTRAINT IF EXISTS device_workers_notification_budget_per_day_nonneg;
|
|
57
|
+
|
|
58
|
+
ALTER TABLE public.device_workers
|
|
59
|
+
DROP COLUMN IF EXISTS notification_budget_per_day;
|
|
60
|
+
|
|
61
|
+
DROP INDEX IF EXISTS public.idx_watchers_device_worker_id;
|
|
62
|
+
|
|
63
|
+
ALTER TABLE public.watchers
|
|
64
|
+
DROP CONSTRAINT IF EXISTS watchers_min_cooldown_seconds_nonneg;
|
|
65
|
+
|
|
66
|
+
ALTER TABLE public.watchers
|
|
67
|
+
DROP CONSTRAINT IF EXISTS watchers_notification_priority_check;
|
|
68
|
+
|
|
69
|
+
ALTER TABLE public.watchers
|
|
70
|
+
DROP CONSTRAINT IF EXISTS watchers_notification_channel_check;
|
|
71
|
+
|
|
72
|
+
ALTER TABLE public.watchers
|
|
73
|
+
DROP COLUMN IF EXISTS last_fired_at,
|
|
74
|
+
DROP COLUMN IF EXISTS min_cooldown_seconds,
|
|
75
|
+
DROP COLUMN IF EXISTS notification_priority,
|
|
76
|
+
DROP COLUMN IF EXISTS notification_channel,
|
|
77
|
+
DROP COLUMN IF EXISTS agent_kind,
|
|
78
|
+
DROP COLUMN IF EXISTS device_worker_id;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
-- migrate:up
|
|
2
|
+
|
|
3
|
+
-- Goals primitive — a top-level handle that groups watchers under a single
|
|
4
|
+
-- user-facing intent (e.g. "Keep my CRM clean", "Watch competitors"). Each
|
|
5
|
+
-- watcher may optionally point at a goal; goals are the surface the
|
|
6
|
+
-- canvas/UI (#801) hangs off, while watchers stay the executable unit.
|
|
7
|
+
--
|
|
8
|
+
-- Schema notes:
|
|
9
|
+
-- - organization_id is `text` (the better-auth `organization.id`) to match
|
|
10
|
+
-- the rest of the schema; the issue body called it `integer`, but every
|
|
11
|
+
-- other org-scoped table (watchers, feeds, connections, …) uses text.
|
|
12
|
+
-- - `(organization_id, slug)` is unique so `lobu apply` can upsert by slug
|
|
13
|
+
-- the same way it does for watchers/agents.
|
|
14
|
+
-- - Goal-template loading from YAML is out of scope here; `template_key`
|
|
15
|
+
-- is just a free-form pointer for the future loader to claim.
|
|
16
|
+
-- - `metadata` is jsonb for forward-compat (icon, color, owner, etc.)
|
|
17
|
+
-- without another migration each time the UI grows a knob.
|
|
18
|
+
|
|
19
|
+
CREATE TABLE public.goals (
|
|
20
|
+
id bigserial PRIMARY KEY,
|
|
21
|
+
organization_id text NOT NULL REFERENCES public.organization(id) ON DELETE CASCADE,
|
|
22
|
+
slug text NOT NULL,
|
|
23
|
+
name text NOT NULL,
|
|
24
|
+
description text,
|
|
25
|
+
status text NOT NULL DEFAULT 'active',
|
|
26
|
+
template_key text,
|
|
27
|
+
metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
28
|
+
created_at timestamp with time zone NOT NULL DEFAULT now(),
|
|
29
|
+
updated_at timestamp with time zone NOT NULL DEFAULT now(),
|
|
30
|
+
|
|
31
|
+
CONSTRAINT goals_status_check
|
|
32
|
+
CHECK (status IN ('active', 'paused', 'archived')),
|
|
33
|
+
CONSTRAINT goals_org_slug_unique
|
|
34
|
+
UNIQUE (organization_id, slug)
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
CREATE INDEX idx_goals_organization_id
|
|
38
|
+
ON public.goals (organization_id);
|
|
39
|
+
|
|
40
|
+
-- Watcher → goal link. NULL means "ungrouped" (today's behavior). ON DELETE
|
|
41
|
+
-- SET NULL keeps the watcher alive when its goal is archived/deleted; the
|
|
42
|
+
-- watcher just becomes ungrouped.
|
|
43
|
+
ALTER TABLE public.watchers
|
|
44
|
+
ADD COLUMN goal_id bigint REFERENCES public.goals(id) ON DELETE SET NULL;
|
|
45
|
+
|
|
46
|
+
CREATE INDEX idx_watchers_goal_id
|
|
47
|
+
ON public.watchers (goal_id)
|
|
48
|
+
WHERE goal_id IS NOT NULL;
|
|
49
|
+
|
|
50
|
+
-- migrate:down
|
|
51
|
+
|
|
52
|
+
ALTER TABLE public.watchers
|
|
53
|
+
DROP COLUMN IF EXISTS goal_id;
|
|
54
|
+
|
|
55
|
+
DROP TABLE IF EXISTS public.goals;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
-- migrate:up
|
|
2
|
+
|
|
3
|
+
-- Revert the goals primitive added in 20260517150000_goals_primitive.sql.
|
|
4
|
+
-- Agents already encapsulate the watcher-grouping use case via
|
|
5
|
+
-- watchers.agent_id; goals were a redundant nullable FK + parallel CRUD with
|
|
6
|
+
-- zero behavioral value. The primitive never shipped in a release.
|
|
7
|
+
|
|
8
|
+
DROP INDEX IF EXISTS idx_watchers_goal_id;
|
|
9
|
+
|
|
10
|
+
ALTER TABLE public.watchers
|
|
11
|
+
DROP COLUMN IF EXISTS goal_id;
|
|
12
|
+
|
|
13
|
+
DROP INDEX IF EXISTS idx_goals_organization_id;
|
|
14
|
+
|
|
15
|
+
DROP TABLE IF EXISTS public.goals;
|
|
16
|
+
|
|
17
|
+
-- migrate:down
|
|
18
|
+
|
|
19
|
+
CREATE TABLE public.goals (
|
|
20
|
+
id bigserial PRIMARY KEY,
|
|
21
|
+
organization_id text NOT NULL REFERENCES public.organization(id) ON DELETE CASCADE,
|
|
22
|
+
slug text NOT NULL,
|
|
23
|
+
name text NOT NULL,
|
|
24
|
+
description text,
|
|
25
|
+
status text NOT NULL DEFAULT 'active',
|
|
26
|
+
template_key text,
|
|
27
|
+
metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
28
|
+
created_at timestamp with time zone NOT NULL DEFAULT now(),
|
|
29
|
+
updated_at timestamp with time zone NOT NULL DEFAULT now(),
|
|
30
|
+
|
|
31
|
+
CONSTRAINT goals_status_check
|
|
32
|
+
CHECK (status IN ('active', 'paused', 'archived')),
|
|
33
|
+
CONSTRAINT goals_org_slug_unique
|
|
34
|
+
UNIQUE (organization_id, slug)
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
CREATE INDEX idx_goals_organization_id
|
|
38
|
+
ON public.goals (organization_id);
|
|
39
|
+
|
|
40
|
+
ALTER TABLE public.watchers
|
|
41
|
+
ADD COLUMN goal_id bigint REFERENCES public.goals(id) ON DELETE SET NULL;
|
|
42
|
+
|
|
43
|
+
CREATE INDEX idx_watchers_goal_id
|
|
44
|
+
ON public.watchers (goal_id)
|
|
45
|
+
WHERE goal_id IS NOT NULL;
|