-- 154_scenario_flags_ssot — t-paliad-331 / m/paliad#149 Phase 2 P0 -- -- Single source of truth for per-project scenario state. Per the -- design (docs/design-deadline-system-revision-2026-05-27.md §2.3 -- and §2.4a), every scenario decision a user makes on a project -- lives in one jsonb column on paliad.projects: -- -- { "with_ccr": true, "with_amend": false, -- "rule:": true, -- "rule:": false } -- -- Entries are either: -- * named scenario flags (whitelist via paliad.scenario_flag_catalog), or -- * per-rule selection deviations of shape "rule:". -- -- The application validates writes against the catalog and the -- project's active sequencing-rules set; this migration only adds the -- storage. The three known flags (with_ccr / with_amend / with_cci) -- are seeded into the catalog so the API layer has something to -- validate against on day one — extra flags are admin-added later -- (see §4.2.1 R.109 worked example: with_interpreter_denied / -- with_translation_granted both land via the editor when m walks the -- backfill, no fresh migration needed). -- -- Purely additive: ADD COLUMN with safe DEFAULT, CREATE TABLE, seed -- inserts. Three existing scenario storage surfaces (project_event_ -- choices, scenarios.spec, DOM-only) are all empty per athena's audit -- (zero rows in either persistent surface), so there is nothing to -- migrate. -- -- No audit trigger fires on paliad.projects today; set_config is -- defensive so any future audit trigger inherits the reason. BEGIN; SELECT set_config( 'paliad.audit_reason', 'mig 154: scenario_flags SSoT (t-paliad-331 / m/paliad#149 Phase 2 P0)', true ); -- ---------------------------------------------------------------- -- 1. paliad.projects.scenario_flags — the jsonb SSoT. -- ---------------------------------------------------------------- ALTER TABLE paliad.projects ADD COLUMN scenario_flags jsonb NOT NULL DEFAULT '{}'::jsonb CHECK (jsonb_typeof(scenario_flags) = 'object'); COMMENT ON COLUMN paliad.projects.scenario_flags IS 'Per-project scenario state — single source of truth (m/paliad#149 ' 'Phase 2 P0, design §2.3 + §2.4a). Flat jsonb object whose keys are ' 'either named scenario flags (whitelist via paliad.scenario_flag_catalog) ' 'or per-rule selection deviations of shape "rule:". Values are ' 'always JSON booleans; missing keys take the priority-driven default ' '(mandatory always selected; recommended default-selected; optional ' 'default-unselected). Validated at write time by the ' 'ScenarioFlagsService.Patch handler; this column''s CHECK only ' 'enforces that the top-level shape is an object.'; -- ---------------------------------------------------------------- -- 2. paliad.scenario_flag_catalog — the named-flag whitelist. -- Per design §4.1: a small admin-editable vocabulary that powers -- both the write-time validator and the UI's scenario-flag strip. -- Per-rule entries ("rule:") are NOT enumerated here — they -- match a pattern and are validated by resolving the UUID against -- the project's active sequencing-rules set. -- ---------------------------------------------------------------- CREATE TABLE paliad.scenario_flag_catalog ( flag_key text PRIMARY KEY CHECK (flag_key ~ '^[a-z][a-z0-9_]*$' AND flag_key NOT LIKE 'rule:%' AND char_length(flag_key) BETWEEN 1 AND 64), label_de text NOT NULL CHECK (char_length(label_de) > 0), label_en text NOT NULL CHECK (char_length(label_en) > 0), description text NULL, -- hidden_unless_set: when true, the flag is only surfaced in the -- UI's scenario strip once a rule's condition_expr references it -- (or once it's explicitly set on a project). Per design §4.2.1, -- with_interpreter_denied + with_translation_granted are good -- candidates for this once they're seeded — the flag exists for -- write validation but doesn't clutter the default UI. hidden_unless_set boolean NOT NULL DEFAULT false, added_at timestamptz NOT NULL DEFAULT now() ); COMMENT ON TABLE paliad.scenario_flag_catalog IS 'Named-flag vocabulary for paliad.projects.scenario_flags ' '(m/paliad#149 Phase 2 P0, design §4.1). Read by the write-time ' 'validator in ScenarioFlagsService.Patch and by the Verfahrensablauf ' 'scenario-strip UI. Per-rule selection entries ("rule:") are ' 'NOT enumerated here — they match a pattern and are validated by ' 'UUID lookup against the project''s active sequencing-rules set.'; COMMENT ON COLUMN paliad.scenario_flag_catalog.hidden_unless_set IS 'When true, the flag does not appear in the default UI scenario ' 'strip — it is surfaced only when a rule''s condition_expr ' 'references it or when the project already has it set. Lets us ' 'register rare flags (e.g. with_interpreter_denied) without ' 'cluttering the default strip.'; -- ---------------------------------------------------------------- -- 3. Seed the three known flags. These are the flags referenced by -- the 18 condition_expr rows in paliad.sequencing_rules today -- (4 composite condition_expr rows are and/or-of these three). -- ---------------------------------------------------------------- INSERT INTO paliad.scenario_flag_catalog (flag_key, label_de, label_en, description, hidden_unless_set) VALUES ('with_ccr', 'Mit Widerklage auf Nichtigkeit', 'With counterclaim for revocation (CCR)', 'Active when the defendant has filed a CCR. Gates R.025 + the R.029 reply/rejoinder chain on upc.inf.cfi and the R.030 amendment branch nested under it.', false), ('with_amend', 'Mit Antrag auf Patentänderung (R.30)', 'With application to amend the patent (R.30)', 'Active when the patentee has filed an R.30 application. Gates the R.032 def-to-amend / reply / rejoinder chain on the amendment branch.', false), ('with_cci', 'Mit Widerklage auf Verletzung', 'With counterclaim for infringement (CCI)', 'Active when the defendant on a revocation action has filed an infringement counterclaim. Gates the analogous chain on upc.rev.cfi (the inverse of with_ccr).', false); -- ---------------------------------------------------------------- -- 4. Sanity check + informational notice. -- ---------------------------------------------------------------- DO $$ DECLARE n int; BEGIN SELECT COUNT(*) INTO n FROM paliad.scenario_flag_catalog; IF n <> 3 THEN RAISE EXCEPTION '[mig 154] expected 3 seeded flags, found %', n; END IF; RAISE NOTICE '[mig 154] scenario_flags SSoT ready — % flag(s) in catalog', n; END $$; COMMIT;