Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions db/migrations/20260517150000_goals_primitive.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
-- migrate:up

-- Goals primitive — a top-level handle that groups watchers under a single
-- user-facing intent (e.g. "Keep my CRM clean", "Watch competitors"). Each
-- watcher may optionally point at a goal; goals are the surface the
-- canvas/UI (#801) hangs off, while watchers stay the executable unit.
--
-- Schema notes:
-- - organization_id is `text` (the better-auth `organization.id`) to match
-- the rest of the schema; the issue body called it `integer`, but every
-- other org-scoped table (watchers, feeds, connections, …) uses text.
-- - `(organization_id, slug)` is unique so `lobu apply` can upsert by slug
-- the same way it does for watchers/agents.
-- - Goal-template loading from YAML is out of scope here; `template_key`
-- is just a free-form pointer for the future loader to claim.
-- - `metadata` is jsonb for forward-compat (icon, color, owner, etc.)
-- without another migration each time the UI grows a knob.

CREATE TABLE public.goals (
id bigserial PRIMARY KEY,
organization_id text NOT NULL REFERENCES public.organization(id) ON DELETE CASCADE,
slug text NOT NULL,
name text NOT NULL,
description text,
status text NOT NULL DEFAULT 'active',
template_key text,
metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
created_at timestamp with time zone NOT NULL DEFAULT now(),
updated_at timestamp with time zone NOT NULL DEFAULT now(),

CONSTRAINT goals_status_check
CHECK (status IN ('active', 'paused', 'archived')),
CONSTRAINT goals_org_slug_unique
UNIQUE (organization_id, slug)
);

CREATE INDEX idx_goals_organization_id
ON public.goals (organization_id);

-- Watcher → goal link. NULL means "ungrouped" (today's behavior). ON DELETE
-- SET NULL keeps the watcher alive when its goal is archived/deleted; the
-- watcher just becomes ungrouped.
ALTER TABLE public.watchers
ADD COLUMN goal_id bigint REFERENCES public.goals(id) ON DELETE SET NULL;

CREATE INDEX idx_watchers_goal_id
ON public.watchers (goal_id)
WHERE goal_id IS NOT NULL;

-- migrate:down

ALTER TABLE public.watchers
DROP COLUMN IF EXISTS goal_id;

DROP TABLE IF EXISTS public.goals;
85 changes: 84 additions & 1 deletion db/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -1103,6 +1103,41 @@ CREATE SEQUENCE public.feeds_id_seq

ALTER SEQUENCE public.feeds_id_seq OWNED BY public.feeds.id;

--
-- Name: goals; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.goals (
id bigint NOT NULL,
organization_id text NOT NULL,
slug text NOT NULL,
name text NOT NULL,
description text,
status text DEFAULT 'active'::text NOT NULL,
template_key text,
metadata jsonb DEFAULT '{}'::jsonb NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT goals_status_check CHECK ((status = ANY (ARRAY['active'::text, 'paused'::text, 'archived'::text])))
);

--
-- Name: goals_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.goals_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;

--
-- Name: goals_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.goals_id_seq OWNED BY public.goals.id;

--
-- Name: grants; Type: TABLE; Schema: public; Owner: -
--
Expand Down Expand Up @@ -2004,6 +2039,7 @@ CREATE TABLE public.watchers (
scheduler_client_id text,
source_watcher_id integer,
watcher_group_id integer NOT NULL,
goal_id bigint,
device_worker_id uuid,
agent_kind text,
notification_channel text DEFAULT 'canvas'::text NOT NULL,
Expand Down Expand Up @@ -2153,6 +2189,12 @@ ALTER TABLE ONLY public.events ALTER COLUMN id SET DEFAULT nextval('public.conte

ALTER TABLE ONLY public.feeds ALTER COLUMN id SET DEFAULT nextval('public.feeds_id_seq'::regclass);

--
-- Name: goals id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.goals ALTER COLUMN id SET DEFAULT nextval('public.goals_id_seq'::regclass);

--
-- Name: personal_access_tokens id; Type: DEFAULT; Schema: public; Owner: -
--
Expand Down Expand Up @@ -2437,6 +2479,20 @@ ALTER TABLE ONLY public.events
ALTER TABLE ONLY public.feeds
ADD CONSTRAINT feeds_pkey PRIMARY KEY (id);

--
-- Name: goals goals_org_slug_unique; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.goals
ADD CONSTRAINT goals_org_slug_unique UNIQUE (organization_id, slug);

--
-- Name: goals goals_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.goals
ADD CONSTRAINT goals_pkey PRIMARY KEY (id);

--
-- Name: grants grants_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
Expand Down Expand Up @@ -3571,6 +3627,12 @@ CREATE INDEX idx_feeds_org ON public.feeds USING btree (organization_id);

CREATE INDEX idx_feeds_status ON public.feeds USING btree (status);

--
-- Name: idx_goals_organization_id; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_goals_organization_id ON public.goals USING btree (organization_id);

--
-- Name: idx_latest_ec_classifier_id; Type: INDEX; Schema: public; Owner: -
--
Expand Down Expand Up @@ -3823,6 +3885,12 @@ CREATE INDEX idx_watchers_device_worker_id ON public.watchers USING btree (devic

CREATE INDEX idx_watchers_entity_ids ON public.watchers USING gin (entity_ids);

--
-- Name: idx_watchers_goal_id; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_watchers_goal_id ON public.watchers USING btree (goal_id) WHERE (goal_id IS NOT NULL);

--
-- Name: idx_watchers_next_run_at; Type: INDEX; Schema: public; Owner: -
--
Expand Down Expand Up @@ -4565,6 +4633,13 @@ ALTER TABLE ONLY public.event_classifiers
ALTER TABLE ONLY public.event_classifiers
ADD CONSTRAINT fk_event_classifiers_insight FOREIGN KEY (watcher_id) REFERENCES public.watchers(id) ON DELETE SET NULL;

--
-- Name: goals goals_organization_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.goals
ADD CONSTRAINT goals_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES public.organization(id) ON DELETE CASCADE;

--
-- Name: grants grants_org_agent_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--
Expand Down Expand Up @@ -4943,6 +5018,13 @@ ALTER TABLE ONLY public.watchers
ALTER TABLE ONLY public.watchers
ADD CONSTRAINT watchers_device_worker_id_fkey FOREIGN KEY (device_worker_id) REFERENCES public.device_workers(id);

--
-- Name: watchers watchers_goal_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.watchers
ADD CONSTRAINT watchers_goal_id_fkey FOREIGN KEY (goal_id) REFERENCES public.goals(id) ON DELETE SET NULL;

--
-- Name: watchers watchers_organization_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--
Expand Down Expand Up @@ -5028,4 +5110,5 @@ INSERT INTO public.schema_migrations (version) VALUES
('20260517030000'),
('20260517040000'),
('20260517050000'),
('20260517060000');
('20260517060000'),
('20260517150000');
Loading
Loading