Phase 2 P0 of the deadline + procedural-events revision. Establishes
paliad.projects.scenario_flags (jsonb) + paliad.scenario_flag_catalog as
the single source of truth for per-project scenario state — replacing
the three fragmented stores athena flagged (project_event_choices,
scenarios.spec, DOM-only). All three were empty per the audit so no
data migration is needed.
The jsonb map carries two key shapes:
* named flags (whitelist via scenario_flag_catalog) — today
with_ccr / with_amend / with_cci
* per-rule selection deviations of shape "rule:<uuid>" — wired up
here for validation; the consumer UI lands in P3
Endpoints:
GET /api/projects/{id}/scenario-flags
PATCH /api/projects/{id}/scenario-flags
PATCH semantics: bool = write; null = delete (priority-driven default
returns); missing key = leave alone. The service validates every key
on write (catalog lookup + UUID rule-membership + mandatory-cannot-be-
deselected) before persisting, so a single bad key fails the whole
patch.
Frontend bind: new scenario-flags.ts client module + Mode B's flag
checkboxes (ccr-flag / inf-amend-flag / rev-amend-flag / rev-cci-flag)
now hydrate from / persist to the project's scenario_flags on every
toggle. Kontextfrei (no project) is unchanged. Cross-surface coherence
via a scenario-flag-changed CustomEvent (peer surfaces — Verfahrens-
ablauf strip, Mode B result-view — will subscribe in P3).
Mig 154 is audit-defensive (set_config of paliad.audit_reason); no
audit trigger fires on paliad.projects today but a future one will
inherit the reason. Seeds the three known flags. CHECK constraints
enforce the top-level shape (jsonb_typeof = 'object') and the
catalog key pattern (lowercase, not 'rule:%' prefix).
Verified against the live DB: 18 projects default to '{}', catalog
has 3 rows, applied_migrations advanced to 154.
Design: docs/design-deadline-system-revision-2026-05-27.md §2.3, §2.4a,
§4.1, §5 (P0 row). t-paliad-331.
140 lines
6.8 KiB
PL/PgSQL
140 lines
6.8 KiB
PL/PgSQL
-- 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:<uuid_of_optional_X>": true,
|
|
-- "rule:<uuid_of_recommended_Y>": false }
|
|
--
|
|
-- Entries are either:
|
|
-- * named scenario flags (whitelist via paliad.scenario_flag_catalog), or
|
|
-- * per-rule selection deviations of shape "rule:<uuid>".
|
|
--
|
|
-- 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:<uuid>". 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:<uuid>") 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:<uuid>") 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;
|