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.
501 lines
22 KiB
PL/PgSQL
501 lines
22 KiB
PL/PgSQL
-- 157_scenario_builder_foundation — t-paliad-340 / m/paliad#153 B0
|
|
--
|
|
-- Schema foundation for the Litigation Builder (PRD
|
|
-- docs/plans/prd-procedures-litigation-planner-2026-05-27.md §5.1 + §5.2).
|
|
-- Phase B0 of the 7-slice train described in PRD §7.1. DB-only — no UI
|
|
-- depends on these tables yet; B1 wires the builder shell on top.
|
|
--
|
|
-- What this migration adds:
|
|
--
|
|
-- 1. Six new columns on paliad.scenarios for the builder shape:
|
|
-- owner_id, status, origin_project_id, promoted_project_id,
|
|
-- stichtag, notes.
|
|
-- Two relaxations on existing columns:
|
|
-- - spec NOT NULL → NULL (the builder normalises spec contents
|
|
-- into scenario_proceedings / scenario_events; new rows skip
|
|
-- spec entirely. Legacy callers from mig 145 still provide it
|
|
-- explicitly, so they keep inserting valid rows.)
|
|
-- - DROP CONSTRAINT scenarios_unique_per_scope (the builder
|
|
-- allows multiple "Unbenanntes Szenario" + multiple scratch
|
|
-- scenarios per user — uniqueness on (project_id, created_by,
|
|
-- name) blocks that. The legacy service treated the constraint
|
|
-- as UX collision avoidance, not correctness.)
|
|
--
|
|
-- 2. Three new tables for the normalised builder shape:
|
|
-- - paliad.scenario_proceedings (one row per proceeding in a
|
|
-- scenario; multi-proceeding constellations + spawned children)
|
|
-- - paliad.scenario_events (one row per event card on the
|
|
-- canvas; planned / filed / skipped state + actual_date + notes
|
|
-- + per-card optional horizon)
|
|
-- - paliad.scenario_shares (read-only team shares; owner is
|
|
-- the sole editor)
|
|
--
|
|
-- 3. One new column on paliad.projects:
|
|
-- - origin_scenario_id — audit trail for promote-to-project
|
|
-- (B5; the column lands now so the FK is in place when the
|
|
-- wizard arrives).
|
|
--
|
|
-- 4. New helper function paliad.can_see_scenario(_scenario_id) that
|
|
-- mirrors paliad.can_see_project's STABLE SECURITY DEFINER shape.
|
|
-- Visibility logic:
|
|
-- - global_admin sees everything,
|
|
-- - owner_id = auth.uid() (builder-owned scenarios),
|
|
-- - scenario_shares.shared_with_user_id = auth.uid()
|
|
-- (read-only shared scenarios),
|
|
-- - legacy project-scoped scenarios (owner_id IS NULL AND
|
|
-- project_id IS NOT NULL) follow can_see_project(project_id),
|
|
-- - legacy abstract scenarios (owner_id IS NULL AND project_id
|
|
-- IS NULL) follow created_by = auth.uid().
|
|
--
|
|
-- 5. Replacement RLS policies on paliad.scenarios that fold builder
|
|
-- visibility together with the legacy shape. The legacy
|
|
-- project_* / abstract_* policies are dropped (they covered only
|
|
-- legacy paths) and rewritten as a single pair of policies that
|
|
-- treats owner_id, scenario_shares, and the legacy paths uniformly.
|
|
--
|
|
-- Builder-only RLS for the three new tables: read = scenario
|
|
-- visibility; write = scenario owner (or legacy editor) only.
|
|
--
|
|
-- PRD §5.1 deviations called out for the reader:
|
|
--
|
|
-- - PRD specs `proceeding_type_id uuid REFERENCES paliad.proceeding_types(id)`.
|
|
-- The live column is `integer` (see paliad.proceeding_types.id);
|
|
-- scenario_proceedings.proceeding_type_id is integer here to match
|
|
-- the real FK target. PRD authors did not check the column type;
|
|
-- this migration uses the truth on disk.
|
|
--
|
|
-- - PRD references `auth.users(id)` for owner_id and share columns;
|
|
-- the established paliad convention (see paliad.projects.created_by,
|
|
-- paliad.scenarios.created_by) uses `paliad.users(id)`. Same UUIDs
|
|
-- either way (paliad.users.id == auth.users.id), but the FK targets
|
|
-- paliad.users to stay consistent with project tables.
|
|
--
|
|
-- Audit-first: all DDL ran clean against a BEGIN/ROLLBACK probe on the
|
|
-- live DB before this file was committed. paliad.scenarios has 0 rows
|
|
-- (verified pre-mig), so the column additions and constraint relaxations
|
|
-- have no data impact.
|
|
|
|
BEGIN;
|
|
|
|
SELECT set_config(
|
|
'paliad.audit_reason',
|
|
'mig 157: Scenario builder foundation (t-paliad-340 / m/paliad#153 B0)',
|
|
true
|
|
);
|
|
|
|
-- ----------------------------------------------------------------
|
|
-- 1. paliad.scenarios — additive columns + constraint relaxations
|
|
-- ----------------------------------------------------------------
|
|
|
|
ALTER TABLE paliad.scenarios
|
|
ADD COLUMN owner_id uuid NULL REFERENCES paliad.users(id) ON DELETE CASCADE,
|
|
ADD COLUMN status text NOT NULL DEFAULT 'active'
|
|
CHECK (status IN ('active','archived','promoted')),
|
|
ADD COLUMN origin_project_id uuid NULL REFERENCES paliad.projects(id) ON DELETE SET NULL,
|
|
ADD COLUMN promoted_project_id uuid NULL REFERENCES paliad.projects(id) ON DELETE SET NULL,
|
|
ADD COLUMN stichtag date NULL,
|
|
ADD COLUMN notes text NULL;
|
|
|
|
ALTER TABLE paliad.scenarios ALTER COLUMN spec DROP NOT NULL;
|
|
ALTER TABLE paliad.scenarios DROP CONSTRAINT IF EXISTS scenarios_unique_per_scope;
|
|
|
|
CREATE INDEX scenarios_owner_status_idx
|
|
ON paliad.scenarios(owner_id, status)
|
|
WHERE owner_id IS NOT NULL;
|
|
|
|
CREATE INDEX scenarios_updated_idx
|
|
ON paliad.scenarios(owner_id, updated_at DESC)
|
|
WHERE owner_id IS NOT NULL;
|
|
|
|
COMMENT ON COLUMN paliad.scenarios.owner_id IS
|
|
'Litigation Builder owner (PRD §5.1). NULL = legacy composition-spec '
|
|
'scenario from m/paliad#124 Slice D (mig 145). Builder rows MUST have '
|
|
'owner_id set; the application enforces it via ScenarioBuilderService.';
|
|
|
|
COMMENT ON COLUMN paliad.scenarios.status IS
|
|
'Lifecycle: active (default; user-editable) / archived (soft-deleted, '
|
|
'still visible in side panel) / promoted (converted to project via '
|
|
'B5 wizard; read-only). Legacy mig-145 rows default to active.';
|
|
|
|
COMMENT ON COLUMN paliad.scenarios.origin_project_id IS
|
|
'Set when the scenario was exported from an existing project '
|
|
'("Im Builder öffnen" — Akte mode, PRD §2.3).';
|
|
|
|
COMMENT ON COLUMN paliad.scenarios.promoted_project_id IS
|
|
'Set after the scenario was promoted to a real project via the 3-step '
|
|
'wizard (PRD §5.4). Together with paliad.projects.origin_scenario_id, '
|
|
'forms the bidirectional audit link.';
|
|
|
|
COMMENT ON COLUMN paliad.scenarios.stichtag IS
|
|
'Scenario-level default Stichtag; per-proceeding overrides in '
|
|
'paliad.scenario_proceedings.stichtag take precedence.';
|
|
|
|
-- ----------------------------------------------------------------
|
|
-- 2. paliad.scenario_proceedings — one proceeding per scenario row
|
|
-- ----------------------------------------------------------------
|
|
|
|
CREATE TABLE paliad.scenario_proceedings (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
scenario_id uuid NOT NULL
|
|
REFERENCES paliad.scenarios(id) ON DELETE CASCADE,
|
|
proceeding_type_id integer NOT NULL
|
|
REFERENCES paliad.proceeding_types(id),
|
|
primary_party text NULL
|
|
CHECK (primary_party IN ('claimant','defendant')),
|
|
scenario_flags jsonb NOT NULL DEFAULT '{}'::jsonb
|
|
CHECK (jsonb_typeof(scenario_flags) = 'object'),
|
|
parent_scenario_proceeding_id uuid NULL
|
|
REFERENCES paliad.scenario_proceedings(id) ON DELETE CASCADE,
|
|
spawn_anchor_event_id uuid NULL
|
|
REFERENCES paliad.sequencing_rules(id),
|
|
ordinal int NOT NULL DEFAULT 0,
|
|
stichtag date NULL,
|
|
detailgrad text NOT NULL DEFAULT 'selected'
|
|
CHECK (detailgrad IN ('selected','all_options')),
|
|
appeal_target text NULL,
|
|
collapsed boolean NOT NULL DEFAULT false,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE INDEX scenario_proceedings_scenario_idx
|
|
ON paliad.scenario_proceedings(scenario_id, ordinal);
|
|
|
|
CREATE INDEX scenario_proceedings_parent_idx
|
|
ON paliad.scenario_proceedings(parent_scenario_proceeding_id)
|
|
WHERE parent_scenario_proceeding_id IS NOT NULL;
|
|
|
|
COMMENT ON TABLE paliad.scenario_proceedings IS
|
|
'One proceeding inside a Litigation Builder scenario. Multiple rows '
|
|
'per scenario for multi-proceeding constellations. '
|
|
'parent_scenario_proceeding_id self-refs for spawned children '
|
|
'(e.g. upc.ccr.cfi spawned by with_ccr on upc.inf.cfi). '
|
|
'PRD §5.1, m/paliad#153 B0.';
|
|
|
|
COMMENT ON COLUMN paliad.scenario_proceedings.primary_party IS
|
|
'Per-proceeding perspective ("our side"). NULL = no perspective '
|
|
'picked yet (both party columns render with natural labels). '
|
|
'Per-proceeding so multi-jurisdiction constellations can flip side '
|
|
'independently (PRD §3.3).';
|
|
|
|
COMMENT ON COLUMN paliad.scenario_proceedings.scenario_flags IS
|
|
'Per-proceeding flags (e.g. {"with_ccr": true, "with_amend": false}). '
|
|
'Mirrors paliad.projects.scenario_flags shape but lives per-proceeding-'
|
|
'per-scenario. Validated by the application against '
|
|
'paliad.scenario_flag_catalog at write time.';
|
|
|
|
COMMENT ON COLUMN paliad.scenario_proceedings.spawn_anchor_event_id IS
|
|
'Which sequencing_rule of the parent proceeding caused this spawn. '
|
|
'NULL for root proceedings. Used by the UI to place the spawned child '
|
|
'triplet directly below the parent at the spawn node (PRD §3.6).';
|
|
|
|
COMMENT ON COLUMN paliad.scenario_proceedings.ordinal IS
|
|
'Stack order on canvas (top to bottom). Siblings under the same '
|
|
'parent (or top-level) are ordered by ordinal asc, then created_at.';
|
|
|
|
COMMENT ON COLUMN paliad.scenario_proceedings.detailgrad IS
|
|
'Per-proceeding optional-detail toggle: selected (only explicitly '
|
|
'chosen optionals + mandatories) or all_options (every optional '
|
|
'sequencing_rule surfaces). Matches today''s Verfahrensablauf pattern.';
|
|
|
|
-- ----------------------------------------------------------------
|
|
-- 3. paliad.scenario_events — one event card on the canvas
|
|
-- ----------------------------------------------------------------
|
|
|
|
CREATE TABLE paliad.scenario_events (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
scenario_proceeding_id uuid NOT NULL
|
|
REFERENCES paliad.scenario_proceedings(id) ON DELETE CASCADE,
|
|
sequencing_rule_id uuid NULL
|
|
REFERENCES paliad.sequencing_rules(id),
|
|
procedural_event_id uuid NULL
|
|
REFERENCES paliad.procedural_events(id),
|
|
custom_label text NULL,
|
|
state text NOT NULL DEFAULT 'planned'
|
|
CHECK (state IN ('planned','filed','skipped')),
|
|
actual_date date NULL,
|
|
skip_reason text NULL,
|
|
notes text NULL,
|
|
horizon_optional int NOT NULL DEFAULT 0
|
|
CHECK (horizon_optional >= 0),
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
CONSTRAINT scenario_events_one_anchor CHECK (
|
|
(sequencing_rule_id IS NOT NULL)::int +
|
|
(procedural_event_id IS NOT NULL)::int +
|
|
(custom_label IS NOT NULL)::int >= 1
|
|
)
|
|
);
|
|
|
|
CREATE INDEX scenario_events_proceeding_idx
|
|
ON paliad.scenario_events(scenario_proceeding_id);
|
|
|
|
-- A single proceeding can't carry two cards for the same sequencing rule
|
|
-- (each rule maps to one card). Free-form / procedural_event-only cards
|
|
-- skip this uniqueness — multiple custom cards per proceeding are OK.
|
|
CREATE UNIQUE INDEX scenario_events_rule_uniq_idx
|
|
ON paliad.scenario_events(scenario_proceeding_id, sequencing_rule_id)
|
|
WHERE sequencing_rule_id IS NOT NULL;
|
|
|
|
COMMENT ON TABLE paliad.scenario_events IS
|
|
'One event card on the Litigation Builder canvas. Captures state '
|
|
'(planned/filed/skipped), actual_date, notes, skip_reason, and the '
|
|
'per-card optional-horizon setting. At least one of '
|
|
'(sequencing_rule_id, procedural_event_id, custom_label) must be '
|
|
'set — sequencing-rule-backed cards are the common case; free-form '
|
|
'cards exist for events the catalog doesn''t cover yet. '
|
|
'PRD §3.4 / §5.1.';
|
|
|
|
COMMENT ON COLUMN paliad.scenario_events.state IS
|
|
'3-state machine: planned (default, future event with computed date) '
|
|
'/ filed (past event, actual_date set) / skipped (user chose not to '
|
|
'file; optional skip_reason). No "overdue" enum — that''s derived '
|
|
'(date < today AND state=planned), not stored. PRD Q10 / §3.4.';
|
|
|
|
COMMENT ON COLUMN paliad.scenario_events.actual_date IS
|
|
'Set when state=filed (real-world filing date) OR when state=planned '
|
|
'and the user overrode the computed date (court-set events, manual '
|
|
'tweaks). NULL when the computed date is canonical.';
|
|
|
|
COMMENT ON COLUMN paliad.scenario_events.horizon_optional IS
|
|
'Per-card "show N more optional follow-ups" affordance. Default 0 '
|
|
'(hidden). PRD Q4 / §3.4.';
|
|
|
|
-- ----------------------------------------------------------------
|
|
-- 4. paliad.scenario_shares — read-only team shares
|
|
-- ----------------------------------------------------------------
|
|
|
|
CREATE TABLE paliad.scenario_shares (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
scenario_id uuid NOT NULL
|
|
REFERENCES paliad.scenarios(id) ON DELETE CASCADE,
|
|
shared_with_user_id uuid NOT NULL
|
|
REFERENCES paliad.users(id) ON DELETE CASCADE,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
created_by uuid NOT NULL REFERENCES paliad.users(id),
|
|
UNIQUE (scenario_id, shared_with_user_id)
|
|
);
|
|
|
|
CREATE INDEX scenario_shares_user_idx
|
|
ON paliad.scenario_shares(shared_with_user_id);
|
|
|
|
COMMENT ON TABLE paliad.scenario_shares IS
|
|
'Read-only team shares for Litigation Builder scenarios. Owner '
|
|
'(paliad.scenarios.owner_id) is the sole editor; rows here grant '
|
|
'view-only access to other paliad users. PRD Q12 / §5.1.';
|
|
|
|
-- ----------------------------------------------------------------
|
|
-- 5. paliad.projects.origin_scenario_id — promote-to-project trail
|
|
-- ----------------------------------------------------------------
|
|
|
|
ALTER TABLE paliad.projects
|
|
ADD COLUMN origin_scenario_id uuid NULL
|
|
REFERENCES paliad.scenarios(id) ON DELETE SET NULL;
|
|
|
|
CREATE INDEX projects_origin_scenario_idx
|
|
ON paliad.projects(origin_scenario_id)
|
|
WHERE origin_scenario_id IS NOT NULL;
|
|
|
|
COMMENT ON COLUMN paliad.projects.origin_scenario_id IS
|
|
'FK to the scenario this project was promoted from (B5 wizard). '
|
|
'NULL = project was created directly, not via Builder. Together with '
|
|
'paliad.scenarios.promoted_project_id, forms the bidirectional audit '
|
|
'link. PRD §5.2.';
|
|
|
|
-- ----------------------------------------------------------------
|
|
-- 6. paliad.can_see_scenario — visibility helper
|
|
-- ----------------------------------------------------------------
|
|
|
|
CREATE OR REPLACE FUNCTION paliad.can_see_scenario(_scenario_id uuid)
|
|
RETURNS boolean
|
|
LANGUAGE sql STABLE SECURITY DEFINER
|
|
SET search_path TO 'paliad', 'public'
|
|
AS $func$
|
|
SELECT EXISTS (
|
|
SELECT 1 FROM paliad.users u
|
|
WHERE u.id = auth.uid() AND u.global_role = 'global_admin'
|
|
)
|
|
OR EXISTS (
|
|
SELECT 1 FROM paliad.scenarios s
|
|
WHERE s.id = _scenario_id AND s.owner_id = auth.uid()
|
|
)
|
|
OR EXISTS (
|
|
SELECT 1 FROM paliad.scenario_shares sh
|
|
WHERE sh.scenario_id = _scenario_id
|
|
AND sh.shared_with_user_id = auth.uid()
|
|
)
|
|
-- Legacy project-scoped scenarios (mig 145) — visible via project
|
|
-- team membership.
|
|
OR EXISTS (
|
|
SELECT 1 FROM paliad.scenarios s
|
|
WHERE s.id = _scenario_id
|
|
AND s.owner_id IS NULL
|
|
AND s.project_id IS NOT NULL
|
|
AND paliad.can_see_project(s.project_id)
|
|
)
|
|
-- Legacy abstract scenarios (mig 145) — owner-only via created_by.
|
|
OR EXISTS (
|
|
SELECT 1 FROM paliad.scenarios s
|
|
WHERE s.id = _scenario_id
|
|
AND s.owner_id IS NULL
|
|
AND s.project_id IS NULL
|
|
AND s.created_by = auth.uid()
|
|
);
|
|
$func$;
|
|
|
|
COMMENT ON FUNCTION paliad.can_see_scenario(uuid) IS
|
|
'Returns true if the caller (auth.uid()) can see the given scenario. '
|
|
'Mirrors paliad.can_see_project. Covers builder-owned scenarios '
|
|
'(owner_id), read-only shares (scenario_shares), and the two legacy '
|
|
'paths from mig 145 (project-scoped via can_see_project, abstract '
|
|
'via created_by). Used by RLS on all four scenario_* tables.';
|
|
|
|
-- ----------------------------------------------------------------
|
|
-- 7. RLS — replace legacy scenarios policies + new tables
|
|
-- ----------------------------------------------------------------
|
|
|
|
-- Replace mig-145's four policies with a single pair that handles
|
|
-- builder + legacy shapes together.
|
|
DROP POLICY IF EXISTS scenarios_project_select ON paliad.scenarios;
|
|
DROP POLICY IF EXISTS scenarios_project_mutate ON paliad.scenarios;
|
|
DROP POLICY IF EXISTS scenarios_abstract_select ON paliad.scenarios;
|
|
DROP POLICY IF EXISTS scenarios_abstract_mutate ON paliad.scenarios;
|
|
|
|
CREATE POLICY scenarios_select ON paliad.scenarios
|
|
FOR SELECT USING (paliad.can_see_scenario(id));
|
|
|
|
-- Write rule: builder owner, legacy project team member (if no owner),
|
|
-- or legacy abstract creator (if no owner + no project). Shares are
|
|
-- read-only — they don't grant mutate.
|
|
CREATE POLICY scenarios_owner_mutate ON paliad.scenarios
|
|
FOR ALL
|
|
USING (
|
|
owner_id = auth.uid()
|
|
OR (owner_id IS NULL AND project_id IS NOT NULL AND paliad.can_see_project(project_id))
|
|
OR (owner_id IS NULL AND project_id IS NULL AND created_by = auth.uid())
|
|
)
|
|
WITH CHECK (
|
|
owner_id = auth.uid()
|
|
OR (owner_id IS NULL AND project_id IS NOT NULL AND paliad.can_see_project(project_id))
|
|
OR (owner_id IS NULL AND project_id IS NULL AND created_by = auth.uid())
|
|
);
|
|
|
|
-- scenario_proceedings — visibility piggybacks on the parent scenario.
|
|
ALTER TABLE paliad.scenario_proceedings ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY scenario_proceedings_select ON paliad.scenario_proceedings
|
|
FOR SELECT USING (paliad.can_see_scenario(scenario_id));
|
|
|
|
CREATE POLICY scenario_proceedings_mutate ON paliad.scenario_proceedings
|
|
FOR ALL
|
|
USING (EXISTS (
|
|
SELECT 1 FROM paliad.scenarios s
|
|
WHERE s.id = scenario_id
|
|
AND (s.owner_id = auth.uid()
|
|
OR (s.owner_id IS NULL AND s.project_id IS NOT NULL AND paliad.can_see_project(s.project_id))
|
|
OR (s.owner_id IS NULL AND s.project_id IS NULL AND s.created_by = auth.uid()))
|
|
))
|
|
WITH CHECK (EXISTS (
|
|
SELECT 1 FROM paliad.scenarios s
|
|
WHERE s.id = scenario_id
|
|
AND (s.owner_id = auth.uid()
|
|
OR (s.owner_id IS NULL AND s.project_id IS NOT NULL AND paliad.can_see_project(s.project_id))
|
|
OR (s.owner_id IS NULL AND s.project_id IS NULL AND s.created_by = auth.uid()))
|
|
));
|
|
|
|
-- scenario_events — visibility piggybacks on the parent scenario via
|
|
-- the proceeding row.
|
|
ALTER TABLE paliad.scenario_events ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY scenario_events_select ON paliad.scenario_events
|
|
FOR SELECT
|
|
USING (EXISTS (
|
|
SELECT 1 FROM paliad.scenario_proceedings sp
|
|
WHERE sp.id = scenario_proceeding_id
|
|
AND paliad.can_see_scenario(sp.scenario_id)
|
|
));
|
|
|
|
CREATE POLICY scenario_events_mutate ON paliad.scenario_events
|
|
FOR ALL
|
|
USING (EXISTS (
|
|
SELECT 1 FROM paliad.scenario_proceedings sp
|
|
JOIN paliad.scenarios s ON s.id = sp.scenario_id
|
|
WHERE sp.id = scenario_proceeding_id
|
|
AND (s.owner_id = auth.uid()
|
|
OR (s.owner_id IS NULL AND s.project_id IS NOT NULL AND paliad.can_see_project(s.project_id))
|
|
OR (s.owner_id IS NULL AND s.project_id IS NULL AND s.created_by = auth.uid()))
|
|
))
|
|
WITH CHECK (EXISTS (
|
|
SELECT 1 FROM paliad.scenario_proceedings sp
|
|
JOIN paliad.scenarios s ON s.id = sp.scenario_id
|
|
WHERE sp.id = scenario_proceeding_id
|
|
AND (s.owner_id = auth.uid()
|
|
OR (s.owner_id IS NULL AND s.project_id IS NOT NULL AND paliad.can_see_project(s.project_id))
|
|
OR (s.owner_id IS NULL AND s.project_id IS NULL AND s.created_by = auth.uid()))
|
|
));
|
|
|
|
-- scenario_shares — recipient can see their share rows; the scenario
|
|
-- owner (or legacy editor) can manage them.
|
|
ALTER TABLE paliad.scenario_shares ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY scenario_shares_select ON paliad.scenario_shares
|
|
FOR SELECT
|
|
USING (
|
|
shared_with_user_id = auth.uid()
|
|
OR EXISTS (
|
|
SELECT 1 FROM paliad.scenarios s
|
|
WHERE s.id = scenario_id
|
|
AND (s.owner_id = auth.uid()
|
|
OR (s.owner_id IS NULL AND s.project_id IS NOT NULL AND paliad.can_see_project(s.project_id))
|
|
OR (s.owner_id IS NULL AND s.project_id IS NULL AND s.created_by = auth.uid()))
|
|
)
|
|
);
|
|
|
|
CREATE POLICY scenario_shares_mutate ON paliad.scenario_shares
|
|
FOR ALL
|
|
USING (EXISTS (
|
|
SELECT 1 FROM paliad.scenarios s
|
|
WHERE s.id = scenario_id
|
|
AND (s.owner_id = auth.uid()
|
|
OR (s.owner_id IS NULL AND s.project_id IS NOT NULL AND paliad.can_see_project(s.project_id))
|
|
OR (s.owner_id IS NULL AND s.project_id IS NULL AND s.created_by = auth.uid()))
|
|
))
|
|
WITH CHECK (EXISTS (
|
|
SELECT 1 FROM paliad.scenarios s
|
|
WHERE s.id = scenario_id
|
|
AND (s.owner_id = auth.uid()
|
|
OR (s.owner_id IS NULL AND s.project_id IS NOT NULL AND paliad.can_see_project(s.project_id))
|
|
OR (s.owner_id IS NULL AND s.project_id IS NULL AND s.created_by = auth.uid()))
|
|
));
|
|
|
|
-- ----------------------------------------------------------------
|
|
-- 8. updated_at triggers on the new tables (reuse the function mig 145
|
|
-- already created for paliad.scenarios).
|
|
-- ----------------------------------------------------------------
|
|
|
|
CREATE TRIGGER scenario_proceedings_touch_updated_at_trg
|
|
BEFORE UPDATE ON paliad.scenario_proceedings
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION paliad.scenarios_touch_updated_at();
|
|
|
|
CREATE TRIGGER scenario_events_touch_updated_at_trg
|
|
BEFORE UPDATE ON paliad.scenario_events
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION paliad.scenarios_touch_updated_at();
|
|
|
|
-- ----------------------------------------------------------------
|
|
-- 9. Informational NOTICE.
|
|
-- ----------------------------------------------------------------
|
|
|
|
DO $$
|
|
BEGIN
|
|
RAISE NOTICE '[mig 157] paliad.scenarios extended with builder columns (0 legacy rows affected)';
|
|
RAISE NOTICE '[mig 157] paliad.scenario_proceedings created';
|
|
RAISE NOTICE '[mig 157] paliad.scenario_events created';
|
|
RAISE NOTICE '[mig 157] paliad.scenario_shares created';
|
|
RAISE NOTICE '[mig 157] paliad.projects.origin_scenario_id added';
|
|
RAISE NOTICE '[mig 157] paliad.can_see_scenario(uuid) created';
|
|
END $$;
|
|
|
|
COMMIT;
|