Files
paliad/internal/db/migrations/010_seed_holidays.up.sql
m 1b2ef28334 feat(db): Phase A — paliad schema, RLS, migrations, golang-migrate
Implements docs/design-kanzlai-integration.md §8 Phase A.

Schema (paliad.*):
- users (extends auth.users) with office, practice_group, role
- akten with visibility columns: owning_office, collaborators uuid[],
  firm_wide_visible (per design §2)
- parteien, fristen, termine, dokumente, akten_events, notizen
  (polymorphic notes; notizen_exactly_one_parent CHECK)
- proceeding_types, deadline_rules, holidays (reference data)
- 4 feedback tables re-namespaced from public.* into paliad.*
  (handler swap to direct DB is a follow-up; old public tables stay
  intact for now and continue serving via PostgREST)

Visibility (paliad.can_see_akte):
- single SQL function, used by every RLS policy
- predicate: firm_wide_visible OR owning_office matches user's office
  OR auth.uid() ∈ collaborators OR user is admin
- mirrored at app layer in Phase B (defense in depth)

RLS (real, not permissive):
- akten: visibility predicate; insert restricted to own office or admin;
  delete restricted to partners + admins
- parteien/fristen/dokumente/akten_events: inherit via can_see_akte(akte_id)
- termine: personal (akte_id NULL) visible only to creator; Akte-linked
  follow visibility predicate
- notizen: paliad.notiz_is_visible() resolves polymorphic parent
- reference tables: SELECT for any authenticated user
- users: SELECT all; UPDATE/INSERT only self
- feedback tables: INSERT for any authenticated user (write-only)

Seed data (ported from KanzlAI seed_upc_timeline.sql):
- 7 proceeding_types (INF, REV, CCR, APM, APP, AMD, ZPO_CIVIL)
- 40 deadline_rules (32 UPC + 4 ZPO + 4 cross-type appeal spawns)
  including conditional logic: Reply rule code (RoP.029b → 029a) and
  Rejoinder duration (1mo → 2mo) flip when CCR active
- 55 holidays (DE federal 2026/2027 + UPC summer 2026 + UPC winter 26/27)

Indexes per audit §3.3 + visibility-predicate hot paths:
- akten: (status, owning_office), (owning_office), partial on
  firm_wide_visible, GIN on collaborators
- fristen: (status, due_date), (akte_id)
- termine: (start_at), (akte_id)
- akten_events: (akte_id, created_at DESC)
- notizen: 4 partial indexes per parent type
- users: (office), (role)

Migration tooling:
- golang-migrate/migrate/v4 with embed.FS source
- Migrations live in internal/db/migrations/ (Go embed can't reach
  outside the package; this is the conventional Go layout for embedded
  migrations)
- Applied at server startup before HTTP listener binds
- DATABASE_URL is optional today (existing knowledge tools work without
  DB); becomes required once Phase B services land
- Mock Supabase auth schema for local testing in
  internal/db/migrations/_dev/mock_supabase_auth.sql (excluded from
  embed pattern by the underscore prefix)

Other changes:
- Dockerfile: bump golang to 1.24, copy go.sum (audit §2.9), rename
  binary patholo → paliad
- docker-compose.yml: add DATABASE_URL passthrough
- README.md: rewritten to reflect Paliad brand + Phase A migration system

Verified locally:
- 11 migrations applied cleanly against postgres:16-alpine
- RLS enabled on all 15 paliad.* tables (verified via pg_class.relrowsecurity)
- Visibility predicate verified with 4-case scenario:
  - Alice (Munich associate): sees Munich + firm-wide + collab-on (t f t t)
  - Bob (Düsseldorf associate): sees Düsseldorf + firm-wide + collab-on (f t t t)
  - Carol (Munich partner): sees Munich + firm-wide only (t f t f)
  - Anonymous: sees firm-wide only (f f t f)
- migrate down + re-up cycle clean (initial 007 down had ordering bug,
  fixed: drop policies before referenced function)
- Existing endpoints (/, /login) return 302 + 200 — no regressions
2026-04-16 13:54:19 +02:00

98 lines
5.9 KiB
SQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- Phase A seed: holidays 2026 + 2027.
--
-- German federal public holidays (observed in all 16 Länder).
-- Easter-relative dates computed manually using Gregorian-algorithm output:
-- 2026: Easter = 2026-04-05 (Karfreitag 04-03, Ostermontag 04-06,
-- Christi Himmelfahrt 05-14,
-- Pfingstsonntag 05-24, Pfingstmontag 05-25)
-- 2027: Easter = 2027-03-28 (Karfreitag 03-26, Ostermontag 03-29,
-- Christi Himmelfahrt 05-06,
-- Pfingstsonntag 05-16, Pfingstmontag 05-17)
--
-- UPC judicial vacations (official 2026 periods per UPC Annual Report of work):
-- Winter: 24 Dec 2026 6 Jan 2027 (holiday_type = 'vacation')
-- Summer: 27 Jul 2026 28 Aug 2026 (holiday_type = 'vacation')
--
-- For 2027 we seed known-fixed dates + Easter-derived set; summer/winter UPC
-- vacations are added once the UPC publishes them.
-- ============================================================================
-- German federal — 2026
-- ============================================================================
INSERT INTO paliad.holidays (date, name, country, holiday_type) VALUES
('2026-01-01', 'Neujahr', 'DE', 'public_holiday'),
('2026-04-03', 'Karfreitag', 'DE', 'public_holiday'),
('2026-04-05', 'Ostersonntag', 'DE', 'public_holiday'),
('2026-04-06', 'Ostermontag', 'DE', 'public_holiday'),
('2026-05-01', 'Tag der Arbeit', 'DE', 'public_holiday'),
('2026-05-14', 'Christi Himmelfahrt', 'DE', 'public_holiday'),
('2026-05-24', 'Pfingstsonntag', 'DE', 'public_holiday'),
('2026-05-25', 'Pfingstmontag', 'DE', 'public_holiday'),
('2026-10-03', 'Tag der Deutschen Einheit', 'DE', 'public_holiday'),
('2026-12-25', '1. Weihnachtstag', 'DE', 'public_holiday'),
('2026-12-26', '2. Weihnachtstag', 'DE', 'public_holiday')
ON CONFLICT (date, name, country) DO NOTHING;
-- ============================================================================
-- UPC summer vacation 2026 (27 Jul 28 Aug, weekdays only — weekends are
-- already non-working days, storing the whole span as vacation entries)
-- ============================================================================
INSERT INTO paliad.holidays (date, name, country, holiday_type) VALUES
('2026-07-27', 'UPC Summer Vacation', 'UPC', 'vacation'),
('2026-07-28', 'UPC Summer Vacation', 'UPC', 'vacation'),
('2026-07-29', 'UPC Summer Vacation', 'UPC', 'vacation'),
('2026-07-30', 'UPC Summer Vacation', 'UPC', 'vacation'),
('2026-07-31', 'UPC Summer Vacation', 'UPC', 'vacation'),
('2026-08-03', 'UPC Summer Vacation', 'UPC', 'vacation'),
('2026-08-04', 'UPC Summer Vacation', 'UPC', 'vacation'),
('2026-08-05', 'UPC Summer Vacation', 'UPC', 'vacation'),
('2026-08-06', 'UPC Summer Vacation', 'UPC', 'vacation'),
('2026-08-07', 'UPC Summer Vacation', 'UPC', 'vacation'),
('2026-08-10', 'UPC Summer Vacation', 'UPC', 'vacation'),
('2026-08-11', 'UPC Summer Vacation', 'UPC', 'vacation'),
('2026-08-12', 'UPC Summer Vacation', 'UPC', 'vacation'),
('2026-08-13', 'UPC Summer Vacation', 'UPC', 'vacation'),
('2026-08-14', 'UPC Summer Vacation', 'UPC', 'vacation'),
('2026-08-17', 'UPC Summer Vacation', 'UPC', 'vacation'),
('2026-08-18', 'UPC Summer Vacation', 'UPC', 'vacation'),
('2026-08-19', 'UPC Summer Vacation', 'UPC', 'vacation'),
('2026-08-20', 'UPC Summer Vacation', 'UPC', 'vacation'),
('2026-08-21', 'UPC Summer Vacation', 'UPC', 'vacation'),
('2026-08-24', 'UPC Summer Vacation', 'UPC', 'vacation'),
('2026-08-25', 'UPC Summer Vacation', 'UPC', 'vacation'),
('2026-08-26', 'UPC Summer Vacation', 'UPC', 'vacation'),
('2026-08-27', 'UPC Summer Vacation', 'UPC', 'vacation'),
('2026-08-28', 'UPC Summer Vacation', 'UPC', 'vacation')
ON CONFLICT (date, name, country) DO NOTHING;
-- ============================================================================
-- UPC winter vacation 2026 → 2027 (24 Dec 2026 6 Jan 2027, weekdays)
-- ============================================================================
INSERT INTO paliad.holidays (date, name, country, holiday_type) VALUES
('2026-12-24', 'UPC Winter Vacation', 'UPC', 'vacation'),
('2026-12-28', 'UPC Winter Vacation', 'UPC', 'vacation'),
('2026-12-29', 'UPC Winter Vacation', 'UPC', 'vacation'),
('2026-12-30', 'UPC Winter Vacation', 'UPC', 'vacation'),
('2026-12-31', 'UPC Winter Vacation', 'UPC', 'vacation'),
('2027-01-04', 'UPC Winter Vacation', 'UPC', 'vacation'),
('2027-01-05', 'UPC Winter Vacation', 'UPC', 'vacation'),
('2027-01-06', 'UPC Winter Vacation', 'UPC', 'vacation')
ON CONFLICT (date, name, country) DO NOTHING;
-- ============================================================================
-- German federal — 2027 (so deadlines falling into early 2027 adjust correctly)
-- ============================================================================
INSERT INTO paliad.holidays (date, name, country, holiday_type) VALUES
('2027-01-01', 'Neujahr', 'DE', 'public_holiday'),
('2027-03-26', 'Karfreitag', 'DE', 'public_holiday'),
('2027-03-28', 'Ostersonntag', 'DE', 'public_holiday'),
('2027-03-29', 'Ostermontag', 'DE', 'public_holiday'),
('2027-05-01', 'Tag der Arbeit', 'DE', 'public_holiday'),
('2027-05-06', 'Christi Himmelfahrt', 'DE', 'public_holiday'),
('2027-05-16', 'Pfingstsonntag', 'DE', 'public_holiday'),
('2027-05-17', 'Pfingstmontag', 'DE', 'public_holiday'),
('2027-10-03', 'Tag der Deutschen Einheit', 'DE', 'public_holiday'),
('2027-12-25', '1. Weihnachtstag', 'DE', 'public_holiday'),
('2027-12-26', '2. Weihnachtstag', 'DE', 'public_holiday')
ON CONFLICT (date, name, country) DO NOTHING;