Files
paliad/internal/db/migrations/157_scenario_builder_foundation.down.sql
mAi 0f3c30a647
Some checks failed
Paliad CI gate / build (push) Has been cancelled
Paliad CI gate / test-go (push) Has been cancelled
Paliad CI gate / deploy (push) Has been cancelled
feat(scenario-builder): B0 schema foundation + minimal API (m/paliad#153)
t-paliad-340 — B0 of edison's 7-slice train (PRD §7.1). DB-only: schema
+ RLS land, dev-only test route exercises the surface, no user-facing
change. B1 wires the actual builder UI on top.

Migration 157 (additive on the legacy mig-145 scenarios table — 0 rows
in prod, safe to relax):
- paliad.scenarios gets owner_id / status / origin_project_id /
  promoted_project_id / stichtag / notes. spec drops NOT NULL and the
  scenarios_unique_per_scope constraint drops (the builder allows
  multiple scratch + Unbenanntes Szenario rows per user).
- New tables: scenario_proceedings, scenario_events, scenario_shares.
- paliad.projects.origin_scenario_id for the promote-to-project audit
  trail (the FK lands now; the wizard ships in B5).
- paliad.can_see_scenario(uuid) STABLE SECURITY DEFINER helper covering
  owner / share / global_admin / two legacy paths.
- Replacement RLS on scenarios + RLS on the three new tables; legacy
  service + handlers stay live and unchanged.

PRD §5.1 deviations called out in the migration header:
- proceeding_type_id is integer (live schema), not uuid (PRD draft).
- FK target is paliad.users, matching the rest of paliad's schema.

Go surface:
- ScenarioBuilderService — list/create/get-deep/patch scenarios,
  add/patch/delete proceedings, add/patch/delete events,
  add/delete shares. Writes wrap in transactions with set_config(
  paliad.audit_reason, ..., true) per event_choice_service.go pattern.
- /api/builder/scenarios/* — handlers register under a builder/
  prefix so the legacy /api/scenarios surface still works.
- /dev/scenario-builder — single-page HTML form gated to
  PaliadinOwnerEmail, exercises the B0 surface without Postman.
- Live-DB integration test (TEST_DATABASE_URL gated) covers
  create + list + deep-get + share + visibility negatives + patch.

Audit-first: every DDL block ran clean via BEGIN/ROLLBACK against
the live DB before commit; end-to-end sanity (insert chain + CHECK
constraints + CASCADE-on-delete) verified via the Supabase MCP.

bun build clean. go vet + go test -short ./... green.
2026-05-27 23:50:14 +02:00

95 lines
3.9 KiB
PL/PgSQL

-- 157_scenario_builder_foundation — down
--
-- Rolls back mig 157 in reverse order. Down files are reference material
-- (not auto-applied); operator recovery path is:
--
-- psql ... < 157_scenario_builder_foundation.down.sql
-- DELETE FROM paliad.applied_migrations WHERE version = 157;
--
-- This restores the legacy paliad.scenarios shape from mig 145 — the
-- builder columns and the three sibling tables are dropped wholesale.
-- Any builder data in the dropped tables is lost (the tables CASCADE to
-- their children, and DROP TABLE doesn't keep a backup).
BEGIN;
SELECT set_config(
'paliad.audit_reason',
'mig 157 rollback: tear down Scenario builder foundation (t-paliad-340)',
true
);
-- 8. updated_at triggers
DROP TRIGGER IF EXISTS scenario_events_touch_updated_at_trg ON paliad.scenario_events;
DROP TRIGGER IF EXISTS scenario_proceedings_touch_updated_at_trg ON paliad.scenario_proceedings;
-- 7. RLS — drop new policies + restore legacy four
DROP POLICY IF EXISTS scenario_shares_mutate ON paliad.scenario_shares;
DROP POLICY IF EXISTS scenario_shares_select ON paliad.scenario_shares;
DROP POLICY IF EXISTS scenario_events_mutate ON paliad.scenario_events;
DROP POLICY IF EXISTS scenario_events_select ON paliad.scenario_events;
DROP POLICY IF EXISTS scenario_proceedings_mutate ON paliad.scenario_proceedings;
DROP POLICY IF EXISTS scenario_proceedings_select ON paliad.scenario_proceedings;
DROP POLICY IF EXISTS scenarios_owner_mutate ON paliad.scenarios;
DROP POLICY IF EXISTS scenarios_select ON paliad.scenarios;
-- Restore the four mig-145 policies verbatim.
CREATE POLICY scenarios_project_select ON paliad.scenarios
FOR SELECT
USING (project_id IS NOT NULL AND paliad.can_see_project(project_id));
CREATE POLICY scenarios_project_mutate ON paliad.scenarios
FOR ALL
USING (project_id IS NOT NULL AND paliad.can_see_project(project_id))
WITH CHECK (project_id IS NOT NULL AND paliad.can_see_project(project_id));
CREATE POLICY scenarios_abstract_select ON paliad.scenarios
FOR SELECT
USING (project_id IS NULL AND created_by = auth.uid());
CREATE POLICY scenarios_abstract_mutate ON paliad.scenarios
FOR ALL
USING (project_id IS NULL AND created_by = auth.uid())
WITH CHECK (project_id IS NULL AND created_by = auth.uid());
-- 6. helper function
DROP FUNCTION IF EXISTS paliad.can_see_scenario(uuid);
-- 5. paliad.projects.origin_scenario_id
DROP INDEX IF EXISTS paliad.projects_origin_scenario_idx;
ALTER TABLE paliad.projects DROP COLUMN IF EXISTS origin_scenario_id;
-- 4. paliad.scenario_shares
DROP TABLE IF EXISTS paliad.scenario_shares;
-- 3. paliad.scenario_events
DROP TABLE IF EXISTS paliad.scenario_events;
-- 2. paliad.scenario_proceedings
DROP TABLE IF EXISTS paliad.scenario_proceedings;
-- 1. paliad.scenarios — restore mig-145 shape
DROP INDEX IF EXISTS paliad.scenarios_updated_idx;
DROP INDEX IF EXISTS paliad.scenarios_owner_status_idx;
-- Restore the unique constraint mig 145 had.
ALTER TABLE paliad.scenarios
ADD CONSTRAINT scenarios_unique_per_scope
UNIQUE NULLS NOT DISTINCT (project_id, created_by, name);
-- spec was NOT NULL in mig 145. Restore that — but only after backfilling
-- any NULL specs the builder might have created (none in legacy paths;
-- only builder rows have NULL spec, and those are dropped together with
-- the builder schema if a real rollback is needed).
UPDATE paliad.scenarios SET spec = '{}'::jsonb WHERE spec IS NULL;
ALTER TABLE paliad.scenarios ALTER COLUMN spec SET NOT NULL;
ALTER TABLE paliad.scenarios DROP COLUMN IF EXISTS notes;
ALTER TABLE paliad.scenarios DROP COLUMN IF EXISTS stichtag;
ALTER TABLE paliad.scenarios DROP COLUMN IF EXISTS promoted_project_id;
ALTER TABLE paliad.scenarios DROP COLUMN IF EXISTS origin_project_id;
ALTER TABLE paliad.scenarios DROP COLUMN IF EXISTS status;
ALTER TABLE paliad.scenarios DROP COLUMN IF EXISTS owner_id;
COMMIT;